@directus/api 20.1.0 → 21.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +5 -5
- package/dist/auth/drivers/ldap.js +5 -5
- package/dist/auth/drivers/local.js +4 -4
- package/dist/auth/drivers/oauth2.js +5 -5
- package/dist/auth/drivers/openid.js +3 -5
- package/dist/auth/drivers/saml.js +1 -1
- package/dist/auth.js +1 -1
- package/dist/cache.js +4 -1
- package/dist/cli/commands/bootstrap/index.js +9 -3
- package/dist/cli/commands/count/index.js +1 -1
- package/dist/cli/commands/database/install.js +1 -1
- package/dist/cli/commands/database/migrate.js +1 -1
- package/dist/cli/commands/init/index.js +9 -10
- package/dist/cli/commands/roles/create.js +1 -1
- package/dist/cli/commands/schema/apply.js +1 -1
- package/dist/cli/commands/schema/snapshot.js +1 -1
- package/dist/cli/commands/users/create.js +1 -1
- package/dist/cli/commands/users/passwd.js +1 -1
- package/dist/cli/load-extensions.js +1 -1
- package/dist/cli/utils/defaults.d.ts +4 -11
- package/dist/cli/utils/defaults.js +7 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +2 -2
- package/dist/controllers/access.d.ts +2 -0
- package/dist/controllers/access.js +148 -0
- package/dist/controllers/assets.js +1 -1
- package/dist/controllers/auth.js +6 -17
- package/dist/controllers/files.js +1 -1
- package/dist/controllers/permissions.js +14 -2
- package/dist/controllers/policies.d.ts +2 -0
- package/dist/controllers/policies.js +169 -0
- package/dist/controllers/roles.js +22 -1
- package/dist/controllers/schema.js +1 -1
- package/dist/controllers/tus.js +11 -23
- package/dist/controllers/users.js +0 -55
- package/dist/database/get-ast-from-query/get-ast-from-query.d.ts +16 -0
- package/dist/database/get-ast-from-query/get-ast-from-query.js +82 -0
- package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +13 -0
- package/dist/database/get-ast-from-query/lib/convert-wildcards.js +69 -0
- package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +15 -0
- package/dist/database/get-ast-from-query/lib/parse-fields.js +190 -0
- package/dist/database/get-ast-from-query/utils/get-deep-query.d.ts +14 -0
- package/dist/database/get-ast-from-query/utils/get-deep-query.js +17 -0
- package/dist/database/get-ast-from-query/utils/get-related-collection.d.ts +2 -0
- package/dist/database/get-ast-from-query/utils/get-related-collection.js +13 -0
- package/dist/database/get-ast-from-query/utils/get-relation.d.ts +2 -0
- package/dist/database/get-ast-from-query/utils/get-relation.js +7 -0
- package/dist/database/helpers/fn/types.d.ts +2 -1
- package/dist/database/helpers/fn/types.js +1 -1
- package/dist/database/helpers/geometry/dialects/mssql.d.ts +1 -1
- package/dist/database/helpers/geometry/dialects/mssql.js +4 -2
- package/dist/database/helpers/geometry/dialects/mysql.js +1 -1
- package/dist/database/helpers/geometry/dialects/oracle.d.ts +1 -1
- package/dist/database/helpers/geometry/dialects/oracle.js +5 -3
- package/dist/database/helpers/geometry/types.d.ts +1 -1
- package/dist/database/helpers/geometry/types.js +4 -2
- package/dist/database/index.js +3 -2
- package/dist/database/migrations/20210518A-add-foreign-key-constraints.js +1 -1
- package/dist/database/migrations/20210519A-add-system-fk-triggers.js +1 -1
- package/dist/database/migrations/20210802A-replace-groups.js +1 -1
- package/dist/database/migrations/20230721A-require-shares-fields.js +1 -1
- package/dist/database/migrations/20240710A-permissions-policies.d.ts +3 -0
- package/dist/database/migrations/20240710A-permissions-policies.js +169 -0
- package/dist/database/migrations/run.js +1 -1
- package/dist/database/run-ast/lib/get-db-query.d.ts +4 -0
- package/dist/database/run-ast/lib/get-db-query.js +208 -0
- package/dist/database/run-ast/lib/parse-current-level.d.ts +7 -0
- package/dist/database/run-ast/lib/parse-current-level.js +41 -0
- package/dist/database/run-ast/run-ast.d.ts +7 -0
- package/dist/database/run-ast/run-ast.js +107 -0
- package/dist/database/{run-ast.d.ts → run-ast/types.d.ts} +3 -9
- package/dist/database/run-ast/types.js +1 -0
- package/dist/database/run-ast/utils/apply-case-when.d.ts +16 -0
- package/dist/database/run-ast/utils/apply-case-when.js +26 -0
- package/dist/database/run-ast/utils/apply-parent-filters.d.ts +3 -0
- package/dist/database/run-ast/utils/apply-parent-filters.js +55 -0
- package/dist/database/run-ast/utils/get-column-pre-processor.d.ts +10 -0
- package/dist/database/run-ast/utils/get-column-pre-processor.js +57 -0
- package/dist/database/run-ast/utils/get-field-alias.d.ts +2 -0
- package/dist/database/run-ast/utils/get-field-alias.js +4 -0
- package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.d.ts +5 -0
- package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.js +23 -0
- package/dist/database/run-ast/utils/merge-with-parent-items.d.ts +3 -0
- package/dist/database/run-ast/utils/merge-with-parent-items.js +87 -0
- package/dist/database/run-ast/utils/remove-temporary-fields.d.ts +3 -0
- package/dist/database/run-ast/utils/remove-temporary-fields.js +73 -0
- package/dist/emitter.js +1 -1
- package/dist/extensions/lib/get-shared-deps-mapping.js +1 -1
- package/dist/extensions/lib/installation/manager.js +1 -1
- package/dist/extensions/lib/sandbox/register/call-reference.js +1 -1
- package/dist/extensions/lib/sandbox/sdk/generators/log.js +1 -1
- package/dist/extensions/lib/sync-extensions.js +1 -1
- package/dist/extensions/manager.js +1 -1
- package/dist/flows.js +4 -5
- package/dist/{logger.js → logger/index.js} +2 -8
- package/dist/logger/redact-query.d.ts +1 -0
- package/dist/logger/redact-query.js +13 -0
- package/dist/mailer.js +1 -1
- package/dist/middleware/authenticate.js +2 -7
- package/dist/middleware/cache.js +2 -2
- package/dist/middleware/error-handler.js +1 -1
- package/dist/middleware/rate-limiter-global.js +1 -1
- package/dist/middleware/respond.js +2 -2
- package/dist/operations/log/index.js +1 -1
- package/dist/operations/mail/index.js +1 -1
- package/dist/permissions/cache.d.ts +2 -0
- package/dist/permissions/cache.js +23 -0
- package/dist/permissions/lib/fetch-permissions.d.ts +10 -0
- package/dist/permissions/lib/fetch-permissions.js +55 -0
- package/dist/permissions/lib/fetch-policies.d.ts +7 -0
- package/dist/permissions/lib/fetch-policies.js +28 -0
- package/dist/permissions/lib/fetch-roles-tree.d.ts +3 -0
- package/dist/permissions/lib/fetch-roles-tree.js +28 -0
- package/dist/{services/permissions → permissions}/lib/with-app-minimal-permissions.d.ts +1 -1
- package/dist/permissions/lib/with-app-minimal-permissions.js +10 -0
- package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.d.ts +7 -0
- package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.js +56 -0
- package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.d.ts +3 -0
- package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.js +16 -0
- package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.d.ts +8 -0
- package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +24 -0
- package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.d.ts +9 -0
- package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +31 -0
- package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.d.ts +16 -0
- package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js +27 -0
- package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +10 -0
- package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +23 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +5 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +7 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +5 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +10 -0
- package/dist/permissions/modules/fetch-global-access/types.d.ts +4 -0
- package/dist/permissions/modules/fetch-global-access/types.js +1 -0
- package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +4 -0
- package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +27 -0
- package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.d.ts +12 -0
- package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +32 -0
- package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +4 -0
- package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.js +29 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.d.ts +4 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.js +49 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.d.ts +3 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.js +56 -0
- package/dist/permissions/modules/process-ast/lib/field-map-from-ast.d.ts +4 -0
- package/dist/permissions/modules/process-ast/lib/field-map-from-ast.js +8 -0
- package/dist/permissions/modules/process-ast/lib/inject-cases.d.ts +9 -0
- package/dist/permissions/modules/process-ast/lib/inject-cases.js +93 -0
- package/dist/permissions/modules/process-ast/process-ast.d.ts +9 -0
- package/dist/permissions/modules/process-ast/process-ast.js +39 -0
- package/dist/permissions/modules/process-ast/types.d.ts +24 -0
- package/dist/permissions/modules/process-ast/types.js +1 -0
- package/dist/permissions/modules/process-ast/utils/collections-in-field-map.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/collections-in-field-map.js +7 -0
- package/dist/permissions/modules/process-ast/utils/dedupe-access.d.ts +12 -0
- package/dist/permissions/modules/process-ast/utils/dedupe-access.js +30 -0
- package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.d.ts +15 -0
- package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +50 -0
- package/dist/permissions/modules/process-ast/utils/find-related-collection.d.ts +3 -0
- package/dist/permissions/modules/process-ast/utils/find-related-collection.js +9 -0
- package/dist/permissions/modules/process-ast/utils/flatten-filter.d.ts +3 -0
- package/dist/permissions/modules/process-ast/utils/flatten-filter.js +34 -0
- package/dist/permissions/modules/process-ast/utils/format-a2o-key.d.ts +1 -0
- package/dist/permissions/modules/process-ast/utils/format-a2o-key.js +3 -0
- package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +5 -0
- package/dist/permissions/modules/process-ast/utils/get-info-for-path.js +7 -0
- package/dist/permissions/modules/process-ast/utils/has-item-permissions.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/has-item-permissions.js +3 -0
- package/dist/permissions/modules/process-ast/utils/stringify-query-path.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/stringify-query-path.js +3 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/create-error.d.ts +3 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/create-error.js +16 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.js +12 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.js +28 -0
- package/dist/permissions/modules/process-payload/lib/is-field-nullable.d.ts +5 -0
- package/dist/permissions/modules/process-payload/lib/is-field-nullable.js +12 -0
- package/dist/permissions/modules/process-payload/process-payload.d.ts +13 -0
- package/dist/permissions/modules/process-payload/process-payload.js +77 -0
- package/dist/permissions/modules/validate-access/lib/validate-collection-access.d.ts +12 -0
- package/dist/permissions/modules/validate-access/lib/validate-collection-access.js +11 -0
- package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +9 -0
- package/dist/permissions/modules/validate-access/lib/validate-item-access.js +33 -0
- package/dist/permissions/modules/validate-access/validate-access.d.ts +14 -0
- package/dist/permissions/modules/validate-access/validate-access.js +28 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.d.ts +1 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.js +8 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +5 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.js +10 -0
- package/dist/permissions/types.d.ts +6 -0
- package/dist/permissions/types.js +1 -0
- package/dist/permissions/utils/create-default-accountability.d.ts +2 -0
- package/dist/permissions/utils/create-default-accountability.js +11 -0
- package/dist/permissions/utils/extract-required-dynamic-variable-context.d.ts +8 -0
- package/dist/permissions/utils/extract-required-dynamic-variable-context.js +27 -0
- package/dist/permissions/utils/fetch-dynamic-variable-context.d.ts +9 -0
- package/dist/permissions/utils/fetch-dynamic-variable-context.js +43 -0
- package/dist/permissions/utils/filter-policies-by-ip.d.ts +2 -0
- package/dist/permissions/utils/filter-policies-by-ip.js +15 -0
- package/dist/permissions/utils/get-unaliased-field-key.d.ts +5 -0
- package/dist/permissions/utils/get-unaliased-field-key.js +17 -0
- package/dist/permissions/utils/process-permissions.d.ts +7 -0
- package/dist/permissions/utils/process-permissions.js +9 -0
- package/dist/permissions/utils/with-cache.d.ts +10 -0
- package/dist/permissions/utils/with-cache.js +25 -0
- package/dist/request/is-denied-ip.js +1 -1
- package/dist/server.js +1 -1
- package/dist/services/access.d.ts +10 -0
- package/dist/services/access.js +43 -0
- package/dist/services/activity.js +23 -11
- package/dist/services/assets.d.ts +2 -3
- package/dist/services/assets.js +11 -6
- package/dist/services/authentication.js +18 -18
- package/dist/services/collections.js +18 -17
- package/dist/services/fields.d.ts +0 -1
- package/dist/services/fields.js +53 -24
- package/dist/services/files/utils/get-metadata.js +1 -1
- package/dist/services/files.js +11 -4
- package/dist/services/graphql/index.d.ts +3 -3
- package/dist/services/graphql/index.js +126 -22
- package/dist/services/graphql/subscription.js +2 -4
- package/dist/services/graphql/utils/process-error.js +1 -1
- package/dist/services/graphql/utils/sanitize-gql-schema.js +1 -1
- package/dist/services/import-export.js +19 -5
- package/dist/services/index.d.ts +3 -2
- package/dist/services/index.js +3 -2
- package/dist/services/items.js +115 -44
- package/dist/services/mail/index.js +1 -1
- package/dist/services/meta.js +60 -23
- package/dist/services/notifications.js +15 -7
- package/dist/services/payload.d.ts +9 -10
- package/dist/services/payload.js +18 -3
- package/dist/services/{permissions/index.d.ts → permissions.d.ts} +5 -7
- package/dist/services/{permissions/index.js → permissions.js} +30 -54
- package/dist/services/policies.d.ts +12 -0
- package/dist/services/policies.js +87 -0
- package/dist/services/relations.d.ts +0 -6
- package/dist/services/relations.js +26 -29
- package/dist/services/roles.d.ts +4 -12
- package/dist/services/roles.js +57 -424
- package/dist/services/server.js +1 -1
- package/dist/services/shares.d.ts +0 -2
- package/dist/services/shares.js +13 -9
- package/dist/services/specifications.d.ts +2 -2
- package/dist/services/specifications.js +39 -27
- package/dist/services/tus/data-store.js +1 -1
- package/dist/services/users.d.ts +1 -5
- package/dist/services/users.js +79 -162
- package/dist/services/utils.js +11 -7
- package/dist/services/versions.d.ts +0 -2
- package/dist/services/versions.js +34 -10
- package/dist/services/webhooks.js +1 -1
- package/dist/telemetry/lib/get-report.js +2 -2
- package/dist/telemetry/lib/track.js +1 -1
- package/dist/telemetry/utils/check-user-limits.d.ts +5 -0
- package/dist/telemetry/utils/check-user-limits.js +19 -0
- package/dist/types/ast.d.ts +43 -1
- package/dist/types/items.d.ts +11 -0
- package/dist/utils/apply-diff.js +1 -1
- package/dist/utils/apply-query.d.ts +4 -3
- package/dist/utils/apply-query.js +37 -8
- package/dist/utils/delete-from-require-cache.js +1 -1
- package/dist/utils/fetch-user-count/fetch-access-lookup.d.ts +17 -0
- package/dist/utils/fetch-user-count/fetch-access-lookup.js +22 -0
- package/dist/utils/fetch-user-count/fetch-access-roles.d.ts +16 -0
- package/dist/utils/fetch-user-count/fetch-access-roles.js +37 -0
- package/dist/utils/fetch-user-count/fetch-active-users.d.ts +6 -0
- package/dist/utils/fetch-user-count/fetch-active-users.js +3 -0
- package/dist/utils/fetch-user-count/fetch-user-count.d.ts +12 -0
- package/dist/utils/fetch-user-count/fetch-user-count.js +57 -0
- package/dist/utils/fetch-user-count/get-user-count-query.d.ts +20 -0
- package/dist/utils/fetch-user-count/get-user-count-query.js +17 -0
- package/dist/utils/get-accountability-for-role.js +16 -25
- package/dist/utils/get-accountability-for-token.js +17 -16
- package/dist/utils/get-cache-key.d.ts +1 -1
- package/dist/utils/get-cache-key.js +12 -1
- package/dist/utils/get-column.d.ts +2 -1
- package/dist/utils/get-column.js +1 -0
- package/dist/utils/get-default-value.js +1 -1
- package/dist/utils/get-ip-from-req.js +1 -1
- package/dist/utils/get-schema.js +1 -1
- package/dist/utils/get-service.js +5 -1
- package/dist/utils/is-url-allowed.js +1 -1
- package/dist/utils/reduce-schema.d.ts +4 -6
- package/dist/utils/reduce-schema.js +16 -32
- package/dist/utils/sanitize-query.js +1 -1
- package/dist/utils/transaction.js +1 -1
- package/dist/utils/validate-env.js +1 -1
- package/dist/utils/validate-storage.js +1 -1
- package/dist/utils/validate-user-count-integrity.d.ts +13 -0
- package/dist/utils/validate-user-count-integrity.js +29 -0
- package/dist/websocket/authenticate.d.ts +0 -2
- package/dist/websocket/authenticate.js +0 -12
- package/dist/websocket/controllers/base.js +1 -1
- package/dist/websocket/controllers/graphql.js +2 -5
- package/dist/websocket/controllers/hooks.js +4 -0
- package/dist/websocket/controllers/rest.js +1 -3
- package/dist/websocket/errors.js +1 -1
- package/dist/websocket/handlers/subscribe.js +0 -2
- package/dist/websocket/utils/items.d.ts +1 -1
- package/package.json +24 -23
- package/dist/database/run-ast.js +0 -458
- package/dist/middleware/check-ip.d.ts +0 -2
- package/dist/middleware/check-ip.js +0 -37
- package/dist/middleware/get-permissions.d.ts +0 -3
- package/dist/middleware/get-permissions.js +0 -10
- package/dist/services/authorization.d.ts +0 -17
- package/dist/services/authorization.js +0 -456
- package/dist/services/permissions/lib/with-app-minimal-permissions.js +0 -13
- package/dist/telemetry/utils/check-increased-user-limits.d.ts +0 -7
- package/dist/telemetry/utils/check-increased-user-limits.js +0 -25
- package/dist/telemetry/utils/get-role-counts-by-roles.d.ts +0 -6
- package/dist/telemetry/utils/get-role-counts-by-roles.js +0 -27
- package/dist/telemetry/utils/get-role-counts-by-users.d.ts +0 -11
- package/dist/telemetry/utils/get-role-counts-by-users.js +0 -34
- package/dist/telemetry/utils/get-user-count.d.ts +0 -8
- package/dist/telemetry/utils/get-user-count.js +0 -33
- package/dist/telemetry/utils/get-user-counts-by-roles.d.ts +0 -7
- package/dist/telemetry/utils/get-user-counts-by-roles.js +0 -35
- package/dist/utils/get-ast-from-query.d.ts +0 -13
- package/dist/utils/get-ast-from-query.js +0 -297
- package/dist/utils/get-permissions.d.ts +0 -2
- package/dist/utils/get-permissions.js +0 -150
- package/dist/utils/merge-permissions-for-share.d.ts +0 -4
- package/dist/utils/merge-permissions-for-share.js +0 -109
- package/dist/utils/merge-permissions.d.ts +0 -3
- package/dist/utils/merge-permissions.js +0 -95
- /package/dist/{logger.d.ts → logger/index.d.ts} +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { cloneDeep } from 'lodash-es';
|
|
3
|
+
import applyQuery, { applyLimit, applySort, generateAlias } from '../../../utils/apply-query.js';
|
|
4
|
+
import { getCollectionFromAlias } from '../../../utils/get-collection-from-alias.js';
|
|
5
|
+
import { getColumn } from '../../../utils/get-column.js';
|
|
6
|
+
import { getHelpers } from '../../helpers/index.js';
|
|
7
|
+
import { applyCaseWhen } from '../utils/apply-case-when.js';
|
|
8
|
+
import { getColumnPreprocessor } from '../utils/get-column-pre-processor.js';
|
|
9
|
+
import { getNodeAlias } from '../utils/get-field-alias.js';
|
|
10
|
+
import { getInnerQueryColumnPreProcessor } from '../utils/get-inner-query-column-pre-processor.js';
|
|
11
|
+
export function getDBQuery(schema, knex, table, fieldNodes, o2mNodes, query, cases) {
|
|
12
|
+
const aliasMap = Object.create(null);
|
|
13
|
+
const env = useEnv();
|
|
14
|
+
const preProcess = getColumnPreprocessor(knex, schema, table, cases, aliasMap);
|
|
15
|
+
const queryCopy = cloneDeep(query);
|
|
16
|
+
const helpers = getHelpers(knex);
|
|
17
|
+
const hasCaseWhen = o2mNodes.some((node) => node.whenCase && node.whenCase.length > 0) ||
|
|
18
|
+
fieldNodes.some((node) => node.whenCase && node.whenCase.length > 0);
|
|
19
|
+
queryCopy.limit = typeof queryCopy.limit === 'number' ? queryCopy.limit : Number(env['QUERY_LIMIT_DEFAULT']);
|
|
20
|
+
// Queries with aggregates and groupBy will not have duplicate result
|
|
21
|
+
if (queryCopy.aggregate || queryCopy.group) {
|
|
22
|
+
const flatQuery = knex.from(table).select(fieldNodes.map((node) => preProcess(node)));
|
|
23
|
+
return applyQuery(knex, table, flatQuery, queryCopy, schema, cases).query;
|
|
24
|
+
}
|
|
25
|
+
const primaryKey = schema.collections[table].primary;
|
|
26
|
+
let dbQuery = knex.from(table);
|
|
27
|
+
let sortRecords;
|
|
28
|
+
const innerQuerySortRecords = [];
|
|
29
|
+
let hasMultiRelationalSort;
|
|
30
|
+
if (queryCopy.sort) {
|
|
31
|
+
const sortResult = applySort(knex, schema, dbQuery, queryCopy, table, aliasMap, true);
|
|
32
|
+
if (sortResult) {
|
|
33
|
+
sortRecords = sortResult.sortRecords;
|
|
34
|
+
hasMultiRelationalSort = sortResult.hasMultiRelationalSort;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const { hasMultiRelationalFilter } = applyQuery(knex, table, dbQuery, queryCopy, schema, cases, {
|
|
38
|
+
aliasMap,
|
|
39
|
+
isInnerQuery: true,
|
|
40
|
+
hasMultiRelationalSort,
|
|
41
|
+
});
|
|
42
|
+
const needsInnerQuery = hasMultiRelationalSort || hasMultiRelationalFilter;
|
|
43
|
+
if (needsInnerQuery) {
|
|
44
|
+
dbQuery.select(`${table}.${primaryKey}`);
|
|
45
|
+
// Only add distinct if there are no case/when constructs, since otherwise we rely on group by
|
|
46
|
+
if (!hasCaseWhen)
|
|
47
|
+
dbQuery.distinct();
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
dbQuery.select(fieldNodes.map((node) => preProcess(node)));
|
|
51
|
+
// Add flags for o2m fields with case/when to the let the DB to the partial item permissions
|
|
52
|
+
dbQuery.select(o2mNodes
|
|
53
|
+
.filter((node) => node.whenCase && node.whenCase.length > 0)
|
|
54
|
+
.map((node) => {
|
|
55
|
+
const columnCases = node.whenCase.map((index) => cases[index]);
|
|
56
|
+
return applyCaseWhen({
|
|
57
|
+
column: knex.raw(1),
|
|
58
|
+
columnCases,
|
|
59
|
+
aliasMap,
|
|
60
|
+
cases,
|
|
61
|
+
table,
|
|
62
|
+
alias: node.fieldKey,
|
|
63
|
+
}, { knex, schema });
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
if (sortRecords) {
|
|
67
|
+
// Clears the order if any, eg: from MSSQL offset
|
|
68
|
+
dbQuery.clear('order');
|
|
69
|
+
if (needsInnerQuery) {
|
|
70
|
+
let orderByString = '';
|
|
71
|
+
const orderByFields = [];
|
|
72
|
+
sortRecords.map((sortRecord) => {
|
|
73
|
+
if (orderByString.length !== 0) {
|
|
74
|
+
orderByString += ', ';
|
|
75
|
+
}
|
|
76
|
+
const sortAlias = `sort_${generateAlias()}`;
|
|
77
|
+
if (sortRecord.column.includes('.')) {
|
|
78
|
+
const [alias, field] = sortRecord.column.split('.');
|
|
79
|
+
const originalCollectionName = getCollectionFromAlias(alias, aliasMap);
|
|
80
|
+
dbQuery.select(getColumn(knex, alias, field, sortAlias, schema, { originalCollectionName }));
|
|
81
|
+
orderByString += `?? ${sortRecord.order}`;
|
|
82
|
+
orderByFields.push(getColumn(knex, alias, field, false, schema, { originalCollectionName }));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
dbQuery.select(getColumn(knex, table, sortRecord.column, sortAlias, schema));
|
|
86
|
+
orderByString += `?? ${sortRecord.order}`;
|
|
87
|
+
orderByFields.push(getColumn(knex, table, sortRecord.column, false, schema));
|
|
88
|
+
}
|
|
89
|
+
innerQuerySortRecords.push({ alias: sortAlias, order: sortRecord.order });
|
|
90
|
+
});
|
|
91
|
+
if (hasMultiRelationalSort) {
|
|
92
|
+
dbQuery = helpers.schema.applyMultiRelationalSort(knex, dbQuery, table, primaryKey, orderByString, orderByFields);
|
|
93
|
+
// Start order by with directus_row_number. The directus_row_number is derived from a window function that
|
|
94
|
+
// is ordered by the sort fields within every primary key partition. That ensures that the result with the
|
|
95
|
+
// row number = 1 is the top-most row of every partition, according to the selected sort fields.
|
|
96
|
+
// Since the only relevant result is the first row of this partition, adding the directus_row_number to the
|
|
97
|
+
// order by here ensures that all rows with a directus_row_number = 1 show up first in the inner query result,
|
|
98
|
+
// and are correctly truncated by the limit, but not earlier.
|
|
99
|
+
orderByString = `?? asc, ${orderByString}`;
|
|
100
|
+
orderByFields.unshift(knex.ref('directus_row_number'));
|
|
101
|
+
}
|
|
102
|
+
dbQuery.orderByRaw(orderByString, orderByFields);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
sortRecords.map((sortRecord) => {
|
|
106
|
+
if (sortRecord.column.includes('.')) {
|
|
107
|
+
const [alias, field] = sortRecord.column.split('.');
|
|
108
|
+
sortRecord.column = getColumn(knex, alias, field, false, schema, {
|
|
109
|
+
originalCollectionName: getCollectionFromAlias(alias, aliasMap),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
sortRecord.column = getColumn(knex, table, sortRecord.column, false, schema);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
dbQuery.orderBy(sortRecords);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!needsInnerQuery)
|
|
120
|
+
return dbQuery;
|
|
121
|
+
const innerCaseWhenAliasPrefix = generateAlias();
|
|
122
|
+
if (hasCaseWhen) {
|
|
123
|
+
/* If there are cases, we need to employ a trick in order to evaluate the case/when structure in the inner query,
|
|
124
|
+
while passing the result of the evaluation to the outer query. The case/when needs to be evaluated in the inner
|
|
125
|
+
query since only there all joined in tables, that might be required for the case/when, are available.
|
|
126
|
+
|
|
127
|
+
The problem is, that the resulting columns can not be directly selected in the inner query,
|
|
128
|
+
as a `SELECT DISTINCT` does not work for all datatypes in all vendors.
|
|
129
|
+
|
|
130
|
+
So instead of having an inner query which might look like this:
|
|
131
|
+
|
|
132
|
+
SELECT DISTINCT ...,
|
|
133
|
+
CASE WHEN <condition> THEN <actual-column> END AS <alias>
|
|
134
|
+
|
|
135
|
+
Another problem is that all not all rows with the same primary key are guaranteed to have the same value for
|
|
136
|
+
the columns with the case/when, so we to `or` those together, but counting the number of flags in a group by
|
|
137
|
+
operation. This way the flag is set to > 0 if any of the rows in the group allows access to the column.
|
|
138
|
+
|
|
139
|
+
The inner query only evaluates the condition and passes up or-ed flag, that is used in the wrapper query to select
|
|
140
|
+
the actual column:
|
|
141
|
+
|
|
142
|
+
SELECT ...,
|
|
143
|
+
COUNT (CASE WHEN <condition> THEN 1 END) AS <random-prefix>_<alias>
|
|
144
|
+
...
|
|
145
|
+
GROUP BY <primary-key>
|
|
146
|
+
|
|
147
|
+
Then, in the wrapper query there is no need to evaluate the condition again, but instead rely on the flag:
|
|
148
|
+
|
|
149
|
+
SELECT ...,
|
|
150
|
+
CASE WHEN `inner`.<random-prefix>_<alias> > 0 THEN <actual-column> END AS <alias>
|
|
151
|
+
*/
|
|
152
|
+
const innerPreprocess = getInnerQueryColumnPreProcessor(knex, schema, table, cases, aliasMap, innerCaseWhenAliasPrefix);
|
|
153
|
+
// To optimize the query we avoid having unnecessary columns in the inner query, that don't have a caseWhen, since
|
|
154
|
+
// they are selected in the outer query directly
|
|
155
|
+
dbQuery.select(fieldNodes.map(innerPreprocess).filter((x) => x !== null));
|
|
156
|
+
// In addition to the regular columns select a flag that indicates if a user has access to o2m related field
|
|
157
|
+
// based on the case/when of that field.
|
|
158
|
+
dbQuery.select(o2mNodes.map(innerPreprocess).filter((x) => x !== null));
|
|
159
|
+
const groupByFields = [knex.raw('??.??', [table, primaryKey])];
|
|
160
|
+
if (hasMultiRelationalSort) {
|
|
161
|
+
// Sort fields that are not directly in the table the primary key is from need to be included in the group
|
|
162
|
+
// by clause, otherwise this causes problems on some DBs
|
|
163
|
+
groupByFields.push(...innerQuerySortRecords.map(({ alias }) => knex.raw('??', alias)));
|
|
164
|
+
}
|
|
165
|
+
dbQuery.groupBy(groupByFields);
|
|
166
|
+
}
|
|
167
|
+
const wrapperQuery = knex
|
|
168
|
+
.from(table)
|
|
169
|
+
.innerJoin(knex.raw('??', dbQuery.as('inner')), `${table}.${primaryKey}`, `inner.${primaryKey}`);
|
|
170
|
+
if (!hasCaseWhen) {
|
|
171
|
+
// No need for case/when in the wrapper query, just select the preprocessed columns
|
|
172
|
+
wrapperQuery.select(fieldNodes.map((node) => preProcess(node)));
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// This applies a simplified case/when construct in the wrapper query, that only looks at flag > 1
|
|
176
|
+
// Distinguish between column with and without case/when and handle them differently
|
|
177
|
+
const plainColumns = fieldNodes.filter((fieldNode) => !fieldNode.whenCase || fieldNode.whenCase.length === 0);
|
|
178
|
+
const whenCaseColumns = fieldNodes.filter((fieldNode) => fieldNode.whenCase && fieldNode.whenCase.length > 0);
|
|
179
|
+
// Select the plain columns
|
|
180
|
+
wrapperQuery.select(plainColumns.map((node) => preProcess(node)));
|
|
181
|
+
// Select the case/when columns based on the flag from the inner query
|
|
182
|
+
wrapperQuery.select(whenCaseColumns.map((fieldNode) => {
|
|
183
|
+
const alias = getNodeAlias(fieldNode);
|
|
184
|
+
const innerAlias = `${innerCaseWhenAliasPrefix}_${alias}`;
|
|
185
|
+
// Preprocess the column without the case/when, since that is applied in a simpler fashion in the select
|
|
186
|
+
const column = preProcess({ ...fieldNode, whenCase: [] }, { noAlias: true });
|
|
187
|
+
return knex.raw(`CASE WHEN ??.?? > 0 THEN ?? END as ??`, ['inner', innerAlias, column, alias]);
|
|
188
|
+
}));
|
|
189
|
+
// Pass the flags of o2m fields up through the wrapper query
|
|
190
|
+
wrapperQuery.select(o2mNodes
|
|
191
|
+
.filter((node) => node.whenCase && node.whenCase.length > 0)
|
|
192
|
+
.map((node) => {
|
|
193
|
+
const alias = node.fieldKey;
|
|
194
|
+
const innerAlias = `${innerCaseWhenAliasPrefix}_${alias}`;
|
|
195
|
+
return knex.raw(`CASE WHEN ??.?? > 0 THEN 1 END as ??`, ['inner', innerAlias, alias]);
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
if (sortRecords) {
|
|
199
|
+
innerQuerySortRecords.map((innerQuerySortRecord) => {
|
|
200
|
+
wrapperQuery.orderBy(`inner.${innerQuerySortRecord.alias}`, innerQuerySortRecord.order);
|
|
201
|
+
});
|
|
202
|
+
if (hasMultiRelationalSort) {
|
|
203
|
+
wrapperQuery.where('inner.directus_row_number', '=', 1);
|
|
204
|
+
applyLimit(knex, wrapperQuery, queryCopy.limit);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return wrapperQuery;
|
|
208
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Query, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { FieldNode, FunctionFieldNode, NestedCollectionNode } from '../../../types/ast.js';
|
|
3
|
+
export declare function parseCurrentLevel(schema: SchemaOverview, collection: string, children: (NestedCollectionNode | FieldNode | FunctionFieldNode)[], query: Query): Promise<{
|
|
4
|
+
fieldNodes: FieldNode[];
|
|
5
|
+
nestedCollectionNodes: NestedCollectionNode[];
|
|
6
|
+
primaryKeyField: string;
|
|
7
|
+
}>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { parseFilterKey } from '../../../utils/parse-filter-key.js';
|
|
2
|
+
export async function parseCurrentLevel(schema, collection, children, query) {
|
|
3
|
+
const primaryKeyField = schema.collections[collection].primary;
|
|
4
|
+
const columnsInCollection = Object.keys(schema.collections[collection].fields);
|
|
5
|
+
const columnsToSelectInternal = [];
|
|
6
|
+
const nestedCollectionNodes = [];
|
|
7
|
+
for (const child of children) {
|
|
8
|
+
if (child.type === 'field' || child.type === 'functionField') {
|
|
9
|
+
const { fieldName } = parseFilterKey(child.name);
|
|
10
|
+
if (columnsInCollection.includes(fieldName)) {
|
|
11
|
+
columnsToSelectInternal.push(child.fieldKey);
|
|
12
|
+
}
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (!child.relation)
|
|
16
|
+
continue;
|
|
17
|
+
if (child.type === 'm2o') {
|
|
18
|
+
columnsToSelectInternal.push(child.relation.field);
|
|
19
|
+
}
|
|
20
|
+
if (child.type === 'a2o') {
|
|
21
|
+
columnsToSelectInternal.push(child.relation.field);
|
|
22
|
+
columnsToSelectInternal.push(child.relation.meta.one_collection_field);
|
|
23
|
+
}
|
|
24
|
+
nestedCollectionNodes.push(child);
|
|
25
|
+
}
|
|
26
|
+
const isAggregate = (query.group || (query.aggregate && Object.keys(query.aggregate).length > 0)) ?? false;
|
|
27
|
+
/** Always fetch primary key in case there's a nested relation that needs it. Aggregate payloads
|
|
28
|
+
* can't have nested relational fields
|
|
29
|
+
*/
|
|
30
|
+
if (isAggregate === false && columnsToSelectInternal.includes(primaryKeyField) === false) {
|
|
31
|
+
columnsToSelectInternal.push(primaryKeyField);
|
|
32
|
+
}
|
|
33
|
+
/** Make sure select list has unique values */
|
|
34
|
+
const columnsToSelect = [...new Set(columnsToSelectInternal)];
|
|
35
|
+
const fieldNodes = columnsToSelect.map((column) => children.find((childNode) => (childNode.type === 'field' || childNode.type === 'functionField') && childNode.fieldKey === column) ?? {
|
|
36
|
+
type: 'field',
|
|
37
|
+
name: column,
|
|
38
|
+
fieldKey: column,
|
|
39
|
+
});
|
|
40
|
+
return { fieldNodes, nestedCollectionNodes, primaryKeyField };
|
|
41
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Item, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { AST, NestedCollectionNode } from '../../types/ast.js';
|
|
3
|
+
import type { RunASTOptions } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Execute a given AST using Knex. Returns array of items based on requested AST.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runAst(originalAST: AST | NestedCollectionNode, schema: SchemaOverview, options?: RunASTOptions): Promise<null | Item | Item[]>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { cloneDeep, merge } from 'lodash-es';
|
|
3
|
+
import { PayloadService } from '../../services/payload.js';
|
|
4
|
+
import getDatabase from '../index.js';
|
|
5
|
+
import { getDBQuery } from './lib/get-db-query.js';
|
|
6
|
+
import { parseCurrentLevel } from './lib/parse-current-level.js';
|
|
7
|
+
import { applyParentFilters } from './utils/apply-parent-filters.js';
|
|
8
|
+
import { mergeWithParentItems } from './utils/merge-with-parent-items.js';
|
|
9
|
+
import { removeTemporaryFields } from './utils/remove-temporary-fields.js';
|
|
10
|
+
/**
|
|
11
|
+
* Execute a given AST using Knex. Returns array of items based on requested AST.
|
|
12
|
+
*/
|
|
13
|
+
export async function runAst(originalAST, schema, options) {
|
|
14
|
+
const ast = cloneDeep(originalAST);
|
|
15
|
+
const knex = options?.knex || getDatabase();
|
|
16
|
+
if (ast.type === 'a2o') {
|
|
17
|
+
const results = {};
|
|
18
|
+
for (const collection of ast.names) {
|
|
19
|
+
results[collection] = await run(collection, ast.children[collection], ast.query[collection], ast.cases[collection] ?? []);
|
|
20
|
+
}
|
|
21
|
+
return results;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
return await run(ast.name, ast.children, options?.query || ast.query, ast.cases);
|
|
25
|
+
}
|
|
26
|
+
async function run(collection, children, query, cases) {
|
|
27
|
+
const env = useEnv();
|
|
28
|
+
// Retrieve the database columns to select in the current AST
|
|
29
|
+
const { fieldNodes, primaryKeyField, nestedCollectionNodes } = await parseCurrentLevel(schema, collection, children, query);
|
|
30
|
+
const o2mNodes = nestedCollectionNodes.filter((node) => node.type === 'o2m');
|
|
31
|
+
// The actual knex query builder instance. This is a promise that resolves with the raw items from the db
|
|
32
|
+
const dbQuery = getDBQuery(schema, knex, collection, fieldNodes, o2mNodes, query, cases);
|
|
33
|
+
const rawItems = await dbQuery;
|
|
34
|
+
if (!rawItems)
|
|
35
|
+
return null;
|
|
36
|
+
// Run the items through the special transforms
|
|
37
|
+
const payloadService = new PayloadService(collection, { knex, schema });
|
|
38
|
+
let items = await payloadService.processValues('read', rawItems, query.alias ?? {});
|
|
39
|
+
if (!items || (Array.isArray(items) && items.length === 0))
|
|
40
|
+
return items;
|
|
41
|
+
// Apply the `_in` filters to the nested collection batches
|
|
42
|
+
const nestedNodes = applyParentFilters(schema, nestedCollectionNodes, items);
|
|
43
|
+
for (const nestedNode of nestedNodes) {
|
|
44
|
+
let nestedItems = [];
|
|
45
|
+
if (nestedNode.type === 'o2m') {
|
|
46
|
+
let hasMore = true;
|
|
47
|
+
let batchCount = 0;
|
|
48
|
+
// If a nested node has a whenCase it indicates that the user might not be able to access the field for all items.
|
|
49
|
+
// In that case the queried item includes a flag under the fieldKey that is populated in the db and indicates
|
|
50
|
+
// if the user has access to that field for that specific item.
|
|
51
|
+
const hasWhenCase = nestedNode.whenCase && nestedNode.whenCase.length > 0;
|
|
52
|
+
let fieldAllowed = true;
|
|
53
|
+
if (hasWhenCase) {
|
|
54
|
+
// Extract flag and remove field from item, so it can be populated with the actual items
|
|
55
|
+
if (Array.isArray(items)) {
|
|
56
|
+
fieldAllowed = [];
|
|
57
|
+
for (const item of items) {
|
|
58
|
+
fieldAllowed.push(!!item[nestedNode.fieldKey]);
|
|
59
|
+
delete item[nestedNode.fieldKey];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
fieldAllowed = !!items[nestedNode.fieldKey];
|
|
64
|
+
delete items[nestedNode.fieldKey];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
while (hasMore) {
|
|
68
|
+
const node = merge({}, nestedNode, {
|
|
69
|
+
query: {
|
|
70
|
+
limit: env['RELATIONAL_BATCH_SIZE'],
|
|
71
|
+
offset: batchCount * env['RELATIONAL_BATCH_SIZE'],
|
|
72
|
+
page: null,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
nestedItems = (await runAst(node, schema, { knex, nested: true }));
|
|
76
|
+
if (nestedItems) {
|
|
77
|
+
items = mergeWithParentItems(schema, nestedItems, items, nestedNode, fieldAllowed);
|
|
78
|
+
}
|
|
79
|
+
if (!nestedItems || nestedItems.length < env['RELATIONAL_BATCH_SIZE']) {
|
|
80
|
+
hasMore = false;
|
|
81
|
+
}
|
|
82
|
+
batchCount++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const node = merge({}, nestedNode, {
|
|
87
|
+
query: { limit: -1 },
|
|
88
|
+
});
|
|
89
|
+
nestedItems = (await runAst(node, schema, { knex, nested: true }));
|
|
90
|
+
if (nestedItems) {
|
|
91
|
+
// Merge all fetched nested records with the parent items
|
|
92
|
+
items = mergeWithParentItems(schema, nestedItems, items, nestedNode, true);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// During the fetching of data, we have to inject a couple of required fields for the child nesting
|
|
97
|
+
// to work (primary / foreign keys) even if they're not explicitly requested. After all fetching
|
|
98
|
+
// and nesting is done, we parse through the output structure, and filter out all non-requested
|
|
99
|
+
// fields
|
|
100
|
+
// The field allowed flags injected in `getDBQuery` are already removed while processing the nested nodes in
|
|
101
|
+
// the previous step.
|
|
102
|
+
if (options?.nested !== true && options?.stripNonRequested !== false) {
|
|
103
|
+
items = removeTemporaryFields(schema, items, originalAST, primaryKeyField);
|
|
104
|
+
}
|
|
105
|
+
return items;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { Item, SchemaOverview } from '@directus/types';
|
|
2
1
|
import type { Knex } from 'knex';
|
|
3
|
-
import type { AST
|
|
4
|
-
|
|
2
|
+
import type { AST } from '../../types/ast.js';
|
|
3
|
+
export interface RunASTOptions {
|
|
5
4
|
/**
|
|
6
5
|
* Query override for the current level
|
|
7
6
|
*/
|
|
@@ -18,9 +17,4 @@ type RunASTOptions = {
|
|
|
18
17
|
* Whether or not to strip out non-requested required fields automatically (eg IDs / FKs)
|
|
19
18
|
*/
|
|
20
19
|
stripNonRequested?: boolean;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Execute a given AST using Knex. Returns array of items based on requested AST.
|
|
24
|
-
*/
|
|
25
|
-
export default function runAST(originalAST: AST | NestedCollectionNode, schema: SchemaOverview, options?: RunASTOptions): Promise<null | Item | Item[]>;
|
|
26
|
-
export {};
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Filter, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
import type { AliasMap } from '../../../utils/get-column-path.js';
|
|
4
|
+
export interface ApplyCaseWhenOptions {
|
|
5
|
+
column: Knex.Raw;
|
|
6
|
+
columnCases: Filter[];
|
|
7
|
+
table: string;
|
|
8
|
+
cases: Filter[];
|
|
9
|
+
aliasMap: AliasMap;
|
|
10
|
+
alias?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ApplyCaseWhenContext {
|
|
13
|
+
knex: Knex;
|
|
14
|
+
schema: SchemaOverview;
|
|
15
|
+
}
|
|
16
|
+
export declare function applyCaseWhen({ columnCases, table, aliasMap, cases, column, alias }: ApplyCaseWhenOptions, { knex, schema }: ApplyCaseWhenContext): Knex.Raw;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { applyFilter } from '../../../utils/apply-query.js';
|
|
2
|
+
export function applyCaseWhen({ columnCases, table, aliasMap, cases, column, alias }, { knex, schema }) {
|
|
3
|
+
const caseQuery = knex.queryBuilder();
|
|
4
|
+
applyFilter(knex, schema, caseQuery, { _or: columnCases }, table, aliasMap, cases);
|
|
5
|
+
const compiler = knex.client.queryCompiler(caseQuery);
|
|
6
|
+
const sqlParts = [];
|
|
7
|
+
// Only empty filters, so no where was generated, skip it
|
|
8
|
+
if (!compiler.grouped.where)
|
|
9
|
+
return column;
|
|
10
|
+
for (const statement of compiler.grouped.where) {
|
|
11
|
+
const val = compiler[statement.type](statement);
|
|
12
|
+
if (val) {
|
|
13
|
+
if (sqlParts.length > 0) {
|
|
14
|
+
sqlParts.push(statement.bool);
|
|
15
|
+
}
|
|
16
|
+
sqlParts.push(val);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const sql = sqlParts.join(' ');
|
|
20
|
+
const bindings = caseQuery.toSQL().bindings;
|
|
21
|
+
const result = knex.raw(`(CASE WHEN ${sql} THEN ?? END)`, [...bindings, column]);
|
|
22
|
+
if (alias) {
|
|
23
|
+
return knex.raw(result + ' AS ??', [alias]);
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Item, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { NestedCollectionNode } from '../../../types/ast.js';
|
|
3
|
+
export declare function applyParentFilters(schema: SchemaOverview, nestedCollectionNodes: NestedCollectionNode[], parentItem: Item | Item[]): NestedCollectionNode[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { toArray } from '@directus/utils';
|
|
2
|
+
import { isNil, merge, uniq } from 'lodash-es';
|
|
3
|
+
export function applyParentFilters(schema, nestedCollectionNodes, parentItem) {
|
|
4
|
+
const parentItems = toArray(parentItem);
|
|
5
|
+
for (const nestedNode of nestedCollectionNodes) {
|
|
6
|
+
if (!nestedNode.relation)
|
|
7
|
+
continue;
|
|
8
|
+
if (nestedNode.type === 'm2o') {
|
|
9
|
+
const foreignField = schema.collections[nestedNode.relation.related_collection].primary;
|
|
10
|
+
const foreignIds = uniq(parentItems.map((res) => res[nestedNode.relation.field])).filter((id) => !isNil(id));
|
|
11
|
+
merge(nestedNode, { query: { filter: { [foreignField]: { _in: foreignIds } } } });
|
|
12
|
+
}
|
|
13
|
+
else if (nestedNode.type === 'o2m') {
|
|
14
|
+
const relatedM2OisFetched = !!nestedNode.children.find((child) => {
|
|
15
|
+
return child.type === 'field' && child.name === nestedNode.relation.field;
|
|
16
|
+
});
|
|
17
|
+
if (relatedM2OisFetched === false) {
|
|
18
|
+
nestedNode.children.push({
|
|
19
|
+
type: 'field',
|
|
20
|
+
name: nestedNode.relation.field,
|
|
21
|
+
fieldKey: nestedNode.relation.field,
|
|
22
|
+
whenCase: [],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (nestedNode.relation.meta?.sort_field) {
|
|
26
|
+
nestedNode.children.push({
|
|
27
|
+
type: 'field',
|
|
28
|
+
name: nestedNode.relation.meta.sort_field,
|
|
29
|
+
fieldKey: nestedNode.relation.meta.sort_field,
|
|
30
|
+
whenCase: [],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const foreignField = nestedNode.relation.field;
|
|
34
|
+
const foreignIds = uniq(parentItems.map((res) => res[nestedNode.parentKey])).filter((id) => !isNil(id));
|
|
35
|
+
merge(nestedNode, { query: { filter: { [foreignField]: { _in: foreignIds } } } });
|
|
36
|
+
}
|
|
37
|
+
else if (nestedNode.type === 'a2o') {
|
|
38
|
+
const keysPerCollection = {};
|
|
39
|
+
for (const parentItem of parentItems) {
|
|
40
|
+
const collection = parentItem[nestedNode.relation.meta.one_collection_field];
|
|
41
|
+
if (!keysPerCollection[collection])
|
|
42
|
+
keysPerCollection[collection] = [];
|
|
43
|
+
keysPerCollection[collection].push(parentItem[nestedNode.relation.field]);
|
|
44
|
+
}
|
|
45
|
+
for (const relatedCollection of nestedNode.names) {
|
|
46
|
+
const foreignField = nestedNode.relatedKey[relatedCollection];
|
|
47
|
+
const foreignIds = uniq(keysPerCollection[relatedCollection]);
|
|
48
|
+
merge(nestedNode, {
|
|
49
|
+
query: { [relatedCollection]: { filter: { [foreignField]: { _in: foreignIds } }, limit: foreignIds.length } },
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return nestedCollectionNodes;
|
|
55
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Filter, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
import type { FieldNode, FunctionFieldNode, M2ONode } from '../../../types/ast.js';
|
|
4
|
+
import type { AliasMap } from '../../../utils/get-column-path.js';
|
|
5
|
+
interface NodePreProcessOptions {
|
|
6
|
+
/** Don't assign an alias to the column but instead return the column as is */
|
|
7
|
+
noAlias?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function getColumnPreprocessor(knex: Knex, schema: SchemaOverview, table: string, cases: Filter[], aliasMap: AliasMap): (fieldNode: FieldNode | FunctionFieldNode | M2ONode, options?: NodePreProcessOptions) => Knex.Raw<string>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { joinFilterWithCases } from '../../../utils/apply-query.js';
|
|
2
|
+
import { getColumn } from '../../../utils/get-column.js';
|
|
3
|
+
import { parseFilterKey } from '../../../utils/parse-filter-key.js';
|
|
4
|
+
import { getHelpers } from '../../helpers/index.js';
|
|
5
|
+
import { applyCaseWhen } from './apply-case-when.js';
|
|
6
|
+
import { getNodeAlias } from './get-field-alias.js';
|
|
7
|
+
export function getColumnPreprocessor(knex, schema, table, cases, aliasMap) {
|
|
8
|
+
const helpers = getHelpers(knex);
|
|
9
|
+
return function (fieldNode, options) {
|
|
10
|
+
// Don't assign an alias to the column expression if the field has a whenCase
|
|
11
|
+
// (since the alias will be assigned in applyCaseWhen) or if the noAlias option is set
|
|
12
|
+
const hasWhenCase = fieldNode.whenCase && fieldNode.whenCase.length > 0;
|
|
13
|
+
const noAlias = options?.noAlias || hasWhenCase;
|
|
14
|
+
const alias = getNodeAlias(fieldNode);
|
|
15
|
+
const rawColumnAlias = noAlias ? false : alias;
|
|
16
|
+
let field;
|
|
17
|
+
if (fieldNode.type === 'field' || fieldNode.type === 'functionField') {
|
|
18
|
+
const { fieldName } = parseFilterKey(fieldNode.name);
|
|
19
|
+
field = schema.collections[table].fields[fieldName];
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
field = schema.collections[fieldNode.relation.collection].fields[fieldNode.relation.field];
|
|
23
|
+
}
|
|
24
|
+
let column;
|
|
25
|
+
if (field?.type?.startsWith('geometry')) {
|
|
26
|
+
column = helpers.st.asText(table, field.field, rawColumnAlias);
|
|
27
|
+
}
|
|
28
|
+
else if (fieldNode.type === 'functionField') {
|
|
29
|
+
// Include the field cases in the functionField query filter
|
|
30
|
+
column = getColumn(knex, table, fieldNode.name, rawColumnAlias, schema, {
|
|
31
|
+
query: {
|
|
32
|
+
...fieldNode.query,
|
|
33
|
+
filter: joinFilterWithCases(fieldNode.query.filter, fieldNode.cases),
|
|
34
|
+
},
|
|
35
|
+
cases: fieldNode.cases,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
column = getColumn(knex, table, fieldNode.name, rawColumnAlias, schema);
|
|
40
|
+
}
|
|
41
|
+
if (hasWhenCase) {
|
|
42
|
+
const columnCases = [];
|
|
43
|
+
for (const index of fieldNode.whenCase) {
|
|
44
|
+
columnCases.push(cases[index]);
|
|
45
|
+
}
|
|
46
|
+
column = applyCaseWhen({
|
|
47
|
+
column,
|
|
48
|
+
columnCases,
|
|
49
|
+
aliasMap,
|
|
50
|
+
cases,
|
|
51
|
+
table,
|
|
52
|
+
alias,
|
|
53
|
+
}, { knex, schema });
|
|
54
|
+
}
|
|
55
|
+
return column;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Filter, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
import type { FieldNode, FunctionFieldNode, M2ONode, O2MNode } from '../../../types/index.js';
|
|
4
|
+
import type { AliasMap } from '../../../utils/get-column-path.js';
|
|
5
|
+
export declare function getInnerQueryColumnPreProcessor(knex: Knex, schema: SchemaOverview, table: string, cases: Filter[], aliasMap: AliasMap, aliasPrefix: string): (fieldNode: FieldNode | FunctionFieldNode | M2ONode | O2MNode) => Knex.Raw<string> | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { applyCaseWhen } from './apply-case-when.js';
|
|
2
|
+
import { getNodeAlias } from './get-field-alias.js';
|
|
3
|
+
export function getInnerQueryColumnPreProcessor(knex, schema, table, cases, aliasMap, aliasPrefix) {
|
|
4
|
+
return function (fieldNode) {
|
|
5
|
+
const alias = getNodeAlias(fieldNode);
|
|
6
|
+
if (fieldNode.whenCase && fieldNode.whenCase.length > 0) {
|
|
7
|
+
const columnCases = [];
|
|
8
|
+
for (const index of fieldNode.whenCase) {
|
|
9
|
+
columnCases.push(cases[index]);
|
|
10
|
+
}
|
|
11
|
+
// Don't pass in the alias as we need to wrap the whole case/when in a count() an alias that
|
|
12
|
+
const caseWhen = applyCaseWhen({
|
|
13
|
+
column: knex.raw(1),
|
|
14
|
+
columnCases,
|
|
15
|
+
aliasMap,
|
|
16
|
+
cases,
|
|
17
|
+
table,
|
|
18
|
+
}, { knex, schema });
|
|
19
|
+
return knex.raw('COUNT(??) AS ??', [caseWhen, `${aliasPrefix}_${alias}`]);
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Item, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { NestedCollectionNode } from '../../../types/ast.js';
|
|
3
|
+
export declare function mergeWithParentItems(schema: SchemaOverview, nestedItem: Item | Item[], parentItem: Item | Item[], nestedNode: NestedCollectionNode, fieldAllowed: boolean | boolean[]): Item | Item[] | undefined;
|