@sap/cds 6.1.3 → 6.2.1
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 +77 -8
- package/apis/cds.d.ts +18 -6
- package/apis/connect.d.ts +1 -1
- package/apis/cqn.d.ts +1 -1
- package/apis/log.d.ts +23 -5
- package/apis/ql.d.ts +128 -61
- package/apis/services.d.ts +11 -0
- package/apis/test.d.ts +61 -0
- package/apis/utils.d.ts +15 -0
- package/app/fiori/preview.js +1 -0
- package/bin/build/buildTaskEngine.js +70 -22
- package/bin/build/buildTaskFactory.js +18 -11
- package/bin/build/buildTaskHandler.js +1 -1
- package/bin/build/buildTaskProviderFactory.js +3 -13
- package/bin/build/constants.js +0 -1
- package/bin/build/index.js +14 -6
- package/bin/build/provider/buildTaskHandlerEdmx.js +2 -3
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +2 -2
- package/bin/build/provider/buildTaskHandlerInternal.js +3 -6
- package/bin/build/provider/buildTaskProviderInternal.js +51 -39
- package/bin/build/provider/fiori/index.js +3 -3
- package/bin/build/provider/hana/2migration.js +1 -1
- package/bin/build/provider/hana/index.js +34 -27
- package/bin/build/provider/java/index.js +6 -7
- package/bin/build/provider/mtx/index.js +20 -18
- package/bin/build/provider/mtx/resourcesTarBuilder.js +8 -11
- package/bin/build/provider/mtx-sidecar/index.js +13 -17
- package/bin/build/provider/nodejs/index.js +8 -7
- package/bin/build/util.js +22 -4
- package/bin/cds.js +8 -4
- package/bin/deploy/to-hana/cfUtil.js +53 -18
- package/bin/mtx/in-cds.js +1 -0
- package/bin/serve.js +37 -30
- package/lib/auth/basic-auth.js +33 -0
- package/lib/auth/dummy-auth.js +7 -0
- package/lib/auth/ias-auth.js +2 -0
- package/lib/auth/index.js +31 -0
- package/lib/auth/jwt-auth.js +3 -0
- package/lib/auth/mocked-users.js +72 -0
- package/lib/auth/passport-basic.js +12 -0
- package/lib/auth/passport-digest.js +14 -0
- package/lib/auth/xsuaa-auth.js +3 -0
- package/lib/compile/cds-compile.js +3 -3
- package/lib/compile/to/cdl.js +5 -1
- package/lib/compile/to/edm.js +8 -0
- package/lib/compile/to/gql.js +1 -0
- package/lib/compile/to/json.js +30 -5
- package/lib/compile/to/sql.js +3 -1
- package/lib/core/index.js +5 -1
- package/lib/dbs/cds-deploy.js +36 -6
- package/lib/env/cds-env.js +15 -5
- package/lib/env/cds-requires.js +51 -58
- package/lib/env/defaults.js +1 -0
- package/lib/env/schemas/cds-package.json +4 -0
- package/lib/env/schemas/cds-rc.json +63 -77
- package/lib/i18n/localize.js +16 -5
- package/lib/index.js +9 -4
- package/lib/log/cds-error.js +4 -6
- package/lib/log/cds-log.js +89 -53
- package/lib/log/service/index.js +1 -0
- package/lib/ql/CREATE.js +2 -5
- package/lib/ql/DELETE.js +1 -1
- package/lib/ql/DROP.js +1 -3
- package/lib/ql/INSERT.js +3 -3
- package/lib/ql/Query.js +10 -23
- package/lib/ql/SELECT.js +1 -2
- package/lib/ql/UPDATE.js +2 -2
- package/lib/ql/Whereable.js +7 -15
- package/lib/ql/cds-ql.js +9 -3
- package/lib/req/cds-context.js +11 -3
- package/lib/req/context.js +29 -23
- package/lib/req/locale.js +9 -5
- package/lib/req/request.js +1 -0
- package/lib/req/user.js +2 -1
- package/lib/srv/cds-connect.js +1 -1
- package/lib/srv/cds-serve.js +21 -14
- package/lib/srv/middlewares/cds-context.js +29 -0
- package/lib/srv/middlewares/ctx-model.js +24 -0
- package/lib/srv/middlewares/errors.js +9 -0
- package/lib/srv/middlewares/index.js +22 -0
- package/lib/srv/middlewares/sap-statistics.js +13 -0
- package/lib/srv/middlewares/trace.js +102 -0
- package/lib/srv/protocols/_legacy.js +42 -0
- package/lib/srv/protocols/graphql.js +39 -0
- package/lib/srv/protocols/hcql.js +37 -0
- package/lib/srv/protocols/index.js +86 -0
- package/lib/srv/protocols/odata-v2-proxy.js +3767 -0
- package/lib/srv/protocols/odata-v2.js +26 -0
- package/lib/srv/protocols/odata-v4.js +16 -0
- package/lib/srv/protocols/rest.js +13 -0
- package/lib/srv/srv-api.js +5 -0
- package/lib/srv/srv-models.js +4 -6
- package/lib/utils/axios.js +3 -2
- package/lib/utils/cds-test.js +27 -21
- package/lib/utils/cds-utils.js +19 -20
- package/lib/utils/tar.js +175 -0
- package/libx/_runtime/audit/generic/personal/utils.js +18 -7
- package/libx/_runtime/audit/utils/v2.js +1 -0
- package/libx/_runtime/auth/index.js +4 -0
- package/libx/_runtime/auth/strategies/ias-auth.js +76 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +8 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +15 -4
- 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/orderByToCQN.js +1 -1
- 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/uri/ResourcePathParser.js +9 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriInfo.js +5 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +12 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/RequestValidator.js +47 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/util/assert.js +4 -0
- package/libx/_runtime/common/aspects/relation.js +1 -1
- package/libx/_runtime/common/composition/data.js +61 -15
- package/libx/_runtime/common/composition/delete.js +0 -1
- package/libx/_runtime/common/composition/insert.js +0 -1
- package/libx/_runtime/common/composition/tree.js +4 -10
- package/libx/_runtime/common/composition/update.js +44 -21
- package/libx/_runtime/common/generic/auth/capabilities.js +8 -10
- package/libx/_runtime/common/generic/crud.js +1 -2
- package/libx/_runtime/common/generic/etag.js +4 -4
- package/libx/_runtime/common/generic/input.js +4 -4
- package/libx/_runtime/common/generic/paging.js +3 -3
- package/libx/_runtime/common/generic/put.js +3 -3
- package/libx/_runtime/common/generic/sorting.js +4 -4
- package/libx/_runtime/common/generic/temporal.js +3 -3
- package/libx/_runtime/common/i18n/messages.properties +0 -7
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +11 -6
- package/libx/_runtime/common/utils/csn.js +0 -28
- package/libx/_runtime/common/utils/draft.js +8 -1
- package/libx/_runtime/common/utils/path.js +7 -1
- package/libx/_runtime/common/utils/resolveView.js +2 -3
- package/libx/_runtime/db/data-conversion/post-processing.js +3 -44
- package/libx/_runtime/db/generic/input.js +3 -3
- package/libx/_runtime/db/sql-builder/dataTypes.js +4 -0
- package/libx/_runtime/fiori/generic/activate.js +2 -2
- package/libx/_runtime/fiori/generic/before.js +40 -72
- package/libx/_runtime/fiori/generic/cancel.js +2 -2
- package/libx/_runtime/fiori/generic/delete.js +2 -2
- package/libx/_runtime/fiori/generic/edit.js +2 -2
- package/libx/_runtime/fiori/generic/new.js +2 -2
- package/libx/_runtime/fiori/generic/patch.js +49 -37
- package/libx/_runtime/fiori/generic/prepare.js +2 -2
- package/libx/_runtime/fiori/generic/read.js +27 -37
- package/libx/_runtime/fiori/utils/where.js +4 -2
- package/libx/_runtime/hana/Service.js +1 -3
- package/libx/_runtime/hana/conversion.js +3 -0
- package/libx/_runtime/hana/driver.js +33 -3
- package/libx/_runtime/hana/dynatrace.js +1 -0
- package/libx/_runtime/hana/search2Contains.js +12 -1
- package/libx/_runtime/hana/search2cqn4sql.js +10 -27
- package/libx/_runtime/hana/streaming.js +1 -0
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -0
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +5 -2
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -0
- package/libx/_runtime/messaging/enterprise-messaging.js +62 -3
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/redis-messaging.js +1 -0
- package/libx/_runtime/remote/Service.js +2 -2
- package/libx/_runtime/remote/utils/client.js +8 -3
- package/libx/_runtime/remote/utils/data.js +7 -2
- package/libx/_runtime/sqlite/Service.js +18 -7
- package/libx/_runtime/sqlite/conversion.js +3 -0
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +3 -3
- package/libx/_runtime/sqlite/localized.js +8 -8
- package/libx/odata/afterburner.js +39 -7
- package/libx/odata/cqn2odata.js +6 -3
- package/libx/odata/grammar.pegjs +66 -18
- package/libx/odata/index.js +3 -2
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +2 -0
- package/libx/rest/RestAdapter.js +62 -43
- package/libx/rest/middleware/parse.js +2 -1
- package/libx/rest/middleware/update.js +1 -1
- package/package.json +2 -2
- package/server.js +5 -4
- package/srv/mtx.cds +1 -1
- package/srv/mtx.js +4 -33
- package/lib/srv/adapters.js +0 -85
- package/lib/utils/resources/index.js +0 -48
- package/lib/utils/resources/tar.js +0 -49
- package/lib/utils/resources/utils.js +0 -11
- package/libx/_runtime/extensibility/activate.js +0 -69
- package/libx/_runtime/extensibility/add.js +0 -50
- package/libx/_runtime/extensibility/addExtension.js +0 -72
- package/libx/_runtime/extensibility/defaults.js +0 -34
- package/libx/_runtime/extensibility/handler/transformREAD.js +0 -121
- package/libx/_runtime/extensibility/handler/transformRESULT.js +0 -51
- package/libx/_runtime/extensibility/handler/transformWRITE.js +0 -64
- package/libx/_runtime/extensibility/linter/allowlist_checker.js +0 -373
- package/libx/_runtime/extensibility/linter/annotations_checker.js +0 -113
- package/libx/_runtime/extensibility/linter/checker_base.js +0 -20
- package/libx/_runtime/extensibility/linter/namespace_checker.js +0 -180
- package/libx/_runtime/extensibility/linter.js +0 -32
- package/libx/_runtime/extensibility/push.js +0 -118
- package/libx/_runtime/extensibility/service.js +0 -38
- package/libx/_runtime/extensibility/token.js +0 -57
- package/libx/_runtime/extensibility/utils.js +0 -131
- package/libx/_runtime/extensibility/validation.js +0 -50
- package/libx/_runtime/extensibility/views.js +0 -12
- package/srv/extensibility-service.cds +0 -60
- package/srv/extensibility-service.js +0 -1
- package/srv/extensions.cds +0 -8
- package/srv/model-provider.cds +0 -61
- package/srv/model-provider.js +0 -143
|
@@ -20,7 +20,7 @@ const _orderExpression = order => {
|
|
|
20
20
|
lastSegment.getKind() === ResourceKind.ANY_EXPRESSION ||
|
|
21
21
|
lastSegment.getKind() === ResourceKind.ALL_EXPRESSION
|
|
22
22
|
) {
|
|
23
|
-
throw getError(501, '
|
|
23
|
+
throw getError(501, '"$orderby" does not support lambda')
|
|
24
24
|
}
|
|
25
25
|
for (let i = 0; i < order.getExpression().getPathSegments().length; i++) {
|
|
26
26
|
ref.push(_buildNavRef(order.getExpression().getPathSegments()[i]))
|
|
@@ -2,7 +2,7 @@ const { getFeatureNotSupportedError } = require('../../../util/errors')
|
|
|
2
2
|
|
|
3
3
|
const { findCsnTargetFor } = require('../../../../common/utils/csn')
|
|
4
4
|
|
|
5
|
-
const CDL_KEYWORDS = new Set(require('@sap/cds-compiler
|
|
5
|
+
const CDL_KEYWORDS = new Set(require('@sap/cds-compiler').to.cdl.keywords)
|
|
6
6
|
|
|
7
7
|
// TODO: Which EDM Types are missing?
|
|
8
8
|
const notToBeConvertedForCompiler = new Set([
|
package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js
CHANGED
|
@@ -334,6 +334,15 @@ class ResourcePathParser {
|
|
|
334
334
|
|
|
335
335
|
if (err) throw err
|
|
336
336
|
|
|
337
|
+
try {
|
|
338
|
+
nextTokenizer.requireNext(TokenKind.ODataIdentifier)
|
|
339
|
+
const uriResources = this._parseBoundOperation(uriPathSegments, currentResource, nextTokenizer)
|
|
340
|
+
if (uriResources) {
|
|
341
|
+
return [currentResource].concat(uriResources)
|
|
342
|
+
}
|
|
343
|
+
} catch (error) {
|
|
344
|
+
}
|
|
345
|
+
|
|
337
346
|
throw new UriSyntaxError(UriSyntaxError.Message.MUST_BE_COUNT_OR_REF_OR_BOUND_OPERATION, uriPathSegments[0])
|
|
338
347
|
}
|
|
339
348
|
|
|
@@ -155,7 +155,11 @@ UriInfo.QueryOptions = {
|
|
|
155
155
|
SKIP: '$skip',
|
|
156
156
|
TOP: '$top',
|
|
157
157
|
SKIPTOKEN: '$skiptoken',
|
|
158
|
-
DELTATOKEN: '$deltatoken'
|
|
158
|
+
DELTATOKEN: '$deltatoken',
|
|
159
|
+
AT: "$at",
|
|
160
|
+
TO: "$to",
|
|
161
|
+
FROM: "$from",
|
|
162
|
+
TOINCLUSIVE: "$toInclusive"
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
module.exports = UriInfo
|
|
@@ -102,6 +102,18 @@ queryOptionParserMap.set(QueryOptions.EXPAND, (value, edm, referringType, crossj
|
|
|
102
102
|
return expandOption
|
|
103
103
|
})
|
|
104
104
|
|
|
105
|
+
queryOptionParserMap.set(QueryOptions.FROM, (value) => {
|
|
106
|
+
return value
|
|
107
|
+
})
|
|
108
|
+
queryOptionParserMap.set(QueryOptions.TO, (value) => {
|
|
109
|
+
return value
|
|
110
|
+
})
|
|
111
|
+
queryOptionParserMap.set(QueryOptions.TOINCLUSIVE, (value) => {
|
|
112
|
+
return value
|
|
113
|
+
})
|
|
114
|
+
queryOptionParserMap.set(QueryOptions.AT, (value) => {
|
|
115
|
+
return value
|
|
116
|
+
})
|
|
105
117
|
/**
|
|
106
118
|
* The UriParser is the main class to parse an OData URI.
|
|
107
119
|
*/
|
package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { AsyncResource } = require('node:async_hooks')
|
|
3
4
|
const Transform = require('stream').Transform
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -23,14 +24,17 @@ class BufferedWriter extends Transform {
|
|
|
23
24
|
}
|
|
24
25
|
})
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
// REVISIT: AsyncResource.bind()
|
|
28
|
+
// We AsyncResource.bind() here (also for non middleware case!) to ensure subsequent handlers have access to cds.context -> this test would break if not, and there's an async handler before ours in the route: cds/tests/_runtime/odata/__tests__/integration/crud-with-mtx.test.js
|
|
29
|
+
// Yet, if we do so this test breaks because the implementation of srv.on('error') is pretty screwed: cds/tests/runtime/req.test.js
|
|
30
|
+
this.on('finish', AsyncResource.bind(() => {
|
|
27
31
|
/**
|
|
28
32
|
* Result event to emit the result data.
|
|
29
33
|
* @event BufferedWriter#result
|
|
30
34
|
* @type {Buffer}
|
|
31
35
|
*/
|
|
32
36
|
this.emit('result', this.createResultBuffer())
|
|
33
|
-
})
|
|
37
|
+
}))
|
|
34
38
|
|
|
35
39
|
this._internalBufferList = []
|
|
36
40
|
}
|
package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/RequestValidator.js
CHANGED
|
@@ -67,16 +67,36 @@ const queryOptionsPerResourceKindWhiteList = new Map()
|
|
|
67
67
|
QueryOptions.APPLY,
|
|
68
68
|
QueryOptions.ORDERBY,
|
|
69
69
|
QueryOptions.EXPAND,
|
|
70
|
-
QueryOptions.SELECT
|
|
70
|
+
QueryOptions.SELECT,
|
|
71
|
+
QueryOptions.TO,
|
|
72
|
+
QueryOptions.AT,
|
|
73
|
+
QueryOptions.TOINCLUSIVE,
|
|
74
|
+
QueryOptions.FROM,
|
|
71
75
|
])
|
|
72
76
|
.set(ResourceKind.ENTITY_COLLECTION + '/' + ResourceKind.COUNT, [
|
|
73
77
|
QueryOptions.APPLY,
|
|
74
78
|
QueryOptions.SEARCH,
|
|
75
79
|
QueryOptions.FILTER
|
|
76
80
|
])
|
|
77
|
-
.set(ResourceKind.ENTITY, [
|
|
81
|
+
.set(ResourceKind.ENTITY, [
|
|
82
|
+
QueryOptions.EXPAND,
|
|
83
|
+
QueryOptions.SELECT,
|
|
84
|
+
QueryOptions.FORMAT,
|
|
85
|
+
QueryOptions.TO,
|
|
86
|
+
QueryOptions.AT,
|
|
87
|
+
QueryOptions.TOINCLUSIVE,
|
|
88
|
+
QueryOptions.FROM
|
|
89
|
+
])
|
|
78
90
|
.set(ResourceKind.ENTITY + '/' + ResourceKind.VALUE, [QueryOptions.FORMAT])
|
|
79
|
-
.set(ResourceKind.SINGLETON, [
|
|
91
|
+
.set(ResourceKind.SINGLETON, [
|
|
92
|
+
QueryOptions.EXPAND,
|
|
93
|
+
QueryOptions.SELECT,
|
|
94
|
+
QueryOptions.FORMAT,
|
|
95
|
+
QueryOptions.TO,
|
|
96
|
+
QueryOptions.AT,
|
|
97
|
+
QueryOptions.TOINCLUSIVE,
|
|
98
|
+
QueryOptions.FROM
|
|
99
|
+
])
|
|
80
100
|
.set(ResourceKind.REF, [QueryOptions.FORMAT])
|
|
81
101
|
.set(ResourceKind.REF_COLLECTION, [
|
|
82
102
|
QueryOptions.SEARCH,
|
|
@@ -86,9 +106,21 @@ const queryOptionsPerResourceKindWhiteList = new Map()
|
|
|
86
106
|
QueryOptions.SKIP,
|
|
87
107
|
QueryOptions.SKIPTOKEN,
|
|
88
108
|
QueryOptions.TOP,
|
|
89
|
-
QueryOptions.FORMAT
|
|
109
|
+
QueryOptions.FORMAT,
|
|
110
|
+
QueryOptions.TO,
|
|
111
|
+
QueryOptions.AT,
|
|
112
|
+
QueryOptions.TOINCLUSIVE,
|
|
113
|
+
QueryOptions.FROM
|
|
114
|
+
])
|
|
115
|
+
.set(ResourceKind.COMPLEX_PROPERTY, [
|
|
116
|
+
QueryOptions.EXPAND,
|
|
117
|
+
QueryOptions.SELECT,
|
|
118
|
+
QueryOptions.FORMAT,
|
|
119
|
+
QueryOptions.TO,
|
|
120
|
+
QueryOptions.AT,
|
|
121
|
+
QueryOptions.TOINCLUSIVE,
|
|
122
|
+
QueryOptions.FROM,
|
|
90
123
|
])
|
|
91
|
-
.set(ResourceKind.COMPLEX_PROPERTY, [QueryOptions.EXPAND, QueryOptions.SELECT, QueryOptions.FORMAT])
|
|
92
124
|
.set(ResourceKind.COMPLEX_COLLECTION_PROPERTY, [
|
|
93
125
|
QueryOptions.APPLY,
|
|
94
126
|
QueryOptions.FILTER,
|
|
@@ -99,7 +131,11 @@ const queryOptionsPerResourceKindWhiteList = new Map()
|
|
|
99
131
|
QueryOptions.TOP,
|
|
100
132
|
QueryOptions.EXPAND,
|
|
101
133
|
QueryOptions.SELECT,
|
|
102
|
-
QueryOptions.FORMAT
|
|
134
|
+
QueryOptions.FORMAT,
|
|
135
|
+
QueryOptions.TO,
|
|
136
|
+
QueryOptions.AT,
|
|
137
|
+
QueryOptions.TOINCLUSIVE,
|
|
138
|
+
QueryOptions.FROM,
|
|
103
139
|
])
|
|
104
140
|
.set(ResourceKind.COMPLEX_COLLECTION_PROPERTY + '/' + ResourceKind.COUNT, [QueryOptions.APPLY, QueryOptions.FILTER])
|
|
105
141
|
.set(ResourceKind.PRIMITIVE_PROPERTY, [QueryOptions.FORMAT])
|
|
@@ -110,7 +146,11 @@ const queryOptionsPerResourceKindWhiteList = new Map()
|
|
|
110
146
|
QueryOptions.SKIP,
|
|
111
147
|
QueryOptions.SKIPTOKEN,
|
|
112
148
|
QueryOptions.TOP,
|
|
113
|
-
QueryOptions.FORMAT
|
|
149
|
+
QueryOptions.FORMAT,
|
|
150
|
+
QueryOptions.TO,
|
|
151
|
+
QueryOptions.AT,
|
|
152
|
+
QueryOptions.TOINCLUSIVE,
|
|
153
|
+
QueryOptions.FROM
|
|
114
154
|
])
|
|
115
155
|
.set(ResourceKind.PRIMITIVE_COLLECTION_PROPERTY + '/' + ResourceKind.COUNT, [QueryOptions.FILTER])
|
|
116
156
|
.set(ResourceKind.PRIMITIVE_PROPERTY + '/' + ResourceKind.VALUE, [QueryOptions.FORMAT])
|
|
@@ -26,7 +26,7 @@ if (cds.mtx || cds.requires.extensibility || cds.requires.toggles)
|
|
|
26
26
|
const id = `${++unique} - ${srv.path}` // REVISIT: this is to allow running multiple express apps serving same endpoints, as done by some questionable tests
|
|
27
27
|
return function ODataAdapter(req, res) {
|
|
28
28
|
const model = cds.context?.model || srv.model
|
|
29
|
-
if (!model._cached) Object.defineProperty(model, '_cached', { value: {} })
|
|
29
|
+
if (!model._cached) Object.defineProperty(model, '_cached', { value: { touched: Date.now() } })
|
|
30
30
|
|
|
31
31
|
// Note: cache is attached to model cache so they get disposed when models are evicted from cache
|
|
32
32
|
let adapters = model._cached._odata_adapters || (model._cached._odata_adapters = {})
|
|
@@ -147,7 +147,11 @@ const CDS_TYPE_CHECKS = {
|
|
|
147
147
|
'cds.UUID': _checkUUID,
|
|
148
148
|
'cds.Boolean': _checkBoolean,
|
|
149
149
|
'cds.Integer': _checkInteger,
|
|
150
|
+
'cds.UInt8': _checkInteger,
|
|
151
|
+
'cds.Int16': _checkInteger,
|
|
152
|
+
'cds.Int32': _checkInteger,
|
|
150
153
|
'cds.Integer64': _checkInteger,
|
|
154
|
+
'cds.Int64': _checkInteger,
|
|
151
155
|
'cds.Decimal': _checkDecimal,
|
|
152
156
|
'cds.DecimalFloat': _checkNumber,
|
|
153
157
|
'cds.Double': _checkNumber,
|
|
@@ -123,21 +123,40 @@ const _subData = (data, prop) =>
|
|
|
123
123
|
return result
|
|
124
124
|
}, [])
|
|
125
125
|
|
|
126
|
+
const _getWhereObj = (row, links) => {
|
|
127
|
+
return links.reduce((res, currentLink) => {
|
|
128
|
+
if (Object.prototype.hasOwnProperty.call(row, currentLink.targetKey) && row[currentLink.targetKey] !== null)
|
|
129
|
+
res[currentLink.entityKey] = row[currentLink.targetKey]
|
|
130
|
+
return res
|
|
131
|
+
}, {})
|
|
132
|
+
}
|
|
133
|
+
|
|
126
134
|
const _subWhere = (result, element) => {
|
|
127
135
|
let where
|
|
128
136
|
const links = [...element.backLinks, ...element.customBackLinks]
|
|
129
|
-
if (links && links.length > 0) {
|
|
130
|
-
where =
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
if (result.length && links && links.length > 0) {
|
|
138
|
+
where = {}
|
|
139
|
+
const chunkSize = cds.env.features.chunk_deep
|
|
140
|
+
const keys0 = Object.keys(_getWhereObj(result[0], links))
|
|
141
|
+
if (chunkSize && result.length > chunkSize && keys0.length) {
|
|
142
|
+
const keys = { list: keys0.map(pk => ({ ref: [pk] })) }
|
|
143
|
+
for (let i = 0; i < result.length; i += chunkSize) {
|
|
144
|
+
const values = {
|
|
145
|
+
list: result
|
|
146
|
+
.slice(i, i + chunkSize)
|
|
147
|
+
.map(row => ({ list: keys0.map(k => ({ val: _getWhereObj(row, links)[k] || null })) }))
|
|
148
|
+
}
|
|
149
|
+
where[i] = [keys, 'in', values]
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
where = []
|
|
153
|
+
for (const row of result) {
|
|
154
|
+
const whereObj = _getWhereObj(row, links)
|
|
155
|
+
const whereCQN = ctUtils.whereKey(whereObj)
|
|
156
|
+
if (whereCQN.length) {
|
|
157
|
+
if (where.length > 0) where.push('or')
|
|
158
|
+
where.push({ xpr: [...whereCQN] })
|
|
159
|
+
}
|
|
141
160
|
}
|
|
142
161
|
}
|
|
143
162
|
}
|
|
@@ -214,8 +233,36 @@ const _select = ({
|
|
|
214
233
|
|
|
215
234
|
const _selectDeepUpdateData = async args => {
|
|
216
235
|
const { model, compositionTree, entityName, data, root, selectData, tx, selectAllColumns } = args
|
|
217
|
-
|
|
218
|
-
const
|
|
236
|
+
let result = []
|
|
237
|
+
const chunkSize = cds.env.features.chunk_deep
|
|
238
|
+
if (
|
|
239
|
+
chunkSize &&
|
|
240
|
+
!args.where &&
|
|
241
|
+
args.parentKeys &&
|
|
242
|
+
args.parentKeys.length > chunkSize &&
|
|
243
|
+
Object.keys(args.parentKeys[0]).length
|
|
244
|
+
) {
|
|
245
|
+
const keys0 = Object.keys(args.parentKeys[0])
|
|
246
|
+
const keys = { list: keys0.map(pk => ({ ref: [pk] })) }
|
|
247
|
+
for (let i = 0; i < args.parentKeys.length; i += chunkSize) {
|
|
248
|
+
const values = {
|
|
249
|
+
list: args.parentKeys.slice(i, i + chunkSize).map(row => ({ list: keys0.map(k => ({ val: row[k] || null })) }))
|
|
250
|
+
}
|
|
251
|
+
const _args = { ...args, where: [keys, 'in', values] }
|
|
252
|
+
const selectCQN = _select(_args)
|
|
253
|
+
result.push(...(await tx.run(selectCQN)))
|
|
254
|
+
}
|
|
255
|
+
} else if (chunkSize && args.where && !Array.isArray(args.where)) {
|
|
256
|
+
for (let where of Object.values(args.where)) {
|
|
257
|
+
const _args = { ...args, where }
|
|
258
|
+
const selectCQN = _select(_args)
|
|
259
|
+
result.push(...(await tx.run(selectCQN)))
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
const selectCQN = _select(args)
|
|
263
|
+
result = await tx.run(selectCQN)
|
|
264
|
+
}
|
|
265
|
+
|
|
219
266
|
if (!result.length) return Promise.resolve(result)
|
|
220
267
|
|
|
221
268
|
const keys = _keys(model.definitions[entityName], result)
|
|
@@ -274,7 +321,6 @@ const selectDeepUpdateData = (service, model, req, selectAllColumns = false) =>
|
|
|
274
321
|
const compositionTree = getCompositionTree({
|
|
275
322
|
definitions: model.definitions,
|
|
276
323
|
rootEntityName: entityName, // REVISIT: drafts are resolved too eagerly
|
|
277
|
-
checkRoot: false,
|
|
278
324
|
resolveViews: !draft,
|
|
279
325
|
service
|
|
280
326
|
})
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
|
|
3
3
|
const { ensureNoDraftsSuffix } = require('../utils/draft')
|
|
4
|
-
const { isRootEntity } = require('../utils/csn')
|
|
5
4
|
const { getTransition, getDBTable } = require('../utils/resolveView')
|
|
6
5
|
|
|
7
|
-
const getError = require('../../common/error')
|
|
8
6
|
const { prefixForStruct } = require('../../common/utils/csn')
|
|
9
7
|
|
|
10
8
|
/*
|
|
@@ -186,12 +184,9 @@ const _removeLocalizedTextsFromDraftTree = (compositionTree, definitions, checke
|
|
|
186
184
|
}
|
|
187
185
|
}
|
|
188
186
|
|
|
189
|
-
const _getCompositionTree = ({ definitions, rootEntityName,
|
|
187
|
+
const _getCompositionTree = ({ definitions, rootEntityName, resolveViews = false, service }) => {
|
|
190
188
|
const rootName = resolveViews ? _resolvedEntityName(rootEntityName, definitions) : rootEntityName
|
|
191
189
|
|
|
192
|
-
if (checkRoot && !isRootEntity(definitions, rootEntityName)) {
|
|
193
|
-
throw getError(400, `Entity "${rootEntityName}" is not root entity`)
|
|
194
|
-
}
|
|
195
190
|
const compositionTree = {}
|
|
196
191
|
_getCompositionTreeRec({
|
|
197
192
|
rootEntityName: rootName,
|
|
@@ -238,8 +233,8 @@ const _cacheCompositionParentsOfOne = ({ definitions }) => {
|
|
|
238
233
|
|
|
239
234
|
const _memoizeGetCompositionTree = fn => {
|
|
240
235
|
const cache = new Map()
|
|
241
|
-
return ({ definitions, rootEntityName,
|
|
242
|
-
const key =
|
|
236
|
+
return ({ definitions, rootEntityName, resolveViews = false, service }) => {
|
|
237
|
+
const key = rootEntityName
|
|
243
238
|
|
|
244
239
|
// use ApplicationService as cache key for extensibility
|
|
245
240
|
// REVISIT: context._tx is not a stable API -> pls do not rely on that
|
|
@@ -249,7 +244,7 @@ const _memoizeGetCompositionTree = fn => {
|
|
|
249
244
|
const cachedResult = map && map.get(key)
|
|
250
245
|
if (cachedResult) return cachedResult
|
|
251
246
|
_cacheCompositionParentsOfOne({ definitions })
|
|
252
|
-
const compTree = fn({ definitions, rootEntityName,
|
|
247
|
+
const compTree = fn({ definitions, rootEntityName, resolveViews, service })
|
|
253
248
|
|
|
254
249
|
const _map = map || new Map()
|
|
255
250
|
_map.set(key, compTree)
|
|
@@ -289,7 +284,6 @@ const getCompositionRoot = (definitions, entity) => {
|
|
|
289
284
|
*
|
|
290
285
|
* @param {object} definitions Definitions of the reflected model
|
|
291
286
|
* @param {string} rootEntityName Name of the root entity
|
|
292
|
-
* @param {boolean} checkRoot Check is provided entity is a root
|
|
293
287
|
* @returns {object} tree of all compositions
|
|
294
288
|
* @throws Error if no valid root entity provided
|
|
295
289
|
*/
|
|
@@ -34,17 +34,39 @@ const _dataByKey = (entity, data) => {
|
|
|
34
34
|
return dataByKey
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function _addSubDeepUpdateCQNForDelete({ entity, data, selectData,
|
|
37
|
+
function _addSubDeepUpdateCQNForDelete({ entity, data, selectData, entityName, deleteCQNs }) {
|
|
38
|
+
const chunkSize = cds.env.features.chunk_deep
|
|
38
39
|
const dataByKey = _dataByKey(entity, data)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
if (chunkSize && selectData.length > chunkSize && Object.keys(selectData[0]).length) {
|
|
41
|
+
// REVISIT: Usage of "where in" syntax would be better
|
|
42
|
+
for (let j = 0; j < selectData.length; j += chunkSize) {
|
|
43
|
+
const deleteCQN = { DELETE: { from: entityName, where: [] } }
|
|
44
|
+
for (let i = j; i < j + chunkSize; i++) {
|
|
45
|
+
const selectEntry = selectData[i]
|
|
46
|
+
if (!selectEntry) continue
|
|
47
|
+
const dataEntry = dataByKey.get(_serializedKey(entity, selectEntry))
|
|
48
|
+
if (!dataEntry) {
|
|
49
|
+
if (deleteCQN.DELETE.where.length > 0) {
|
|
50
|
+
deleteCQN.DELETE.where.push('or')
|
|
51
|
+
}
|
|
52
|
+
deleteCQN.DELETE.where.push({ xpr: [...ctUtils.whereKey(ctUtils.key(entity, selectEntry))] })
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
deleteCQNs.push(deleteCQN)
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
const deleteCQN = { DELETE: { from: entityName, where: [] } }
|
|
59
|
+
for (const selectEntry of selectData) {
|
|
60
|
+
if (!selectEntry) continue
|
|
61
|
+
const dataEntry = dataByKey.get(_serializedKey(entity, selectEntry))
|
|
62
|
+
if (!dataEntry) {
|
|
63
|
+
if (deleteCQN.DELETE.where.length > 0) {
|
|
64
|
+
deleteCQN.DELETE.where.push('or')
|
|
65
|
+
}
|
|
66
|
+
deleteCQN.DELETE.where.push({ xpr: [...ctUtils.whereKey(ctUtils.key(entity, selectEntry))] })
|
|
45
67
|
}
|
|
46
|
-
deleteCQN.DELETE.where.push({ xpr: [...ctUtils.whereKey(ctUtils.key(entity, selectEntry))] })
|
|
47
68
|
}
|
|
69
|
+
deleteCQNs.push(deleteCQN)
|
|
48
70
|
}
|
|
49
71
|
}
|
|
50
72
|
|
|
@@ -131,7 +153,7 @@ function _addSubDeepUpdateCQNForUpdateInsert({ entity, entityName, data, selectD
|
|
|
131
153
|
return deepUpdateData
|
|
132
154
|
}
|
|
133
155
|
|
|
134
|
-
async function _addSubDeepUpdateCQNCollect(model, cqns, updateCQNs, insertCQN,
|
|
156
|
+
async function _addSubDeepUpdateCQNCollect(model, cqns, updateCQNs, insertCQN, deleteCQNs, req) {
|
|
135
157
|
if (updateCQNs.length > 0) {
|
|
136
158
|
cqns[0] = cqns[0] || []
|
|
137
159
|
cqns[0].push(...updateCQNs)
|
|
@@ -152,15 +174,17 @@ async function _addSubDeepUpdateCQNCollect(model, cqns, updateCQNs, insertCQN, d
|
|
|
152
174
|
})
|
|
153
175
|
}
|
|
154
176
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
177
|
+
for (let deleteCQN of deleteCQNs) {
|
|
178
|
+
if (deleteCQN.DELETE.where.length > 0) {
|
|
179
|
+
cqns[0] = cqns[0] || []
|
|
180
|
+
const deepDeleteCQNs = await getDeepDeleteCQNs(model, req, deleteCQN)
|
|
181
|
+
deepDeleteCQNs.forEach((delCQNs, index) => {
|
|
182
|
+
delCQNs.forEach(el => {
|
|
183
|
+
cqns[index] = cqns[index] || []
|
|
184
|
+
cqns[index].push(el)
|
|
185
|
+
})
|
|
162
186
|
})
|
|
163
|
-
}
|
|
187
|
+
}
|
|
164
188
|
}
|
|
165
189
|
}
|
|
166
190
|
|
|
@@ -219,8 +243,8 @@ const _addSubDeepUpdateCQN = async ({ model, compositionTree, data, selectData,
|
|
|
219
243
|
const entityName = ctUtils.addDraftSuffix(draft, entity.name)
|
|
220
244
|
const updateCQNs = []
|
|
221
245
|
const insertCQN = { INSERT: { into: entityName, entries: [] } }
|
|
222
|
-
const
|
|
223
|
-
_addSubDeepUpdateCQNForDelete({ entity, data, selectData,
|
|
246
|
+
const deleteCQNs = []
|
|
247
|
+
_addSubDeepUpdateCQNForDelete({ entity, data, selectData, entityName, deleteCQNs })
|
|
224
248
|
const deepUpdateData = _addSubDeepUpdateCQNForUpdateInsert({
|
|
225
249
|
entity,
|
|
226
250
|
entityName,
|
|
@@ -231,7 +255,7 @@ const _addSubDeepUpdateCQN = async ({ model, compositionTree, data, selectData,
|
|
|
231
255
|
model
|
|
232
256
|
})
|
|
233
257
|
|
|
234
|
-
await _addSubDeepUpdateCQNCollect(model, cqns, updateCQNs, insertCQN,
|
|
258
|
+
await _addSubDeepUpdateCQNCollect(model, cqns, updateCQNs, insertCQN, deleteCQNs, req)
|
|
235
259
|
|
|
236
260
|
if (deepUpdateData.length === 0) return Promise.resolve()
|
|
237
261
|
|
|
@@ -287,7 +311,6 @@ const getDeepUpdateCQNs = async (model, req, selectData) => {
|
|
|
287
311
|
const compositionTree = getCompositionTree({
|
|
288
312
|
definitions: model.definitions,
|
|
289
313
|
rootEntityName: entityName,
|
|
290
|
-
checkRoot: false,
|
|
291
314
|
resolveViews: !draft,
|
|
292
315
|
service: cds.db
|
|
293
316
|
})
|
|
@@ -10,16 +10,14 @@ const _isRestricted = (req, capability, capabilityReadByKey) => {
|
|
|
10
10
|
|
|
11
11
|
const _isNavigationRestricted = (target, path, annotation, req) => {
|
|
12
12
|
if (!target) return
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
)
|
|
22
|
-
}
|
|
13
|
+
if (!Array.isArray(target['@Capabilities.NavigationRestrictions.RestrictedProperties'])) return
|
|
14
|
+
|
|
15
|
+
const [restriction, operation] = annotation.split('.')
|
|
16
|
+
for (const r of target['@Capabilities.NavigationRestrictions.RestrictedProperties']) {
|
|
17
|
+
// prefix check to support both notations: { InsertRestrictions: { Insertable: false } } and { InsertRestrictions.Insertable: false }
|
|
18
|
+
if (r.NavigationProperty['='] === path && Object.keys(r).some(k => k.startsWith(restriction))) {
|
|
19
|
+
const capability = r[annotation] ?? r[restriction]?.[operation]
|
|
20
|
+
return _isRestricted(req, capability, r.ReadRestrictions?.['ReadByKeyRestrictions.Readable'])
|
|
23
21
|
}
|
|
24
22
|
}
|
|
25
23
|
}
|
|
@@ -30,8 +30,7 @@ exports.impl = cds.service.impl(function () {
|
|
|
30
30
|
if (typeof req.query !== 'string' && req.target && req.target._hasPersistenceSkip) {
|
|
31
31
|
throw getError({
|
|
32
32
|
code: 501,
|
|
33
|
-
message:
|
|
34
|
-
args: [req.target.name]
|
|
33
|
+
message: `Entity "${req.target.name}" is annotated with "@cds.persistence.skip" and cannot be served generically.`
|
|
35
34
|
})
|
|
36
35
|
}
|
|
37
36
|
|
|
@@ -69,7 +69,7 @@ const _isConcurrentODataReq = req => {
|
|
|
69
69
|
*
|
|
70
70
|
* @param req
|
|
71
71
|
*/
|
|
72
|
-
const
|
|
72
|
+
const commonGenericEtag = async function (req) {
|
|
73
73
|
// REVISIT: The check for ODataRequest should be removed after etag logic is moved
|
|
74
74
|
// from okra to commons and etag handling is also allowed for rest.
|
|
75
75
|
|
|
@@ -104,7 +104,7 @@ const _handler = async function (req) {
|
|
|
104
104
|
*/
|
|
105
105
|
/* istanbul ignore next */
|
|
106
106
|
module.exports = cds.service.impl(function () {
|
|
107
|
-
|
|
107
|
+
commonGenericEtag._initial = true
|
|
108
108
|
|
|
109
109
|
for (const k in this.entities) {
|
|
110
110
|
const entity = this.entities[k]
|
|
@@ -119,10 +119,10 @@ module.exports = cds.service.impl(function () {
|
|
|
119
119
|
events = ['READ', 'NEW', 'DELETE', 'PATCH', 'EDIT', 'CANCEL']
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
this.before(events, entity,
|
|
122
|
+
this.before(events, entity, commonGenericEtag)
|
|
123
123
|
|
|
124
124
|
for (const action in entity.actions) {
|
|
125
|
-
this.before(action, entity,
|
|
125
|
+
this.before(action, entity, commonGenericEtag)
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
})
|
|
@@ -153,7 +153,7 @@ const _processCategory = (req, category, value, elementInfo, assertMap) => {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
// generate UUIDs
|
|
156
|
-
if (category === 'uuid' && !value.val && (event !== 'UPDATE' || !isRoot)) {
|
|
156
|
+
if (category === 'uuid' && !value.val && ((event !== 'UPDATE' && event !== 'PATCH') || !isRoot)) {
|
|
157
157
|
value.val = row[key] = cds.utils.uuid()
|
|
158
158
|
}
|
|
159
159
|
|
|
@@ -248,7 +248,7 @@ const _getBoundActionBindingParameter = req => {
|
|
|
248
248
|
return (actions && actions[action] && actions[action]['@cds.odata.bindingparameter.name']) || 'in'
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
async function
|
|
251
|
+
async function commonGenericInput(req) {
|
|
252
252
|
if (!req.query) return // FIXME: the code below expects req.query to be defined
|
|
253
253
|
if (!req.target) return
|
|
254
254
|
|
|
@@ -386,11 +386,11 @@ function _actionFunctionHandler(req) {
|
|
|
386
386
|
_callError(req, errors)
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
-
|
|
389
|
+
commonGenericInput._initial = true
|
|
390
390
|
_actionFunctionHandler._initial = true
|
|
391
391
|
|
|
392
392
|
module.exports = cds.service.impl(function () {
|
|
393
|
-
this.before(['CREATE', 'UPDATE', 'NEW', 'PATCH'], '*',
|
|
393
|
+
this.before(['CREATE', 'UPDATE', 'NEW', 'PATCH'], '*', commonGenericInput)
|
|
394
394
|
const operationNames = []
|
|
395
395
|
|
|
396
396
|
for (const operation of this.operations) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const { getDefaultPageSize, getMaxPageSize } = require('../utils/page')
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const commonGenericPaging = function (req) {
|
|
5
5
|
// only if http request
|
|
6
6
|
if (!req._.req) return
|
|
7
7
|
|
|
@@ -24,6 +24,6 @@ const _addPaging = function (query, target) {
|
|
|
24
24
|
* handler registration
|
|
25
25
|
*/
|
|
26
26
|
module.exports = cds.service.impl(function () {
|
|
27
|
-
|
|
28
|
-
this.before('READ', '*',
|
|
27
|
+
commonGenericPaging._initial = true
|
|
28
|
+
this.before('READ', '*', commonGenericPaging)
|
|
29
29
|
})
|
|
@@ -55,7 +55,7 @@ const _pick = element => {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function
|
|
58
|
+
function commonGenericPut(req) {
|
|
59
59
|
if (req.method !== 'PUT') return
|
|
60
60
|
if (!req.query) return // FIXME: the code below expects req.query to be defined
|
|
61
61
|
if (!req.target) return
|
|
@@ -87,8 +87,8 @@ function _handler(req) {
|
|
|
87
87
|
setDataFromCQN(req)
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
commonGenericPut._initial = true
|
|
91
91
|
|
|
92
92
|
module.exports = cds.service.impl(function () {
|
|
93
|
-
this.before(['UPDATE'], '*',
|
|
93
|
+
this.before(['UPDATE'], '*', commonGenericPut)
|
|
94
94
|
})
|