UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

187 lines (185 loc) 8.81 kB
import { generateQueryAlias } from "../utils/generate-alias.js"; import { applyCaseWhen } from "../utils/apply-case-when.js"; import { applyFunctionToColumnName } from "../utils/apply-function-to-column-name.js"; import { getColumn } from "../utils/get-column.js"; import { applyLimit } from "./apply-query/pagination.js"; import { extractFunctionName } from "../../../utils/extract-function-name.js"; import { splitFieldPath } from "../../../utils/split-field-path.js"; import { applySort } from "./apply-query/sort.js"; import applyQuery from "./apply-query/index.js"; import { getHelpers } from "../../helpers/index.js"; import { getCollectionFromAlias } from "../../../utils/get-collection-from-alias.js"; import { getNodeAlias } from "../utils/get-field-alias.js"; import { getColumnPreprocessor } from "../utils/get-column-pre-processor.js"; import { getInnerQueryColumnPreProcessor } from "../utils/get-inner-query-column-pre-processor.js"; import { withPreprocessBindings } from "../utils/with-preprocess-bindings.js"; import { useEnv } from "@directus/env"; import { cloneDeep } from "lodash-es"; //#region src/database/run-ast/lib/get-db-query.ts function getDBQuery({ table, fieldNodes, o2mNodes, query, cases, permissions, permissionsOnly }, { knex, schema }) { const aliasMap = Object.create(null); const env = useEnv(); const preProcess = getColumnPreprocessor(knex, schema, table, cases, permissions, aliasMap, permissionsOnly); const queryCopy = cloneDeep(query); const helpers = getHelpers(knex); const hasCaseWhen = o2mNodes.some((node) => node.whenCase && node.whenCase.length > 0) || fieldNodes.some((node) => node.whenCase && node.whenCase.length > 0); queryCopy.limit = typeof queryCopy.limit === "number" ? queryCopy.limit : Number(env["QUERY_LIMIT_DEFAULT"]); if (queryCopy.aggregate || queryCopy.group) { const flatQuery = knex.from(table); const fieldNodeMap = Object.fromEntries(fieldNodes.map((node, index) => [node.fieldKey, [node, index]])); const groupFieldNodes = queryCopy.group?.map((field) => fieldNodeMap[field][0]) ?? []; const groupWhenCases = hasCaseWhen ? groupFieldNodes.map((node) => node.whenCase ?? []) : void 0; const aggregateCount = Object.entries(queryCopy.aggregate ?? {}).reduce((acc, [_, fields]) => acc + fields.length, 0); const dbQuery$1 = applyQuery(knex, table, flatQuery, queryCopy, schema, cases, permissions, { aliasMap, groupWhenCases, groupColumnPositions: queryCopy.group?.map((field) => fieldNodeMap[field][1] + 1 + aggregateCount) ?? [] }).query; flatQuery.select(fieldNodes.map((node) => preProcess(node))); if (helpers.capabilities.supportsDeduplicationOfParameters() && !helpers.capabilities.supportsColumnPositionInGroupBy()) withPreprocessBindings(knex, dbQuery$1); return dbQuery$1; } const primaryKey = schema.collections[table].primary; const dbQuery = knex.from(table); let sortRecords; const innerQuerySortRecords = []; let hasMultiRelationalSort; if (queryCopy.sort) { const fieldAliasMap = { ...queryCopy.alias ?? {} }; for (const node of fieldNodes) if (node.type === "functionField" && extractFunctionName(node.name) === "json") fieldAliasMap[applyFunctionToColumnName(node.fieldKey)] = node.name; const sortResult = applySort(knex, schema, dbQuery, queryCopy.sort, table, aliasMap, { aggregate: queryCopy.aggregate, returnRecords: true, fieldAliasMap }); if (sortResult) { sortRecords = sortResult.sortRecords; hasMultiRelationalSort = sortResult.hasMultiRelationalSort; } } const { hasMultiRelationalFilter } = applyQuery(knex, table, dbQuery, queryCopy, schema, cases, permissions, { aliasMap, isInnerQuery: true, hasMultiRelationalSort }); const needsInnerQuery = hasMultiRelationalSort || hasMultiRelationalFilter; if (needsInnerQuery) { dbQuery.select(`${table}.${primaryKey}`); if (!hasCaseWhen) dbQuery.distinct(); } else { dbQuery.select(fieldNodes.map((node) => preProcess(node))); dbQuery.select(o2mNodes.filter((node) => node.whenCase && node.whenCase.length > 0).map((node) => { const columnCases = node.whenCase.map((index) => cases[index]); return applyCaseWhen({ column: knex.raw(1), columnCases, aliasMap, cases, table, alias: node.fieldKey, permissions }, { knex, schema }); })); } if (sortRecords) { dbQuery.clear("order"); if (needsInnerQuery) { let orderByString = ""; const orderByFields = []; sortRecords.map((sortRecord, index) => { if (orderByString.length !== 0) orderByString += ", "; const sortAlias = generateQueryAlias(table, queryCopy, `sort_${index}_${sortRecord.column}_${sortRecord.order}`); let orderByColumn; const colParts = splitFieldPath(sortRecord.column); if (colParts.length > 1) { const [alias, ...rest] = colParts; const field = rest.join("."); const originalCollectionName = getCollectionFromAlias(alias, aliasMap); dbQuery.select(getColumn(knex, alias, field, sortAlias, schema, { originalCollectionName })); orderByString += `?? ${sortRecord.order}`; orderByColumn = getColumn(knex, alias, field, false, schema, { originalCollectionName }); } else { dbQuery.select(getColumn(knex, table, sortRecord.column, sortAlias, schema, { jsonReturnType: "text" })); orderByString += `?? ${sortRecord.order}`; orderByColumn = getColumn(knex, table, sortRecord.column, false, schema, { jsonReturnType: "text" }); } orderByFields.push(orderByColumn); innerQuerySortRecords.push({ alias: sortAlias, order: sortRecord.order, column: orderByColumn }); }); if (hasMultiRelationalSort) { dbQuery.rowNumber(knex.ref("directus_row_number").toQuery(), knex.raw(`partition by ?? order by ${orderByString}`, [`${table}.${primaryKey}`, ...orderByFields])); orderByString = `?? asc, ${orderByString}`; orderByFields.unshift(knex.ref("directus_row_number")); } dbQuery.orderByRaw(orderByString, orderByFields); } else { sortRecords.map((sortRecord) => { const colParts = splitFieldPath(sortRecord.column); if (colParts.length > 1) { const [alias, ...rest] = colParts; sortRecord.column = getColumn(knex, alias, rest.join("."), false, schema, { originalCollectionName: getCollectionFromAlias(alias, aliasMap) }); } else sortRecord.column = getColumn(knex, table, sortRecord.column, false, schema, { jsonReturnType: "text" }); }); dbQuery.orderBy(sortRecords); } } if (!needsInnerQuery) return dbQuery; const innerCaseWhenAliasPrefix = generateQueryAlias(table, queryCopy, "inner_case_when"); if (hasCaseWhen) { const innerPreprocess = getInnerQueryColumnPreProcessor(knex, schema, table, cases, permissions, aliasMap, innerCaseWhenAliasPrefix); dbQuery.select(fieldNodes.map(innerPreprocess).filter((x) => x !== null)); dbQuery.select(o2mNodes.map(innerPreprocess).filter((x) => x !== null)); const groupByFields = [knex.raw("??.??", [table, primaryKey])]; helpers.schema.addInnerSortFieldsToGroupBy(groupByFields, innerQuerySortRecords, (hasMultiRelationalSort || sortRecords?.some(({ column }) => column.includes("."))) ?? false); dbQuery.groupBy(groupByFields); } const wrapperQuery = knex.from(table).innerJoin(knex.raw("??", dbQuery.as("inner")), `${table}.${primaryKey}`, `inner.${primaryKey}`); if (!hasCaseWhen) wrapperQuery.select(fieldNodes.map((node) => preProcess(node))); else { const plainColumns = fieldNodes.filter((fieldNode) => !fieldNode.whenCase || fieldNode.whenCase.length === 0); const whenCaseColumns = fieldNodes.filter((fieldNode) => fieldNode.whenCase && fieldNode.whenCase.length > 0); wrapperQuery.select(plainColumns.map((node) => preProcess(node))); wrapperQuery.select(whenCaseColumns.map((fieldNode) => { const alias = getNodeAlias(fieldNode); const innerAlias = `${innerCaseWhenAliasPrefix}_${alias}`; const column = preProcess({ ...fieldNode, whenCase: [] }, { noAlias: true }); return knex.raw(`CASE WHEN ??.?? > 0 THEN ?? END as ??`, [ "inner", innerAlias, column, alias ]); })); wrapperQuery.select(o2mNodes.filter((node) => node.whenCase && node.whenCase.length > 0).map((node) => { const alias = node.fieldKey; const innerAlias = `${innerCaseWhenAliasPrefix}_${alias}`; return knex.raw(`CASE WHEN ??.?? > 0 THEN 1 END as ??`, [ "inner", innerAlias, alias ]); })); } if (sortRecords) { innerQuerySortRecords.map((innerQuerySortRecord) => { wrapperQuery.orderBy(`inner.${innerQuerySortRecord.alias}`, innerQuerySortRecord.order); }); if (hasMultiRelationalSort) { wrapperQuery.where("inner.directus_row_number", "=", 1); applyLimit(knex, wrapperQuery, queryCopy.limit); } } return wrapperQuery; } //#endregion export { getDBQuery };