@sap/cds 7.9.2 → 8.0.3
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/CHANGELOG.md +139 -3656
- package/_i18n/i18n_en_US_saptrc.properties +113 -0
- package/_i18n/i18n_zh_CN.properties +7 -4
- package/app/index.css +129 -0
- package/app/index.html +16 -64
- package/app/index.js +14 -9
- package/bin/args.js +34 -0
- package/bin/serve.js +18 -24
- package/bin/test.js +97 -0
- package/common.cds +5 -12
- package/eslint.config.mjs +133 -0
- package/lib/auth/basic-auth.js +16 -20
- package/lib/auth/dummy-auth.js +1 -1
- package/lib/auth/ias-auth.js +12 -30
- package/lib/auth/index.js +1 -14
- package/lib/auth/jwt-auth.js +14 -30
- package/lib/compile/cds-compile.js +1 -2
- package/lib/compile/cdsc.js +21 -26
- package/lib/compile/etc/_localized.js +1 -6
- package/lib/compile/etc/csv.js +1 -1
- package/lib/compile/etc/properties.js +1 -1
- package/lib/compile/for/java.js +1 -1
- package/lib/compile/for/lean_drafts.js +4 -6
- package/lib/compile/for/nodejs.js +1 -1
- package/lib/compile/parse.js +4 -0
- package/lib/compile/resolve.js +4 -4
- package/lib/compile/to/edm-files.js +16 -23
- package/lib/compile/to/hana.js +27 -0
- package/lib/compile/to/json.js +1 -1
- package/lib/compile/to/sql.js +5 -1
- package/lib/compile/to/srvinfo.js +1 -1
- package/lib/compile/to/yaml.js +3 -3
- package/lib/dbs/cds-deploy.js +4 -2
- package/lib/env/cds-env.js +10 -14
- package/lib/env/cds-requires.js +29 -13
- package/lib/env/defaults.js +46 -16
- package/lib/env/plugins.js +1 -1
- package/lib/env/schemas/cds-rc.js +8 -4
- package/lib/env/schemas/index.js +7 -7
- package/lib/env/serviceBindings.js +1 -1
- package/lib/index.js +12 -10
- package/lib/lazy.js +1 -1
- package/lib/linked/classes.js +36 -8
- package/lib/linked/entities.js +2 -10
- package/lib/linked/models.js +2 -1
- package/lib/linked/validate.js +292 -0
- package/lib/log/cds-error.js +0 -6
- package/lib/log/cds-log.js +3 -3
- package/lib/log/format/json.js +1 -1
- package/lib/log/service/index.js +0 -1
- package/lib/plugins.js +3 -3
- package/lib/ql/Query.js +2 -10
- package/lib/ql/SELECT.js +1 -1
- package/lib/ql/Whereable.js +3 -2
- package/lib/req/cds-context.js +14 -25
- package/lib/req/context.js +23 -25
- package/lib/req/request.js +1 -34
- package/lib/req/user.js +47 -35
- package/lib/srv/bindings.js +1 -1
- package/lib/srv/cds-connect.js +4 -4
- package/lib/srv/cds-serve.js +2 -2
- package/lib/srv/factory.js +1 -1
- package/lib/srv/middlewares/cds-context.js +11 -22
- package/lib/srv/middlewares/ctx-model.js +2 -3
- package/lib/srv/middlewares/errors.js +41 -8
- package/lib/srv/middlewares/index.js +3 -3
- package/lib/srv/middlewares/trace.js +0 -2
- package/lib/srv/protocols/hcql.js +15 -10
- package/lib/srv/protocols/http.js +44 -49
- package/lib/srv/protocols/index.js +1 -23
- package/lib/srv/protocols/odata-v4.js +12 -74
- package/lib/srv/protocols/rest.js +1 -13
- package/lib/srv/srv-api.js +0 -20
- package/lib/srv/srv-dispatch.js +3 -2
- package/lib/srv/srv-handlers.js +22 -11
- package/lib/srv/srv-methods.js +2 -2
- package/lib/srv/srv-models.js +3 -36
- package/lib/test/expect.js +343 -0
- package/lib/test/index.js +2 -0
- package/lib/test/reporter.js +176 -0
- package/lib/utils/axios.js +10 -9
- package/lib/utils/cds-test.js +86 -37
- package/lib/utils/cds-utils.js +54 -7
- package/lib/utils/check-version.js +0 -4
- package/lib/utils/colors.js +49 -0
- package/lib/utils/data.js +5 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +3 -30
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +6 -12
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +4 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +12 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +2 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +5 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +9 -43
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +8 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +4 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -3
- package/libx/_runtime/cds-services/util/assert.js +1 -1
- package/libx/_runtime/cds.js +10 -3
- package/libx/_runtime/common/Service.js +12 -32
- package/libx/_runtime/common/aspects/any.js +1 -0
- package/libx/_runtime/common/code-ext/execute.js +1 -1
- package/libx/_runtime/common/code-ext/worker.js +0 -1
- package/libx/_runtime/common/composition/data.js +0 -1
- package/libx/_runtime/common/composition/delete.js +0 -1
- package/libx/_runtime/common/composition/insert.js +2 -2
- package/libx/_runtime/common/composition/tree.js +0 -1
- package/libx/_runtime/common/composition/update.js +3 -3
- package/libx/_runtime/common/error/frontend.js +21 -12
- package/libx/_runtime/common/error/log.js +36 -0
- package/libx/_runtime/common/error/utils.js +2 -5
- package/libx/_runtime/common/generic/auth/autoexpose.js +18 -17
- package/libx/_runtime/common/generic/auth/expand.js +1 -1
- package/libx/_runtime/common/generic/auth/readOnly.js +1 -2
- package/libx/_runtime/common/generic/auth/restrict.js +23 -42
- package/libx/_runtime/common/generic/auth/restrictions.js +2 -7
- package/libx/_runtime/common/generic/auth/utils.js +91 -88
- package/libx/_runtime/common/generic/crud.js +6 -5
- package/libx/_runtime/common/generic/etag.js +7 -12
- package/libx/_runtime/common/generic/input.js +70 -68
- package/libx/_runtime/common/generic/paging.js +1 -0
- package/libx/_runtime/common/generic/sorting.js +1 -0
- package/libx/_runtime/common/generic/temporal.js +8 -2
- package/libx/_runtime/common/i18n/index.js +1 -1
- package/libx/_runtime/common/i18n/messages.properties +3 -1
- package/libx/_runtime/common/utils/binary.js +8 -2
- package/libx/_runtime/common/utils/compareJson.js +5 -1
- package/libx/_runtime/common/utils/copy.js +6 -11
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +16 -14
- package/libx/_runtime/common/utils/differ.js +3 -6
- package/libx/_runtime/common/utils/keys.js +77 -18
- package/libx/_runtime/common/utils/postProcess.js +12 -15
- package/libx/_runtime/common/utils/propagateForeignKeys.js +0 -1
- package/libx/_runtime/common/utils/resolveView.js +2 -3
- package/libx/_runtime/common/utils/restrictions.js +45 -17
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -8
- package/libx/_runtime/common/utils/stream.js +3 -16
- package/libx/_runtime/common/utils/streamProp.js +8 -18
- package/libx/_runtime/common/utils/structured.js +1 -1
- package/libx/_runtime/common/utils/ucsn.js +0 -2
- package/libx/_runtime/db/Service.js +0 -72
- package/libx/_runtime/db/data-conversion/post-processing.js +0 -1
- package/libx/_runtime/db/expand/expandCQNToJoin.js +9 -9
- package/libx/_runtime/db/expand/rawToExpanded.js +0 -8
- package/libx/_runtime/db/generic/input.js +3 -8
- package/libx/_runtime/db/generic/rewrite.js +27 -4
- package/libx/_runtime/db/query/read.js +2 -2
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
- package/libx/_runtime/db/utils/columns.js +2 -6
- package/libx/_runtime/fiori/lean-draft.js +138 -56
- package/libx/_runtime/hana/Service.js +0 -1
- package/libx/_runtime/hana/driver.js +1 -1
- package/libx/_runtime/hana/dynatrace.js +1 -2
- package/libx/_runtime/hana/pool.js +11 -21
- package/libx/_runtime/hana/streaming.js +0 -1
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +0 -1
- package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
- package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -33
- package/libx/_runtime/messaging/event-broker.js +0 -12
- package/libx/_runtime/messaging/file-based.js +3 -3
- package/libx/_runtime/messaging/http-utils/token.js +1 -1
- package/libx/_runtime/messaging/kafka.js +2 -2
- package/libx/_runtime/messaging/redis-messaging.js +0 -1
- package/libx/_runtime/remote/Service.js +25 -25
- package/libx/_runtime/remote/utils/client.js +4 -5
- package/libx/_runtime/remote/utils/cloudSdkProvider.js +0 -3
- package/libx/_runtime/remote/utils/data.js +0 -1
- package/libx/_runtime/sqlite/Service.js +1 -2
- package/libx/_runtime/ucl/Service.js +37 -78
- package/libx/common/assert/index.js +22 -21
- package/libx/common/assert/type-relaxed.js +39 -0
- package/libx/common/assert/utils.js +3 -2
- package/libx/common/assert/validation.js +3 -8
- package/libx/common/utils/index.js +5 -0
- package/libx/common/utils/path.js +51 -0
- package/libx/odata/ODataAdapter.js +126 -0
- package/libx/odata/index.js +15 -2
- package/libx/odata/middleware/batch.js +261 -72
- package/libx/odata/middleware/body-parser.js +33 -0
- package/libx/odata/middleware/create.js +44 -59
- package/libx/odata/middleware/delete.js +23 -12
- package/libx/odata/middleware/error.js +30 -6
- package/libx/odata/middleware/metadata.js +38 -26
- package/libx/odata/middleware/operation.js +93 -69
- package/libx/odata/middleware/parse.js +6 -8
- package/libx/odata/middleware/read.js +117 -93
- package/libx/odata/middleware/service-document.js +22 -19
- package/libx/odata/middleware/stream.js +54 -56
- package/libx/odata/middleware/update.js +79 -87
- package/libx/odata/parse/afterburner.js +191 -175
- package/libx/odata/parse/cqn2odata.js +8 -8
- package/libx/odata/parse/grammar.peggy +27 -20
- package/libx/odata/parse/multipartToJson.js +17 -9
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/etag.js +14 -6
- package/libx/odata/utils/index.js +84 -12
- package/libx/odata/utils/metadata.js +161 -0
- package/libx/odata/utils/postProcess.js +89 -0
- package/libx/odata/utils/readAfterWrite.js +134 -17
- package/libx/odata/utils/result.js +36 -142
- package/libx/outbox/index.js +5 -4
- package/libx/rest/RestAdapter.js +115 -182
- package/libx/rest/middleware/create.js +28 -24
- package/libx/rest/middleware/delete.js +7 -10
- package/libx/rest/middleware/error.js +19 -16
- package/libx/rest/middleware/operation.js +48 -41
- package/libx/rest/middleware/parse.js +128 -126
- package/libx/rest/middleware/read.js +20 -27
- package/libx/rest/middleware/update.js +26 -31
- package/package.json +16 -12
- package/server.js +4 -2
- package/tasks/enterprise-messaging-deploy.js +1 -1
- package/apis/cds.d.ts +0 -3
- package/apis/core.d.ts +0 -21
- package/apis/cqn.d.ts +0 -18
- package/apis/csn.d.ts +0 -21
- package/apis/events.d.ts +0 -18
- package/apis/internal/inference.d.ts +0 -18
- package/apis/linked.d.ts +0 -18
- package/apis/log.d.ts +0 -20
- package/apis/models.d.ts +0 -18
- package/apis/ql.d.ts +0 -18
- package/apis/reflect.d.ts +0 -32
- package/apis/server.d.ts +0 -18
- package/apis/services.d.ts +0 -22
- package/bin/cds-serve.js +0 -56
- package/lib/compile/to/gql.js +0 -15
- package/lib/srv/protocols/_legacy.js +0 -44
- package/lib/utils/jest.js +0 -43
- package/libx/_runtime/auth/index.js +0 -193
- package/libx/_runtime/auth/strategies/JWT.js +0 -37
- package/libx/_runtime/auth/strategies/basic.js +0 -20
- package/libx/_runtime/auth/strategies/dummy.js +0 -14
- package/libx/_runtime/auth/strategies/ias-auth.js +0 -1
- package/libx/_runtime/auth/strategies/mock.js +0 -77
- package/libx/_runtime/auth/strategies/xssecUtils.js +0 -93
- package/libx/_runtime/auth/strategies/xsuaa.js +0 -38
- package/libx/_runtime/common/perf/index.js +0 -19
- package/libx/_runtime/common/utils/ensureIEEE754.js +0 -29
- package/libx/_runtime/fiori/draft.js +0 -2
- package/libx/_runtime/fiori/generic/activate.js +0 -190
- package/libx/_runtime/fiori/generic/before.js +0 -201
- package/libx/_runtime/fiori/generic/cancel.js +0 -19
- package/libx/_runtime/fiori/generic/delete.js +0 -21
- package/libx/_runtime/fiori/generic/edit.js +0 -157
- package/libx/_runtime/fiori/generic/index.js +0 -25
- package/libx/_runtime/fiori/generic/new.js +0 -82
- package/libx/_runtime/fiori/generic/patch.js +0 -101
- package/libx/_runtime/fiori/generic/prepare.js +0 -57
- package/libx/_runtime/fiori/generic/read.js +0 -1340
- package/libx/_runtime/fiori/generic/readOverDraft.js +0 -146
- package/libx/_runtime/fiori/utils/csn.js +0 -13
- package/libx/_runtime/fiori/utils/delete.js +0 -114
- package/libx/_runtime/fiori/utils/handler.js +0 -264
- package/libx/_runtime/fiori/utils/lockInfo.js +0 -27
- package/libx/_runtime/fiori/utils/req.js +0 -23
- package/libx/_runtime/fiori/utils/stream.js +0 -36
- package/libx/_runtime/fiori/utils/where.js +0 -254
- package/libx/_runtime/index.js +0 -22
- package/libx/odata/utils/handler.js +0 -120
- package/libx/odata/utils/metaInfo.js +0 -410
- package/libx/odata/utils/path.js +0 -75
- package/libx/rest/RestRequest.js +0 -32
- package/libx/rest/index.js +0 -3
- package/libx/rest/readme.md +0 -1
- /package/libx/common/assert/{type.js → type-strict.js} +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
const cds = require('../../../')
|
|
1
|
+
const cds = require('../../../lib')
|
|
2
|
+
|
|
3
|
+
const { keysOf, addRefToWhereIfNecessary } = require('../utils')
|
|
2
4
|
|
|
3
5
|
const { where2obj, resolveFromSelect, targetFromPath } = require('../../_runtime/common/utils/cqn')
|
|
4
6
|
const { findCsnTargetFor } = require('../../_runtime/common/utils/csn')
|
|
@@ -6,40 +8,10 @@ const normalizeTimestamp = require('../../_runtime/common/utils/normalizeTimesta
|
|
|
6
8
|
const { rewriteExpandAsterisk } = require('../../_runtime/common/utils/rewriteAsterisks')
|
|
7
9
|
const resolveStructured = require('../../_runtime/common/utils/resolveStructured')
|
|
8
10
|
|
|
9
|
-
const _addKeysDeep = (keys, keysCollector, ignoreManagedBacklinks) => {
|
|
10
|
-
for (const keyName in keys) {
|
|
11
|
-
const key = keys[keyName]
|
|
12
|
-
const foreignKey = key._foreignKey4
|
|
13
|
-
if (key.isAssociation || foreignKey === 'up_' || key['@cds.api.ignore'] === true) continue
|
|
14
|
-
|
|
15
|
-
if (ignoreManagedBacklinks && foreignKey) {
|
|
16
|
-
const navigationElement = keys[foreignKey]
|
|
17
|
-
if (!navigationElement.on && navigationElement._isBacklink) {
|
|
18
|
-
// skip navigation elements that are backlinks
|
|
19
|
-
continue
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if ('elements' in key) {
|
|
24
|
-
_addKeysDeep(key.elements, keysCollector)
|
|
25
|
-
continue
|
|
26
|
-
}
|
|
27
|
-
keysCollector.push(keyName)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function _keysOf(entity, ignoreManagedBacklinks) {
|
|
32
|
-
const keysCollector = []
|
|
33
|
-
if (!entity || !entity.keys) return keysCollector
|
|
34
|
-
|
|
35
|
-
_addKeysDeep(entity.keys, keysCollector, ignoreManagedBacklinks)
|
|
36
|
-
return keysCollector
|
|
37
|
-
}
|
|
38
|
-
|
|
39
11
|
function _getDefinition(definition, name, namespace) {
|
|
40
12
|
return (
|
|
41
|
-
|
|
42
|
-
|
|
13
|
+
definition?.definitions?.[name] ||
|
|
14
|
+
definition?.elements?.[name] ||
|
|
43
15
|
(definition.actions && (definition.actions[name] || definition.actions[name.replace(namespace + '.', '')])) ||
|
|
44
16
|
definition[name]
|
|
45
17
|
)
|
|
@@ -48,6 +20,7 @@ function _getDefinition(definition, name, namespace) {
|
|
|
48
20
|
function _resolveAliasesInRef(ref, target) {
|
|
49
21
|
if (ref.length === 1) {
|
|
50
22
|
if (target.keys[ref[0]]) return ref
|
|
23
|
+
|
|
51
24
|
// resolve multi-part refs for innermost ref in url
|
|
52
25
|
if (target._flattenedKeys === undefined) {
|
|
53
26
|
const flattenedKeys = []
|
|
@@ -55,11 +28,14 @@ function _resolveAliasesInRef(ref, target) {
|
|
|
55
28
|
if (!target.keys[key].elements) continue
|
|
56
29
|
flattenedKeys.push(...resolveStructured({ element: target.keys[key], structProperties: [] }, false, true))
|
|
57
30
|
}
|
|
31
|
+
|
|
58
32
|
target._flattenedKeys = flattenedKeys.length ? flattenedKeys : null
|
|
59
33
|
}
|
|
34
|
+
|
|
60
35
|
const fk = target._flattenedKeys?.find(fk => fk.key === ref[0])
|
|
61
36
|
if (fk) return [...fk.resolved]
|
|
62
37
|
}
|
|
38
|
+
|
|
63
39
|
for (const seg of ref) {
|
|
64
40
|
target = target.elements[seg.id || seg]
|
|
65
41
|
if (!target) return ref
|
|
@@ -68,11 +44,13 @@ function _resolveAliasesInRef(ref, target) {
|
|
|
68
44
|
if (seg.where) _resolveAliasesInXpr(seg.where, target)
|
|
69
45
|
}
|
|
70
46
|
}
|
|
47
|
+
|
|
71
48
|
return ref
|
|
72
49
|
}
|
|
73
50
|
|
|
74
51
|
function _resolveAliasesInXpr(xpr, target) {
|
|
75
52
|
if (!target || !xpr) return
|
|
53
|
+
|
|
76
54
|
for (const el of xpr) {
|
|
77
55
|
if (el.xpr) _resolveAliasesInXpr(el.xpr, target)
|
|
78
56
|
if (el.args) _resolveAliasesInXpr(el.args, target)
|
|
@@ -82,6 +60,7 @@ function _resolveAliasesInXpr(xpr, target) {
|
|
|
82
60
|
|
|
83
61
|
function _resolveAliasesInNavigation(cqn, target) {
|
|
84
62
|
if (!target || !cqn) return
|
|
63
|
+
|
|
85
64
|
if (cqn.SELECT.from.SELECT) _resolveAliasesInNavigation(cqn.SELECT.from, target)
|
|
86
65
|
if (cqn.SELECT.where) _resolveAliasesInXpr(cqn.SELECT.where, target)
|
|
87
66
|
if (cqn.SELECT.having) _resolveAliasesInXpr(cqn.SELECT.having, target)
|
|
@@ -90,35 +69,25 @@ function _resolveAliasesInNavigation(cqn, target) {
|
|
|
90
69
|
function _addDefaultParams(ref, view) {
|
|
91
70
|
const params = view.params
|
|
92
71
|
const defaults = params && Object.values(params).filter(p => p.default)
|
|
72
|
+
|
|
93
73
|
if (defaults && defaults.length > 0) {
|
|
94
74
|
if (!ref.where) ref.where = []
|
|
95
75
|
for (const def of defaults) {
|
|
96
|
-
if (ref.where.find(e => e.ref && e.ref[0] === def.name))
|
|
97
|
-
continue
|
|
98
|
-
}
|
|
76
|
+
if (ref.where.find(e => e.ref && e.ref[0] === def.name)) continue
|
|
99
77
|
if (ref.where.length > 0) ref.where.push('and')
|
|
100
78
|
ref.where.push({ ref: [def.name] }, '=', { val: def.default.val })
|
|
101
79
|
}
|
|
102
80
|
}
|
|
103
81
|
}
|
|
104
82
|
|
|
105
|
-
// case: single key without name, e.g., Foo(1)
|
|
106
|
-
function addRefToWhereIfNecessary(where, entity) {
|
|
107
|
-
if (!where || where.length !== 1) return 0
|
|
108
|
-
|
|
109
|
-
const isView = !!entity.params
|
|
110
|
-
|
|
111
|
-
const keys = isView ? Object.keys(entity.params) : _keysOf(entity)
|
|
112
|
-
if (keys.length !== 1) return 0
|
|
113
|
-
where.unshift(...[{ ref: [keys[0]] }, '='])
|
|
114
|
-
return 1
|
|
115
|
-
}
|
|
116
|
-
|
|
117
83
|
function getResolvedElement(entity, { ref }) {
|
|
118
84
|
const element = entity.elements[ref[0]]
|
|
85
|
+
|
|
119
86
|
if (element && element.isAssociation && ref.length > 1) {
|
|
120
87
|
return getResolvedElement(element._target, { ref: ref.slice(1) })
|
|
121
|
-
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (element && element._isStructured) {
|
|
122
91
|
return getResolvedElement(element, { ref: ref.slice(1) })
|
|
123
92
|
}
|
|
124
93
|
|
|
@@ -133,18 +102,15 @@ function _processWhere(where, entity) {
|
|
|
133
102
|
const operator = where[i + 1]
|
|
134
103
|
const val = where[i + 2]
|
|
135
104
|
|
|
136
|
-
if (ref in forbidden || val in forbidden || ref.func)
|
|
137
|
-
|
|
138
|
-
}
|
|
105
|
+
if (ref in forbidden || val in forbidden || ref.func) continue
|
|
106
|
+
|
|
139
107
|
if (ref.xpr) {
|
|
140
108
|
_processWhere(ref.xpr, entity)
|
|
141
109
|
continue
|
|
142
110
|
}
|
|
143
111
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
continue
|
|
147
|
-
}
|
|
112
|
+
// xpr check needs to be done first, else it could happen, that we ignore xpr OR xpr
|
|
113
|
+
if (operator in forbidden) continue
|
|
148
114
|
|
|
149
115
|
let valIndex = -1
|
|
150
116
|
let refIndex = -1
|
|
@@ -152,6 +118,7 @@ function _processWhere(where, entity) {
|
|
|
152
118
|
if (val.val !== undefined) valIndex = i + 2
|
|
153
119
|
if (val.ref != undefined) refIndex = i + 2
|
|
154
120
|
}
|
|
121
|
+
|
|
155
122
|
if (typeof ref === 'object') {
|
|
156
123
|
if (ref.val !== undefined) valIndex = i
|
|
157
124
|
if (ref.ref != undefined) refIndex = i
|
|
@@ -172,6 +139,7 @@ function _processWhere(where, entity) {
|
|
|
172
139
|
|
|
173
140
|
function _convertVal(value, element) {
|
|
174
141
|
if (value === null) return value
|
|
142
|
+
|
|
175
143
|
switch (element._type) {
|
|
176
144
|
// numbers
|
|
177
145
|
case 'cds.UInt8':
|
|
@@ -182,33 +150,42 @@ function _convertVal(value, element) {
|
|
|
182
150
|
const msg = `Element "${element.name}" does not contain a valid Integer`
|
|
183
151
|
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
184
152
|
}
|
|
153
|
+
|
|
185
154
|
// eslint-disable-next-line no-case-declarations
|
|
186
155
|
const n = Number(value)
|
|
187
156
|
if (!Number.isSafeInteger(n)) {
|
|
188
157
|
const msg = `Element "${element.name}" does not contain a valid Integer`
|
|
189
158
|
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
190
159
|
}
|
|
160
|
+
|
|
191
161
|
if (element._type === 'cds.UInt8' && n < 0) {
|
|
192
162
|
const msg = `Element "${element.name}" does not contain a valid positive Integer`
|
|
193
163
|
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
194
164
|
}
|
|
165
|
+
|
|
195
166
|
return n
|
|
167
|
+
|
|
196
168
|
case 'cds.Double':
|
|
197
169
|
return parseFloat(value)
|
|
170
|
+
|
|
198
171
|
case 'cds.Decimal':
|
|
199
172
|
case 'cds.DecimalFloat':
|
|
200
173
|
case 'cds.Int64':
|
|
201
174
|
case 'cds.Integer64':
|
|
202
175
|
if (typeof value === 'string') return value
|
|
203
176
|
return String(value)
|
|
177
|
+
|
|
204
178
|
// others
|
|
205
179
|
case 'cds.String':
|
|
206
180
|
case 'cds.LargeString':
|
|
207
181
|
return String(value)
|
|
182
|
+
|
|
208
183
|
case 'cds.Boolean':
|
|
209
184
|
return typeof value === 'string' ? value === 'true' : value
|
|
185
|
+
|
|
210
186
|
case 'cds.Timestamp':
|
|
211
187
|
return normalizeTimestamp(value)
|
|
188
|
+
|
|
212
189
|
default:
|
|
213
190
|
return value
|
|
214
191
|
}
|
|
@@ -220,10 +197,12 @@ const getStructRef = (element, ref = []) => {
|
|
|
220
197
|
getStructRef(element.parent, ref)
|
|
221
198
|
ref.push(element.name)
|
|
222
199
|
}
|
|
200
|
+
|
|
223
201
|
if (element.parent.kind === 'entity') {
|
|
224
202
|
ref.push(element.name)
|
|
225
203
|
}
|
|
226
204
|
}
|
|
205
|
+
|
|
227
206
|
return ref
|
|
228
207
|
}
|
|
229
208
|
|
|
@@ -232,46 +211,71 @@ const getStructTargetName = element => {
|
|
|
232
211
|
if (element.parent.kind === 'element') {
|
|
233
212
|
return getStructTargetName(element.parent)
|
|
234
213
|
}
|
|
214
|
+
|
|
235
215
|
if (element.elements && element.parent.kind === 'entity') {
|
|
236
216
|
return element.parent.name
|
|
237
217
|
}
|
|
238
218
|
}
|
|
239
219
|
}
|
|
240
220
|
|
|
241
|
-
|
|
242
|
-
|
|
221
|
+
const _getDataFromParams = (params, operation) => {
|
|
222
|
+
try {
|
|
223
|
+
return Object.keys(params).reduce((acc, cur) => {
|
|
224
|
+
acc[cur] =
|
|
225
|
+
typeof params[cur] === 'string' && (operation.params[cur]?.elements || operation.params[cur]?.items)
|
|
226
|
+
? JSON.parse(params[cur])
|
|
227
|
+
: params[cur]
|
|
228
|
+
return acc
|
|
229
|
+
}, {})
|
|
230
|
+
} catch (e) {
|
|
231
|
+
throw Object.assign(e, { statusCode: 400, internal: e.message, message: 'Malformed parameters' })
|
|
232
|
+
}
|
|
233
|
+
}
|
|
243
234
|
|
|
244
|
-
|
|
245
|
-
let
|
|
246
|
-
let keys = null
|
|
247
|
-
let keyCount = 0
|
|
248
|
-
let incompleteKeys = false
|
|
249
|
-
let one
|
|
250
|
-
let target
|
|
251
|
-
|
|
252
|
-
function _handleCollectionBoundActions(i) {
|
|
253
|
-
let action
|
|
254
|
-
if (current.actions) {
|
|
255
|
-
const nextRef = typeof ref[i + 1] === 'string' && ref[i + 1]
|
|
256
|
-
const shortName = nextRef && nextRef.replace(namespace + '.', '')
|
|
257
|
-
action = shortName && current.actions[shortName]
|
|
258
|
-
}
|
|
235
|
+
function _handleCollectionBoundActions(current, ref, i, namespace, one) {
|
|
236
|
+
let action
|
|
259
237
|
|
|
260
|
-
|
|
238
|
+
if (current.actions) {
|
|
239
|
+
const nextRef = (typeof ref[i + 1] === 'string' && ref[i + 1]) || ref[i + 1]?.id
|
|
240
|
+
const shortName = nextRef && nextRef.replace(namespace + '.', '')
|
|
241
|
+
action = shortName && current.actions[shortName]
|
|
242
|
+
}
|
|
261
243
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
244
|
+
let incompleteKeys = ref[i].where ? false : i === ref.length - 1 || one ? false : true
|
|
245
|
+
if (!action) return incompleteKeys
|
|
246
|
+
|
|
247
|
+
const onCollection = !!(
|
|
248
|
+
action['@cds.odata.bindingparameter.collection'] || action?.params?.some(p => p?.items?.type === '$self')
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if (onCollection && one) {
|
|
252
|
+
const msg = `${action.kind.at(0).toUpperCase() + action.kind.slice(1)} "${action.name}" must be called on a collection of ${current.name}`
|
|
253
|
+
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (incompleteKeys) {
|
|
257
|
+
if (!onCollection) {
|
|
258
|
+
const msg = `${action.kind.at(0).toUpperCase() + action.kind.slice(1)} "${action.name}" must be called on a single instance of ${current.name}`
|
|
259
|
+
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
272
260
|
}
|
|
261
|
+
|
|
262
|
+
incompleteKeys = false
|
|
273
263
|
}
|
|
274
264
|
|
|
265
|
+
return incompleteKeys
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function _processSegments(from, model, namespace, cqn, protocol) {
|
|
269
|
+
const { ref } = from
|
|
270
|
+
|
|
271
|
+
let current = model,
|
|
272
|
+
path,
|
|
273
|
+
keys = null,
|
|
274
|
+
keyCount = 0,
|
|
275
|
+
incompleteKeys = false,
|
|
276
|
+
one,
|
|
277
|
+
target
|
|
278
|
+
|
|
275
279
|
for (let i = 0; i < ref.length; i++) {
|
|
276
280
|
const seg = ref[i].id || ref[i]
|
|
277
281
|
const whereRef = ref[i].where
|
|
@@ -279,7 +283,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
279
283
|
|
|
280
284
|
if (incompleteKeys) {
|
|
281
285
|
// > key
|
|
282
|
-
keys = keys ||
|
|
286
|
+
keys = keys || keysOf(current, protocol !== 'rest') // if odata, skip backlinks as key as they are used from structure
|
|
283
287
|
let key = keys[keyCount++]
|
|
284
288
|
one = true
|
|
285
289
|
const element = current.elements[key]
|
|
@@ -297,6 +301,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
297
301
|
const val = _convertVal(seg, element)
|
|
298
302
|
base.where.push({ ref: [key] }, '=', { val })
|
|
299
303
|
}
|
|
304
|
+
|
|
300
305
|
ref[i] = null
|
|
301
306
|
ref[i - keyCount] = base
|
|
302
307
|
incompleteKeys = keyCount < keys.length
|
|
@@ -307,14 +312,17 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
307
312
|
one = false
|
|
308
313
|
|
|
309
314
|
path = path ? path + `${path.match(/:/) ? '.' : ':'}${seg}` : seg
|
|
315
|
+
|
|
310
316
|
// REVISIT: replace use case: <namespace>.<entity>_history is at <namespace>.<entity>.history
|
|
311
317
|
current = _getDefinition(current, seg, namespace) || _getDefinition(current, seg.replace(/_/g, '.'), namespace)
|
|
318
|
+
|
|
312
319
|
// REVISIT: 404 or 400?
|
|
313
320
|
if (!current) cds.error(`Invalid resource path "${path}"`, { code: '404', statusCode: 404 })
|
|
314
321
|
|
|
315
322
|
if (current.params && current.kind === 'entity') {
|
|
316
323
|
// > View with params
|
|
317
324
|
target = current
|
|
325
|
+
|
|
318
326
|
if (whereRef) {
|
|
319
327
|
keyCount += addRefToWhereIfNecessary(ref[i].where, current)
|
|
320
328
|
_resolveAliasesInXpr(ref[i].where, current)
|
|
@@ -323,9 +331,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
323
331
|
|
|
324
332
|
_addDefaultParams(ref[i], current)
|
|
325
333
|
if ((!params || !Object.keys(params).length) && ref[i].where) params = where2obj(ref[i].where)
|
|
326
|
-
|
|
327
334
|
_checkAllKeysProvided(params, current)
|
|
328
|
-
|
|
329
335
|
ref[i].args = {}
|
|
330
336
|
|
|
331
337
|
const where = ref[i].where
|
|
@@ -335,23 +341,27 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
335
341
|
ref[i].args[whereElement.ref[0]] = where[j + 2]
|
|
336
342
|
j += 2
|
|
337
343
|
}
|
|
344
|
+
|
|
338
345
|
ref[i].where = undefined
|
|
346
|
+
|
|
339
347
|
if (ref[i + 1] !== 'Set') {
|
|
340
348
|
// /Set is missing
|
|
341
349
|
const msg = `Invalid call to "${current.name}". You need to navigate to Set`
|
|
342
350
|
throw cds.error(msg, { code: '400', statusCode: 400 })
|
|
343
351
|
}
|
|
352
|
+
|
|
344
353
|
ref[++i] = null
|
|
345
354
|
} else if (current.kind === 'entity') {
|
|
346
355
|
// > entity
|
|
347
356
|
target = current
|
|
348
357
|
one = !!(ref[i].where || current._isSingleton)
|
|
349
358
|
|
|
350
|
-
_handleCollectionBoundActions(i)
|
|
359
|
+
incompleteKeys = _handleCollectionBoundActions(current, ref, i, namespace, one)
|
|
351
360
|
|
|
352
361
|
if (whereRef) {
|
|
353
362
|
keyCount += addRefToWhereIfNecessary(whereRef, current)
|
|
354
363
|
_resolveAliasesInXpr(whereRef, current)
|
|
364
|
+
|
|
355
365
|
// in case of Foo(1), params will be {} (before addRefToWhereIfNecessary was called)
|
|
356
366
|
if (!Object.keys(params).length) params = where2obj(ref[i].where)
|
|
357
367
|
_processWhere(ref[i].where, current)
|
|
@@ -359,17 +369,18 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
359
369
|
}
|
|
360
370
|
} else if ({ action: 1, function: 1 }[current.kind]) {
|
|
361
371
|
// > action or function
|
|
362
|
-
if (current.kind === 'action' && ref && ref
|
|
372
|
+
if (current.kind === 'action' && ref && ref.at(-1)?.where?.length === 0) {
|
|
363
373
|
const msg = `Parentheses are not allowed for action calls.`
|
|
364
374
|
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
365
375
|
}
|
|
366
376
|
|
|
367
377
|
if (i !== ref.length - 1) {
|
|
368
|
-
const msg = `${i ? '
|
|
369
|
-
throw Object.assign(new Error(msg), { statusCode:
|
|
378
|
+
const msg = `${i ? 'Bound' : 'Unbound'} ${current.kind}s are only supported as the last path segment`
|
|
379
|
+
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
370
380
|
}
|
|
381
|
+
|
|
371
382
|
ref[i] = { operation: current.name }
|
|
372
|
-
if (params) ref[i].args = params
|
|
383
|
+
if (params) ref[i].args = _getDataFromParams(params, current)
|
|
373
384
|
if (current.returns && current.returns._type) one = true
|
|
374
385
|
} else if (current.isAssociation) {
|
|
375
386
|
if (!current._target._service) {
|
|
@@ -385,7 +396,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
385
396
|
current = model.definitions[current.target]
|
|
386
397
|
target = current
|
|
387
398
|
|
|
388
|
-
_handleCollectionBoundActions(i)
|
|
399
|
+
incompleteKeys = _handleCollectionBoundActions(current, ref, i, namespace, one)
|
|
389
400
|
|
|
390
401
|
if (ref[i].where) {
|
|
391
402
|
keyCount += addRefToWhereIfNecessary(ref[i].where, current)
|
|
@@ -397,34 +408,40 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
397
408
|
continue
|
|
398
409
|
} else {
|
|
399
410
|
// > property
|
|
411
|
+
|
|
400
412
|
// we do not support navigations from properties yet
|
|
401
413
|
one = true
|
|
414
|
+
|
|
402
415
|
// if the last segment is a property, it must be removed and pushed to columns
|
|
403
416
|
target = target || _getDefinition(model, ref[0].id, namespace)
|
|
417
|
+
|
|
404
418
|
if (getStructTargetName(current) === target.name) {
|
|
405
419
|
// TODO add simple isStructured check before
|
|
406
420
|
if (!cqn.SELECT.columns) cqn.SELECT.columns = []
|
|
407
421
|
const ref = getStructRef(current)
|
|
408
422
|
cqn.SELECT.columns.push({ ref }) // store struct as ref
|
|
423
|
+
|
|
409
424
|
// we need the keys to generate the correct @odata.context
|
|
410
425
|
for (const key in target.keys || {}) {
|
|
411
426
|
if (key !== 'IsActiveEntity' && !cqn.SELECT.columns.some(c => c.ref?.[0] === key))
|
|
412
427
|
cqn.SELECT.columns.push({ ref: [key] })
|
|
413
428
|
}
|
|
429
|
+
|
|
414
430
|
Object.defineProperty(cqn, '_propertyAccess', { value: current.name, enumerable: false })
|
|
431
|
+
|
|
415
432
|
// if we end up with structured, keep path as is, if we end up with property in structured, cut off property
|
|
416
|
-
if (!current.elements)
|
|
417
|
-
from.ref.splice(-1)
|
|
418
|
-
}
|
|
433
|
+
if (!current.elements) from.ref.splice(-1)
|
|
419
434
|
break
|
|
420
435
|
} else if (Object.keys(target.elements).includes(current.name)) {
|
|
421
436
|
if (!cqn.SELECT.columns) cqn.SELECT.columns = []
|
|
422
437
|
cqn.SELECT.columns.push({ ref: ref.slice(i) })
|
|
438
|
+
|
|
423
439
|
// we need the keys to generate the correct @odata.context
|
|
424
440
|
for (const key in target.keys || {}) {
|
|
425
441
|
if (key !== 'IsActiveEntity' && !cqn.SELECT.columns.some(c => c.ref?.[0] === key))
|
|
426
442
|
cqn.SELECT.columns.push({ ref: [key] })
|
|
427
443
|
}
|
|
444
|
+
|
|
428
445
|
// REVISIT: remove hacky _propertyAccess
|
|
429
446
|
Object.defineProperty(cqn, '_propertyAccess', { value: current.name, enumerable: false })
|
|
430
447
|
from.ref.splice(i)
|
|
@@ -436,7 +453,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
436
453
|
|
|
437
454
|
if (incompleteKeys) {
|
|
438
455
|
// > last segment not fully qualified
|
|
439
|
-
const msg = `Entity "${current.name}" has ${
|
|
456
|
+
const msg = `Entity "${current.name}" has ${keysOf(current).length} keys. Only ${keyCount} ${keyCount === 1 ? 'was' : 'were'} provided.`
|
|
440
457
|
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
441
458
|
}
|
|
442
459
|
|
|
@@ -452,6 +469,7 @@ const CSTM_AGGR = '@Aggregation.CustomAggregate'
|
|
|
452
469
|
function _addKeys(columns, target) {
|
|
453
470
|
let hasAggregatedColumn = false,
|
|
454
471
|
hasStarColumn = false
|
|
472
|
+
|
|
455
473
|
for (let k = 0; k < columns.length; k++) {
|
|
456
474
|
if (columns[k] === '*') hasStarColumn = true
|
|
457
475
|
else if (columns[k].func || columns[k].func === null) hasAggregatedColumn = true
|
|
@@ -466,7 +484,7 @@ function _addKeys(columns, target) {
|
|
|
466
484
|
|
|
467
485
|
if (hasStarColumn) return
|
|
468
486
|
|
|
469
|
-
const keys =
|
|
487
|
+
const keys = keysOf(target)
|
|
470
488
|
|
|
471
489
|
for (const key of keys) {
|
|
472
490
|
if (!columns.some(c => (typeof c === 'string' ? c === key : c.ref?.[0] === key))) columns.push({ ref: [key] })
|
|
@@ -476,6 +494,7 @@ function _addKeys(columns, target) {
|
|
|
476
494
|
// remove duplicate * in expand (e.g. expand=*,*)
|
|
477
495
|
function _removeDuplicateAsterisk(columns) {
|
|
478
496
|
let hasExpandStar = false
|
|
497
|
+
|
|
479
498
|
for (let i = columns.length - 1; i > 0; i--) {
|
|
480
499
|
const column = columns[i]
|
|
481
500
|
if (!hasExpandStar && !column.ref && column?.expand?.[0] === '*') hasExpandStar = true
|
|
@@ -489,6 +508,7 @@ const _structProperty = (ref, target) => {
|
|
|
489
508
|
if (target.elements && target.kind === 'element') {
|
|
490
509
|
return _structProperty(ref.slice(1), target.elements[ref[0]])
|
|
491
510
|
}
|
|
511
|
+
|
|
492
512
|
return target
|
|
493
513
|
}
|
|
494
514
|
|
|
@@ -504,8 +524,8 @@ function _processColumns(cqn, target, protocol) {
|
|
|
504
524
|
if (target.kind === 'entity') entity = target
|
|
505
525
|
else if (target.kind === 'action' && target.returns?.kind === 'entity') entity = target.returns
|
|
506
526
|
if (!entity) return
|
|
507
|
-
_removeDuplicateAsterisk(columns)
|
|
508
527
|
|
|
528
|
+
_removeDuplicateAsterisk(columns)
|
|
509
529
|
rewriteExpandAsterisk(columns, entity)
|
|
510
530
|
if (protocol !== 'rest') _addKeys(columns, entity)
|
|
511
531
|
}
|
|
@@ -539,6 +559,7 @@ function _processColumns(cqn, target, protocol) {
|
|
|
539
559
|
const _checkAllKeysProvided = (params, entity) => {
|
|
540
560
|
let keysOfEntity
|
|
541
561
|
const isView = !!entity.params
|
|
562
|
+
|
|
542
563
|
if (isView) {
|
|
543
564
|
// view with params
|
|
544
565
|
if (params === undefined) {
|
|
@@ -549,7 +570,7 @@ const _checkAllKeysProvided = (params, entity) => {
|
|
|
549
570
|
|
|
550
571
|
keysOfEntity = Object.keys(entity.params)
|
|
551
572
|
} else {
|
|
552
|
-
keysOfEntity =
|
|
573
|
+
keysOfEntity = keysOf(entity)
|
|
553
574
|
}
|
|
554
575
|
|
|
555
576
|
if (!keysOfEntity) return
|
|
@@ -569,8 +590,8 @@ const _checkAllKeysProvided = (params, entity) => {
|
|
|
569
590
|
|
|
570
591
|
const _doesNotExistError = (isExpand, refName, targetName) => {
|
|
571
592
|
const msg = isExpand
|
|
572
|
-
? `Navigation property "${refName}" is not defined in ${targetName}`
|
|
573
|
-
: `Property "${refName}" does not exist in ${targetName}`
|
|
593
|
+
? `Navigation property "${refName}" is not defined in "${targetName}"`
|
|
594
|
+
: `Property "${refName}" does not exist in "${targetName}"`
|
|
574
595
|
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
575
596
|
}
|
|
576
597
|
|
|
@@ -599,6 +620,10 @@ function _validateXpr(xpr, ignoredColumns, target, isOne, model, aliases = []) {
|
|
|
599
620
|
_validateXpr(x.ref[0].where, ignoredColumns, element._target ?? element.items, isOne, model)
|
|
600
621
|
}
|
|
601
622
|
|
|
623
|
+
if (!target?.elements) {
|
|
624
|
+
_doesNotExistError(false, refName, target.name)
|
|
625
|
+
}
|
|
626
|
+
|
|
602
627
|
if (ignoredColumns.includes(refName) || (!target.elements[refName] && !aliases.includes(refName))) {
|
|
603
628
|
_doesNotExistError(x.expand, refName, target.name)
|
|
604
629
|
} else if (x.ref.length > 1) {
|
|
@@ -630,6 +655,10 @@ function _validateXpr(xpr, ignoredColumns, target, isOne, model, aliases = []) {
|
|
|
630
655
|
element = _structProperty(x.ref.slice(1), element)
|
|
631
656
|
}
|
|
632
657
|
|
|
658
|
+
if (!element._target) {
|
|
659
|
+
_doesNotExistError(true, refName, target.name)
|
|
660
|
+
}
|
|
661
|
+
|
|
633
662
|
const _ignoredColumns = Object.values(element._target.elements ?? {})
|
|
634
663
|
.filter(element => element['@cds.api.ignore'])
|
|
635
664
|
.map(element => element.name)
|
|
@@ -638,6 +667,7 @@ function _validateXpr(xpr, ignoredColumns, target, isOne, model, aliases = []) {
|
|
|
638
667
|
if (x.where) {
|
|
639
668
|
_validateXpr(x.where, _ignoredColumns, element._target, false, model)
|
|
640
669
|
}
|
|
670
|
+
|
|
641
671
|
if (x.orderBy) {
|
|
642
672
|
_validateXpr(x.orderBy, _ignoredColumns, element._target, false, model)
|
|
643
673
|
}
|
|
@@ -657,6 +687,7 @@ function _validateXpr(xpr, ignoredColumns, target, isOne, model, aliases = []) {
|
|
|
657
687
|
_validateQuery(x.SELECT, _ignoredColumns, target, x.SELECT.one, model)
|
|
658
688
|
}
|
|
659
689
|
}
|
|
690
|
+
|
|
660
691
|
return _aliases
|
|
661
692
|
}
|
|
662
693
|
|
|
@@ -680,89 +711,74 @@ function _validateQuery(SELECT, ignoredColumns, target, isOne, model) {
|
|
|
680
711
|
return aliases
|
|
681
712
|
}
|
|
682
713
|
|
|
683
|
-
|
|
684
|
-
const
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
if (!target._service) {
|
|
719
|
-
// proxy navigation, add keys as columns only
|
|
720
|
-
const columns = []
|
|
721
|
-
for (const key in target.keys) {
|
|
722
|
-
if (target.keys[key].isAssociation) continue
|
|
723
|
-
columns.push({ ref: [key] })
|
|
724
|
-
}
|
|
725
|
-
cqn.SELECT.columns = columns
|
|
714
|
+
module.exports = (cqn, model, namespace, protocol) => {
|
|
715
|
+
const from = resolveFromSelect(cqn)
|
|
716
|
+
const { ref } = from
|
|
717
|
+
|
|
718
|
+
// REVISIT: shouldn't be necessary
|
|
719
|
+
// Second findCsnTargetFor is required for concat query, where the root is already identified with the first query
|
|
720
|
+
// and subsequent queries already have correct root
|
|
721
|
+
/*
|
|
722
|
+
* make first path segment fully qualified
|
|
723
|
+
*/
|
|
724
|
+
const root =
|
|
725
|
+
findCsnTargetFor(ref[0].id || ref[0], model, namespace) ||
|
|
726
|
+
findCsnTargetFor(
|
|
727
|
+
ref[0].id?.split('.')[ref[0].id?.split('.').length - 1] || ref[0].split('.')[ref[0].split('.').length - 1],
|
|
728
|
+
model,
|
|
729
|
+
namespace
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
// REVISIT: 404 or 400?
|
|
733
|
+
if (!root) {
|
|
734
|
+
cds.error(`Invalid resource path "${namespace}.${ref[0].id || ref[0]}"`, { code: '404', statusCode: 404 })
|
|
735
|
+
}
|
|
736
|
+
if (ref[0].id) ref[0].id = root.name
|
|
737
|
+
else ref[0] = root.name
|
|
738
|
+
|
|
739
|
+
// key vs. path segments (/Books/1/author/books/2/...) and more
|
|
740
|
+
const { one, current, target } = _processSegments(from, model, namespace, cqn, protocol)
|
|
741
|
+
|
|
742
|
+
if (cds.env.effective.odata.proxies && cds.env.effective.odata.xrefs && target) {
|
|
743
|
+
if (!target._service) {
|
|
744
|
+
// proxy navigation, add keys as columns only
|
|
745
|
+
const columns = []
|
|
746
|
+
for (const key in target.keys) {
|
|
747
|
+
if (target.keys[key].isAssociation) continue
|
|
748
|
+
columns.push({ ref: [key] })
|
|
726
749
|
}
|
|
727
|
-
}
|
|
728
750
|
|
|
729
|
-
|
|
730
|
-
_processWhere(cqn.SELECT.where, root)
|
|
751
|
+
cqn.SELECT.columns = columns
|
|
731
752
|
}
|
|
753
|
+
}
|
|
732
754
|
|
|
733
|
-
|
|
734
|
-
|
|
755
|
+
if (cqn.SELECT.where) {
|
|
756
|
+
_processWhere(cqn.SELECT.where, root)
|
|
757
|
+
}
|
|
735
758
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
cqn.__target = current.parent?.kind === 'entity' ? `${current.parent.name}:$:${current.name}` : current.name
|
|
759
|
+
// one?
|
|
760
|
+
if (one) cqn.SELECT.one = true
|
|
739
761
|
|
|
740
|
-
|
|
741
|
-
|
|
762
|
+
// REVISIT: better
|
|
763
|
+
// set target (csn definition) for later retrieval
|
|
764
|
+
cqn.__target = current.parent?.kind === 'entity' ? `${current.parent.name}:$:${current.name}` : current.name
|
|
742
765
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
*/
|
|
746
|
-
_processColumns(cqn, current, protocol)
|
|
766
|
+
// target <=> endpoint entity, all navigation refs must be resolvable accordingly
|
|
767
|
+
if (cds.env.effective.odata.structs) _resolveAliasesInNavigation(cqn, target)
|
|
747
768
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
769
|
+
/*
|
|
770
|
+
* add default aggregation function (and alias)
|
|
771
|
+
*/
|
|
772
|
+
_processColumns(cqn, current, protocol)
|
|
752
773
|
|
|
753
|
-
|
|
754
|
-
}
|
|
774
|
+
if (target) {
|
|
775
|
+
const ignoredColumns = Object.values(target.elements ?? {})
|
|
776
|
+
.filter(element => element['@cds.api.ignore'])
|
|
777
|
+
.map(element => element.name)
|
|
755
778
|
|
|
756
|
-
|
|
779
|
+
// validate whether only known properties are used in query options
|
|
780
|
+
_validateQuery(cqn.SELECT, ignoredColumns, target, one, model)
|
|
757
781
|
}
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
const cache = new WeakMap()
|
|
761
782
|
|
|
762
|
-
|
|
763
|
-
for: service => {
|
|
764
|
-
if (!cache.has(service)) cache.set(service, _4service(service))
|
|
765
|
-
return cache.get(service)
|
|
766
|
-
},
|
|
767
|
-
addRefToWhereIfNecessary
|
|
783
|
+
return cqn
|
|
768
784
|
}
|