mirror of
https://github.com/directus/directus.git
synced 2026-04-03 03:00:39 -04:00
Add support for aliasing fields (#7419)
* Don't double split csv values * Still join them on create tho * Add support for `alias` query param * Support aliases in wildcards
This commit is contained in:
@@ -2,7 +2,7 @@ import { Knex } from 'knex';
|
||||
import { clone, cloneDeep, pick, uniq } from 'lodash';
|
||||
import { PayloadService } from '../services/payload';
|
||||
import { Item, Query, SchemaOverview } from '../types';
|
||||
import { AST, FieldNode, NestedCollectionNode } from '../types/ast';
|
||||
import { AST, FieldNode, NestedCollectionNode, M2ONode } from '../types/ast';
|
||||
import { applyFunctionToColumnName } from '../utils/apply-function-to-column-name';
|
||||
import applyQuery from '../utils/apply-query';
|
||||
import { getColumn } from '../utils/get-column';
|
||||
@@ -60,14 +60,15 @@ export default async function runAST(
|
||||
|
||||
async function run(collection: string, children: (NestedCollectionNode | FieldNode)[], query: Query) {
|
||||
// Retrieve the database columns to select in the current AST
|
||||
const { columnsToSelect, primaryKeyField, nestedCollectionNodes } = await parseCurrentLevel(
|
||||
const { fieldNodes, primaryKeyField, nestedCollectionNodes } = await parseCurrentLevel(
|
||||
schema,
|
||||
collection,
|
||||
children
|
||||
children,
|
||||
query
|
||||
);
|
||||
|
||||
// The actual knex query builder instance. This is a promise that resolves with the raw items from the db
|
||||
const dbQuery = await getDBQuery(schema, knex, collection, columnsToSelect, query, options?.nested);
|
||||
const dbQuery = await getDBQuery(schema, knex, collection, fieldNodes, query, options?.nested);
|
||||
|
||||
const rawItems: Item | Item[] = await dbQuery;
|
||||
|
||||
@@ -106,7 +107,8 @@ export default async function runAST(
|
||||
async function parseCurrentLevel(
|
||||
schema: SchemaOverview,
|
||||
collection: string,
|
||||
children: (NestedCollectionNode | FieldNode)[]
|
||||
children: (NestedCollectionNode | FieldNode)[],
|
||||
query: Query
|
||||
) {
|
||||
const primaryKeyField = schema.collections[collection].primary;
|
||||
const columnsInCollection = Object.keys(schema.collections[collection].fields);
|
||||
@@ -117,8 +119,17 @@ async function parseCurrentLevel(
|
||||
for (const child of children) {
|
||||
if (child.type === 'field') {
|
||||
const fieldKey = stripFunction(child.name);
|
||||
|
||||
if (columnsInCollection.includes(fieldKey) || fieldKey === '*') {
|
||||
columnsToSelectInternal.push(child.name); // maintain original name here (includes functions)
|
||||
|
||||
if (query.alias) {
|
||||
columnsToSelectInternal.push(
|
||||
...Object.entries(query.alias)
|
||||
.filter(([_key, value]) => value === child.name)
|
||||
.map(([key]) => key)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -127,7 +138,7 @@ async function parseCurrentLevel(
|
||||
if (!child.relation) continue;
|
||||
|
||||
if (child.type === 'm2o') {
|
||||
columnsToSelectInternal.push(child.relation.field);
|
||||
columnsToSelectInternal.push(child.fieldKey);
|
||||
}
|
||||
|
||||
if (child.type === 'm2a') {
|
||||
@@ -138,30 +149,49 @@ async function parseCurrentLevel(
|
||||
nestedCollectionNodes.push(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Always fetch primary key in case there's a nested relation that needs it
|
||||
const isAggregate = (query.aggregate && Object.keys(query.aggregate).length > 0) ?? false;
|
||||
|
||||
/** Always fetch primary key in case there's a nested relation that needs it. Aggregate payloads
|
||||
* can't have nested relational fields
|
||||
*/
|
||||
const childrenContainRelational = children.some((child) => child.type !== 'field');
|
||||
if (childrenContainRelational && columnsToSelectInternal.includes(primaryKeyField) === false) {
|
||||
if (isAggregate === false && columnsToSelectInternal.includes(primaryKeyField) === false) {
|
||||
columnsToSelectInternal.push(primaryKeyField);
|
||||
}
|
||||
|
||||
/** Make sure select list has unique values */
|
||||
const columnsToSelect = [...new Set(columnsToSelectInternal)];
|
||||
|
||||
return { columnsToSelect, nestedCollectionNodes, primaryKeyField };
|
||||
const fieldNodes = columnsToSelect.map(
|
||||
(column: string) =>
|
||||
children.find((childNode) => childNode.fieldKey === column) ?? { type: 'field', name: column, fieldKey: column }
|
||||
) as FieldNode[];
|
||||
|
||||
return { fieldNodes, nestedCollectionNodes, primaryKeyField };
|
||||
}
|
||||
|
||||
function getColumnPreprocessor(knex: Knex, schema: SchemaOverview, table: string) {
|
||||
const helper = getGeometryHelper();
|
||||
return function (column: string): Knex.Raw<string> {
|
||||
const field = schema.collections[table].fields[column];
|
||||
|
||||
if (isNativeGeometry(field)) {
|
||||
return helper.asText(table, column);
|
||||
return function (fieldNode: FieldNode | M2ONode): Knex.Raw<string> {
|
||||
let field;
|
||||
|
||||
if (fieldNode.type === 'field') {
|
||||
field = schema.collections[table].fields[fieldNode.name];
|
||||
} else {
|
||||
field = schema.collections[fieldNode.relation.collection].fields[fieldNode.relation.field];
|
||||
}
|
||||
|
||||
return getColumn(knex, table, column);
|
||||
let alias = undefined;
|
||||
|
||||
if (fieldNode.name !== fieldNode.fieldKey) {
|
||||
alias = fieldNode.fieldKey;
|
||||
}
|
||||
|
||||
if (isNativeGeometry(field)) {
|
||||
return helper.asText(table, field.field);
|
||||
}
|
||||
|
||||
return getColumn(knex, table, field.field, alias);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -169,12 +199,12 @@ function getDBQuery(
|
||||
schema: SchemaOverview,
|
||||
knex: Knex,
|
||||
table: string,
|
||||
columns: string[],
|
||||
fieldNodes: FieldNode[],
|
||||
query: Query,
|
||||
nested?: boolean
|
||||
): Knex.QueryBuilder {
|
||||
const preProcess = getColumnPreprocessor(knex, schema, table);
|
||||
const dbQuery = knex.select(columns.map(preProcess)).from(table);
|
||||
const dbQuery = knex.select(fieldNodes.map(preProcess)).from(table);
|
||||
const queryCopy = clone(query);
|
||||
|
||||
queryCopy.limit = typeof queryCopy.limit === 'number' ? queryCopy.limit : 100;
|
||||
@@ -217,11 +247,19 @@ function applyParentFilters(
|
||||
});
|
||||
|
||||
if (relatedM2OisFetched === false) {
|
||||
nestedNode.children.push({ type: 'field', name: nestedNode.relation.field });
|
||||
nestedNode.children.push({
|
||||
type: 'field',
|
||||
name: nestedNode.relation.field,
|
||||
fieldKey: nestedNode.relation.field,
|
||||
});
|
||||
}
|
||||
|
||||
if (nestedNode.relation.meta?.sort_field) {
|
||||
nestedNode.children.push({ type: 'field', name: nestedNode.relation.meta.sort_field });
|
||||
nestedNode.children.push({
|
||||
type: 'field',
|
||||
name: nestedNode.relation.meta.sort_field,
|
||||
fieldKey: nestedNode.relation.meta.sort_field,
|
||||
});
|
||||
}
|
||||
|
||||
nestedNode.query = {
|
||||
@@ -399,10 +437,9 @@ function removeTemporaryFields(
|
||||
const nestedCollectionNodes: NestedCollectionNode[] = [];
|
||||
|
||||
for (const child of ast.children) {
|
||||
if (child.type === 'field') {
|
||||
fields.push(child.name);
|
||||
} else {
|
||||
fields.push(child.fieldKey);
|
||||
fields.push(child.fieldKey);
|
||||
|
||||
if (child.type !== 'field') {
|
||||
nestedCollectionNodes.push(child);
|
||||
}
|
||||
}
|
||||
@@ -414,7 +451,7 @@ function removeTemporaryFields(
|
||||
|
||||
if (operation === 'count' && aggregateFields.includes('*')) fields.push('count');
|
||||
|
||||
fields.push(...aggregateFields.map((field) => `${field}_${operation}`));
|
||||
fields.push(...aggregateFields.map((field) => `${operation}.${field}`));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user