@sap/cds 5.6.2 → 5.7.2
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 +133 -0
- package/_i18n/i18n_fr.properties +4 -4
- package/apis/cds.d.ts +7 -10
- package/apis/connect.d.ts +3 -3
- package/apis/core.d.ts +2 -4
- package/apis/models.d.ts +2 -3
- package/apis/ql.d.ts +0 -1
- package/apis/services.d.ts +7 -3
- package/bin/build/buildTaskFactory.js +16 -10
- package/bin/build/buildTaskProviderFactory.js +3 -3
- package/bin/build/constants.js +2 -1
- package/bin/build/provider/buildTaskProviderInternal.js +14 -14
- package/bin/build/provider/hana/2migration.js +2 -3
- package/bin/build/provider/hana/index.js +34 -0
- package/bin/build/provider/hana/migrationtable.js +90 -22
- package/bin/build/provider/hana/template/undeploy.json +5 -0
- package/bin/build/provider/node-cf/index.js +9 -2
- package/bin/serve.js +16 -18
- package/lib/compile/cdsc.js +15 -5
- package/lib/compile/etc/_localized.js +4 -4
- package/lib/compile/extend.js +8 -0
- package/lib/compile/index.js +3 -1
- package/lib/compile/minify.js +61 -0
- package/lib/compile/resolve.js +4 -1
- package/lib/compile/to/gql.js +9 -0
- package/lib/compile/to/sql.js +26 -30
- package/lib/connect/index.js +1 -1
- package/lib/core/entities.js +0 -3
- package/lib/core/infer.js +1 -0
- package/lib/core/reflect.js +0 -34
- package/lib/deploy.js +25 -17
- package/lib/env/defaults.js +3 -1
- package/lib/env/index.js +13 -4
- package/lib/env/presets.js +38 -0
- package/lib/env/requires.js +16 -11
- package/lib/index.js +13 -11
- package/lib/log/format/kibana.js +4 -2
- package/lib/log/index.js +2 -2
- package/lib/ql/Whereable.js +1 -0
- package/lib/req/cds-context.js +79 -0
- package/lib/req/context.js +5 -77
- package/lib/req/request.js +1 -1
- package/lib/serve/Service-api.js +8 -4
- package/lib/serve/Service-dispatch.js +0 -7
- package/lib/serve/Service-methods.js +6 -8
- package/lib/serve/Transaction.js +35 -30
- package/lib/serve/adapters.js +1 -4
- package/lib/utils/axios.js +1 -1
- package/libx/_runtime/audit/Service.js +44 -20
- package/libx/_runtime/audit/generic/personal/access.js +16 -11
- package/libx/_runtime/audit/generic/personal/modification.js +5 -5
- package/libx/_runtime/audit/generic/personal/utils.js +46 -37
- package/libx/_runtime/{common/auth → auth}/index.js +21 -7
- package/libx/_runtime/{common/auth → auth}/strategies/JWT.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/basic.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/dummy.js +1 -1
- package/libx/_runtime/{common/auth → auth}/strategies/mock.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/utils/uaa.js +1 -1
- package/libx/_runtime/{common/auth → auth}/strategies/utils/xssec.js +0 -0
- package/libx/_runtime/{common/auth → auth}/strategies/xsuaa.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +7 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +0 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +6 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +16 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -65
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +3 -66
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +26 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +13 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +54 -76
- package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
- package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -2
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
- package/libx/_runtime/cds-services/services/Service.js +0 -6
- package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
- package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -7
- package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
- package/libx/_runtime/cds-services/util/assert.js +1 -262
- package/libx/_runtime/cds.js +6 -9
- package/libx/_runtime/common/aspects/entity.js +1 -1
- package/libx/_runtime/common/composition/delete.js +4 -2
- package/libx/_runtime/common/composition/update.js +22 -35
- package/libx/_runtime/common/composition/utils.js +3 -7
- package/libx/_runtime/common/error/standardError.js +11 -0
- package/libx/_runtime/common/generic/auth.js +63 -33
- package/libx/_runtime/common/generic/crud.js +11 -23
- package/libx/_runtime/common/generic/input.js +20 -0
- package/libx/_runtime/common/generic/paging.js +2 -2
- package/libx/_runtime/common/generic/put.js +4 -10
- package/libx/_runtime/common/generic/sorting.js +12 -30
- package/libx/_runtime/common/perf/index.js +24 -0
- package/libx/_runtime/common/utils/cqn.js +58 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +297 -121
- package/libx/_runtime/common/utils/csn.js +38 -56
- package/libx/_runtime/common/utils/entityFromCqn.js +6 -6
- package/libx/_runtime/common/utils/resolveView.js +4 -5
- package/libx/_runtime/common/utils/rewriteAsterisks.js +46 -5
- package/libx/_runtime/common/utils/search2cqn4sql.js +21 -9
- package/libx/_runtime/common/utils/structured.js +35 -25
- package/libx/_runtime/db/Service.js +0 -6
- package/libx/_runtime/db/expand/expand-v2.js +130 -0
- package/libx/_runtime/db/expand/expandCQNToJoin.js +38 -52
- package/libx/_runtime/db/expand/index.js +3 -1
- package/libx/_runtime/db/generic/arrayed.js +3 -1
- package/libx/_runtime/db/generic/input.js +52 -10
- package/libx/_runtime/db/generic/integrity.js +367 -26
- package/libx/_runtime/db/generic/virtual.js +51 -13
- package/libx/_runtime/db/query/update.js +9 -3
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +8 -9
- package/libx/_runtime/{common → db}/utils/propagateForeignKeys.js +11 -14
- package/libx/_runtime/fiori/generic/activate.js +1 -0
- package/libx/_runtime/fiori/generic/before.js +2 -1
- package/libx/_runtime/fiori/generic/edit.js +1 -0
- package/libx/_runtime/fiori/generic/patch.js +1 -1
- package/libx/_runtime/fiori/generic/read.js +155 -57
- package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +0 -4
- package/libx/_runtime/fiori/uiflex/index.js +1 -1
- package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +6 -4
- package/libx/_runtime/fiori/utils/delete.js +7 -1
- package/libx/_runtime/hana/Service.js +1 -8
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
- package/libx/_runtime/hana/execute.js +10 -4
- package/libx/_runtime/hana/pool.js +55 -45
- package/libx/_runtime/hana/search.js +7 -6
- package/libx/_runtime/hana/search2cqn4sql.js +8 -5
- package/libx/_runtime/hana/searchToContains.js +3 -1
- package/libx/_runtime/index.js +5 -5
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
- package/libx/_runtime/messaging/Outbox.js +53 -0
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
- package/libx/_runtime/messaging/common-utils/connections.js +14 -9
- package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
- package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
- package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
- package/libx/_runtime/messaging/file-based.js +5 -5
- package/libx/_runtime/messaging/message-queuing.js +2 -3
- package/libx/_runtime/messaging/outbox/OutboxRunner.js +75 -0
- package/libx/_runtime/messaging/outbox/utils.js +192 -0
- package/libx/_runtime/messaging/service.js +16 -30
- package/libx/_runtime/remote/Service.js +15 -0
- package/libx/_runtime/remote/utils/client.js +15 -3
- package/libx/_runtime/remote/utils/{dataConversion.js → data.js} +12 -2
- package/libx/_runtime/sqlite/Service.js +7 -10
- package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +19 -0
- package/libx/_runtime/sqlite/execute.js +18 -12
- package/libx/_runtime/types/api.js +2 -1
- package/libx/gql/resolvers/parse/ast2cqn/columns.js +1 -1
- package/libx/odata/{odata2cqn/afterburner.js → afterburner.js} +28 -16
- package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
- package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +182 -118
- package/libx/odata/index.js +18 -15
- package/libx/odata/parser.js +1 -0
- package/libx/odata/utils.js +57 -0
- package/libx/rest/RestAdapter.js +2 -6
- package/libx/rest/utils/data.js +1 -6
- package/package.json +4 -3
- package/server.js +4 -5
- package/srv/audit-log.cds +87 -0
- package/{libx/_runtime/fiori/uiflex/extensibility/index.cds → srv/flex.cds} +0 -0
- package/srv/flex.js +1 -0
- package/srv/outbox.cds +11 -0
- package/srv/outbox.js +0 -0
- package/libx/_runtime/cds-services/adapter/perf/performance.js +0 -104
- package/libx/_runtime/cds-services/adapter/perf/performanceMeasurement.js +0 -33
- package/libx/odata/odata2cqn/index.js +0 -3
- package/libx/odata/odata2cqn/parser.js +0 -1
- package/libx/odata/readme.md +0 -1
- package/libx/odata/utils/index.js +0 -64
|
@@ -25,7 +25,7 @@ const FORMAT_REGEXP = new RegExp(
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
const parseNonNegativeInteger = value => {
|
|
28
|
-
|
|
28
|
+
const tokenizer = new UriTokenizer(value)
|
|
29
29
|
if (tokenizer.next(TokenKind.UnsignedIntegerValue) && tokenizer.next(TokenKind.EOF)) {
|
|
30
30
|
const result = Number.parseInt(value, 10)
|
|
31
31
|
if (Number.isSafeInteger(result)) return result
|
|
@@ -66,19 +66,22 @@ queryOptionParserMap.set(QueryOptions.FORMAT, value => {
|
|
|
66
66
|
throw new UriSyntaxError(UriSyntaxError.Message.WRONG_OPTION_VALUE, QueryOptions.FORMAT)
|
|
67
67
|
})
|
|
68
68
|
queryOptionParserMap.set(QueryOptions.SEARCH, value => {
|
|
69
|
-
|
|
69
|
+
const trimmedValue = value.trim()
|
|
70
|
+
if (!trimmedValue) return
|
|
71
|
+
|
|
72
|
+
const tokenizer = new UriTokenizer(trimmedValue)
|
|
70
73
|
const searchOption = new SearchParser().parse(tokenizer)
|
|
71
74
|
tokenizer.requireNext(TokenKind.EOF)
|
|
72
75
|
return searchOption
|
|
73
76
|
})
|
|
74
77
|
queryOptionParserMap.set(QueryOptions.FILTER, (value, edm, referringType, crossjoinEntitySets, aliases) => {
|
|
75
|
-
|
|
78
|
+
const tokenizer = new UriTokenizer(value)
|
|
76
79
|
const filterOption = new FilterParser(edm).parse(tokenizer, referringType, crossjoinEntitySets, aliases)
|
|
77
80
|
tokenizer.requireNext(TokenKind.EOF)
|
|
78
81
|
return filterOption
|
|
79
82
|
})
|
|
80
83
|
queryOptionParserMap.set(QueryOptions.ORDERBY, (value, edm, referringType, crossjoinEntitySets, aliases) => {
|
|
81
|
-
|
|
84
|
+
const tokenizer = new UriTokenizer(value)
|
|
82
85
|
const orderByOption = new OrderByParser(edm).parse(tokenizer, referringType, crossjoinEntitySets, aliases)
|
|
83
86
|
tokenizer.requireNext(TokenKind.EOF)
|
|
84
87
|
return orderByOption
|
|
@@ -86,14 +89,14 @@ queryOptionParserMap.set(QueryOptions.ORDERBY, (value, edm, referringType, cross
|
|
|
86
89
|
queryOptionParserMap.set(
|
|
87
90
|
QueryOptions.SELECT,
|
|
88
91
|
(value, edm, referringType, crossjoinEntitySets, aliases, isCollection) => {
|
|
89
|
-
|
|
92
|
+
const tokenizer = new UriTokenizer(value)
|
|
90
93
|
const selectOption = new SelectParser(edm).parse(tokenizer, referringType, isCollection)
|
|
91
94
|
tokenizer.requireNext(TokenKind.EOF)
|
|
92
95
|
return selectOption
|
|
93
96
|
}
|
|
94
97
|
)
|
|
95
98
|
queryOptionParserMap.set(QueryOptions.EXPAND, (value, edm, referringType, crossjoinEntitySets, aliases) => {
|
|
96
|
-
|
|
99
|
+
const tokenizer = new UriTokenizer(value)
|
|
97
100
|
const expandOption = new ExpandParser(edm).parse(tokenizer, referringType, crossjoinEntitySets, aliases)
|
|
98
101
|
tokenizer.requireNext(TokenKind.EOF)
|
|
99
102
|
return expandOption
|
|
@@ -170,7 +173,7 @@ class UriParser {
|
|
|
170
173
|
|
|
171
174
|
// $apply must be parsed first.
|
|
172
175
|
if (queryOptions[QueryOptions.APPLY] !== undefined) {
|
|
173
|
-
|
|
176
|
+
const tokenizer = new UriTokenizer(queryOptions[QueryOptions.APPLY])
|
|
174
177
|
referringType = new TransientStructuredType(referringType)
|
|
175
178
|
const parseApplyQueryOptionPm = parseQueryOptionsPm
|
|
176
179
|
? parseQueryOptionsPm.createChild('Parse query option $apply').start()
|
|
@@ -219,7 +222,7 @@ class UriParser {
|
|
|
219
222
|
*/
|
|
220
223
|
_parseRelativeUri (uriPathSegments, aliases) {
|
|
221
224
|
let currentUriSegment = uriPathSegments[0]
|
|
222
|
-
|
|
225
|
+
const tokenizer = new UriTokenizer(currentUriSegment)
|
|
223
226
|
|
|
224
227
|
if (tokenizer.next(TokenKind.EOF)) {
|
|
225
228
|
uriPathSegments.shift()
|
|
@@ -257,7 +260,7 @@ class UriParser {
|
|
|
257
260
|
// Type casts are explicitly not supported (although the parser can parse them)
|
|
258
261
|
FeatureSupport.failUnsupported(FeatureSupport.features.TypeCast, uriPathSegment, 0)
|
|
259
262
|
|
|
260
|
-
|
|
263
|
+
const tokenizer = new UriTokenizer(uriPathSegment)
|
|
261
264
|
tokenizer.requireNext(TokenKind.QualifiedName)
|
|
262
265
|
const qualifiedName = tokenizer.getText()
|
|
263
266
|
const type = this._edm.getEntityType(FullQualifiedName.createFromNameSpaceAndName(qualifiedName))
|
|
@@ -275,7 +278,7 @@ class UriParser {
|
|
|
275
278
|
*/
|
|
276
279
|
_parseResourcePath (uriPathSegments, aliases) {
|
|
277
280
|
let currentUriSegment = uriPathSegments[0]
|
|
278
|
-
|
|
281
|
+
const tokenizer = new UriTokenizer(currentUriSegment)
|
|
279
282
|
|
|
280
283
|
if (tokenizer.next(TokenKind.ALL)) {
|
|
281
284
|
FeatureSupport.failUnsupported(FeatureSupport.features.All)
|
|
@@ -34,13 +34,6 @@ class ConditionalRequestControlCommand extends Command {
|
|
|
34
34
|
execute (next) {
|
|
35
35
|
// Validate that the ETag validation has been called by the data handler.
|
|
36
36
|
if (this._request.isConditional()) {
|
|
37
|
-
if (!this._request.validateEtagHasBeenCalled()) {
|
|
38
|
-
throw new InternalServerError(
|
|
39
|
-
'Error in conditional request processing. ' +
|
|
40
|
-
' The function validateEtag(etag) has to be called by the application.'
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
37
|
// Do not set statusCode directly because of internal response cache.
|
|
45
38
|
if (this._request.getETAGValidationStatus() === StatusCodes.NOT_MODIFIED) {
|
|
46
39
|
this._response.setStatusCode(StatusCodes.NOT_MODIFIED)
|
|
@@ -34,14 +34,6 @@ class ConditionalRequestValidator {
|
|
|
34
34
|
) {
|
|
35
35
|
throw new PreconditionFailedError()
|
|
36
36
|
}
|
|
37
|
-
} else if (
|
|
38
|
-
method !== HttpMethods.GET ||
|
|
39
|
-
(ifMatch && ifMatch.trim() !== '*' && ifMatch.trim() !== '"*"') ||
|
|
40
|
-
ifNoneMatch
|
|
41
|
-
) {
|
|
42
|
-
// A GET request with an If-Match header value of '*' (or '"*"', despite not in RFC 7232) is allowed,
|
|
43
|
-
// but otherwise concurrency-control headers are not expected here.
|
|
44
|
-
throw new ConflictError('The requested resource is not concurrent')
|
|
45
37
|
}
|
|
46
38
|
}
|
|
47
39
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
|
+
const LOG = cds.log('odata')
|
|
3
|
+
|
|
2
4
|
const { SELECT } = cds.ql
|
|
3
|
-
|
|
4
|
-
const {
|
|
5
|
-
const {
|
|
6
|
-
const { findCsnTargetFor } = require('../../../../common/utils/csn')
|
|
5
|
+
|
|
6
|
+
const { targetFromPath, isPathToDraft } = require('../../../../common/utils/cqn')
|
|
7
|
+
const { deepCopyArray } = require('../../../../common/utils/copy')
|
|
7
8
|
|
|
8
9
|
const isStreaming = segments => {
|
|
9
10
|
const lastSegment = segments[segments.length - 1]
|
|
@@ -14,87 +15,64 @@ const isStreaming = segments => {
|
|
|
14
15
|
)
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
// REVISIT DRAFT HANDLING: cqn2cqn4sql should not happen here, but adaptStreamCQN relies on exists clause
|
|
21
|
-
const cqn = cqn2cqn4sql(SELECT.one(req.query.SELECT.from), req._model).columns(properties)
|
|
22
|
-
|
|
23
|
-
// REVISIT: renaming of media type property (e.g., mimeType as MimeType in AFC) not reflected in @Core.MediaType
|
|
24
|
-
if (req.target.query && req.target.query._target && !req.target.drafts) {
|
|
25
|
-
cqn.SELECT.from.ref[0] = req.target.query._target.name
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (!isActiveRequested) {
|
|
29
|
-
cqn.SELECT.from.ref[0] = ensureDraftsSuffix(cqn.SELECT.from.ref[0])
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
return await cds.tx(req).run(cqn)
|
|
34
|
-
} catch (e) {
|
|
35
|
-
// REVISIT: why ignore?
|
|
36
|
-
}
|
|
37
|
-
}
|
|
18
|
+
const getStreamProperties = (req, model) => {
|
|
19
|
+
const mediaTypeProperty = Object.values(req.target.elements).find(val => val['@Core.MediaType'])
|
|
38
20
|
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
|
|
21
|
+
let contentType, contentDisposition
|
|
22
|
+
const columns = []
|
|
23
|
+
if (typeof mediaTypeProperty['@Core.MediaType'] === 'object') {
|
|
24
|
+
let contentTypeProperty = mediaTypeProperty['@Core.MediaType']['=']
|
|
25
|
+
if (!req.target.elements[contentTypeProperty]) {
|
|
26
|
+
LOG._warn &&
|
|
27
|
+
LOG.warn(
|
|
28
|
+
`@Core.MediaType in entity "${req.target.name}" points to property "${contentTypeProperty}" which was renamed or is not part of the projection. You must update the annotation value.`
|
|
29
|
+
)
|
|
30
|
+
contentTypeProperty = (Object.values(req.target.elements).find(val => val['@Core.IsMediaType']) || {}).name
|
|
31
|
+
}
|
|
32
|
+
if (!req.target.elements[contentTypeProperty]) {
|
|
33
|
+
LOG._warn && LOG.warn(`No @Core.IsMediaType found in entity "${req.target.name}".`)
|
|
34
|
+
} else {
|
|
35
|
+
columns.push({ ref: [contentTypeProperty], as: 'contentType' })
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
contentType = mediaTypeProperty['@Core.MediaType']
|
|
43
39
|
}
|
|
44
|
-
if (
|
|
45
|
-
|
|
40
|
+
if (mediaTypeProperty['@Core.ContentDisposition.Filename']) {
|
|
41
|
+
if (typeof mediaTypeProperty['@Core.ContentDisposition.Filename'] === 'object') {
|
|
42
|
+
const contentDispositionProperty = mediaTypeProperty['@Core.ContentDisposition.Filename']['=']
|
|
43
|
+
if (!req.target.elements[contentDispositionProperty]) {
|
|
44
|
+
LOG._warn &&
|
|
45
|
+
LOG.warn(
|
|
46
|
+
`@Core.ContentDisposition.Filename in entity "${req.target.name}" points to property "${contentDispositionProperty}" which was renamed or is not part of the projection. You must update the annotation value.`
|
|
47
|
+
)
|
|
48
|
+
} else {
|
|
49
|
+
columns.push({ ref: [contentDispositionProperty], as: 'contentDisposition' })
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
contentDisposition = mediaTypeProperty['@Core.ContentDisposition.Filename']
|
|
53
|
+
}
|
|
46
54
|
}
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const getStreamProperties = async (segments, srv, req) => {
|
|
52
|
-
// REVISIT: we need to read directly from db, which might not be there!
|
|
53
|
-
if (!cds.db) return {}
|
|
56
|
+
if (columns.length && cds.db && !req.target._hasPersistenceSkip) {
|
|
57
|
+
// used cloned path
|
|
58
|
+
const select = SELECT.one.from({ ref: deepCopyArray(req.query.SELECT.from.ref) }).columns(columns)
|
|
54
59
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (previous.getKind() === 'ENTITY') {
|
|
58
|
-
entityName = previous.getEntitySet().getName()
|
|
59
|
-
namespace = previous.getEdmType().getFullQualifiedName().namespace
|
|
60
|
-
} else if (previous.getKind() === 'NAVIGATION.TO.ONE' && previous.getTarget()) {
|
|
61
|
-
entityName = previous.getTarget().getName()
|
|
62
|
-
namespace = previous.getTarget().getEntityType().getFullQualifiedName().namespace
|
|
63
|
-
}
|
|
60
|
+
if (req.target._isDraftEnabled && isPathToDraft(select.SELECT.from.ref, model))
|
|
61
|
+
select.SELECT.from.ref[0].id = select.SELECT.from.ref[0].id + '_drafts'
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (entityDefinition._hasPersistenceSkip) return {}
|
|
63
|
+
// new parser has media property as last ref element -> remove
|
|
64
|
+
if (targetFromPath(select.SELECT.from.ref, model).kind === 'element') select.SELECT.from.ref.pop()
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const result = await _getProperties(Object.values(properties), req)
|
|
77
|
-
if (properties.contentDisposition && result[properties.contentDisposition] !== undefined) {
|
|
78
|
-
contentDisposition = result[properties.contentDisposition]
|
|
79
|
-
}
|
|
80
|
-
// REVISIT: renaming of media type property (e.g., mimeType as MimeType in AFC) not reflected in @Core.MediaType
|
|
81
|
-
if (properties.contentType) {
|
|
82
|
-
if (result[properties.contentType] !== undefined) contentType = result[properties.contentType]
|
|
83
|
-
else {
|
|
84
|
-
let ct
|
|
85
|
-
for (const k in entityDefinition.elements) {
|
|
86
|
-
if (entityDefinition.elements[k]['@Core.IsMediaType']) {
|
|
87
|
-
ct = k
|
|
88
|
-
break
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
contentType = result[ct]
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
66
|
+
return cds
|
|
67
|
+
.tx(req)
|
|
68
|
+
.run(select)
|
|
69
|
+
.then(res => ({
|
|
70
|
+
contentType: (res && res.contentType) || contentType,
|
|
71
|
+
contentDisposition: (res && res.contentDisposition) || contentDisposition
|
|
72
|
+
}))
|
|
95
73
|
}
|
|
96
74
|
|
|
97
|
-
return { contentType, contentDisposition }
|
|
75
|
+
return Promise.resolve({ contentType, contentDisposition })
|
|
98
76
|
}
|
|
99
77
|
|
|
100
78
|
module.exports = {
|
|
@@ -58,13 +58,6 @@ class RestRequest extends cds.Request {
|
|
|
58
58
|
return cds.tx(this).run(...args)
|
|
59
59
|
}
|
|
60
60
|
})
|
|
61
|
-
|
|
62
|
-
if (this._.req.performanceMeasurement) {
|
|
63
|
-
this.performanceMeasurement = this._.req.performanceMeasurement
|
|
64
|
-
}
|
|
65
|
-
if (this._.req.dynatrace) {
|
|
66
|
-
this.dynatrace = this._.req.dynatrace
|
|
67
|
-
}
|
|
68
61
|
}
|
|
69
62
|
}
|
|
70
63
|
|
|
@@ -50,7 +50,7 @@ module.exports = service => {
|
|
|
50
50
|
const tx = service.tx({ user: restReq.user, req: restReq, _model: service.model })
|
|
51
51
|
cds.context = tx
|
|
52
52
|
|
|
53
|
-
let result, err,
|
|
53
|
+
let result, err, location
|
|
54
54
|
try {
|
|
55
55
|
const reqs = data.map(d => new RestRequest(parsed, d, restReq, restRes, service))
|
|
56
56
|
result = await Promise.all(reqs.map(req => tx.dispatch(req)))
|
|
@@ -64,14 +64,11 @@ module.exports = service => {
|
|
|
64
64
|
location = _locationHeader(target, service.name, result)
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
commit = true
|
|
68
67
|
await tx.commit(result)
|
|
69
68
|
} catch (e) {
|
|
70
69
|
err = e
|
|
71
|
-
if
|
|
72
|
-
|
|
73
|
-
await tx.rollback(e).catch(() => {})
|
|
74
|
-
}
|
|
70
|
+
// REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
|
|
71
|
+
await tx.rollback(e).catch(() => {})
|
|
75
72
|
} finally {
|
|
76
73
|
if (err) next(err)
|
|
77
74
|
else {
|
|
@@ -18,18 +18,15 @@ module.exports = service => {
|
|
|
18
18
|
|
|
19
19
|
const req = new RestRequest(parsed, data, restReq, restRes, service)
|
|
20
20
|
|
|
21
|
-
let err
|
|
21
|
+
let err
|
|
22
22
|
try {
|
|
23
23
|
await tx.dispatch(req)
|
|
24
24
|
|
|
25
|
-
commit = true
|
|
26
25
|
await tx.commit(null)
|
|
27
26
|
} catch (e) {
|
|
28
27
|
err = e
|
|
29
|
-
if
|
|
30
|
-
|
|
31
|
-
await tx.rollback(e).catch(() => {})
|
|
32
|
-
}
|
|
28
|
+
// REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
|
|
29
|
+
await tx.rollback(e).catch(() => {})
|
|
33
30
|
} finally {
|
|
34
31
|
if (err) next(err)
|
|
35
32
|
else {
|
|
@@ -32,7 +32,7 @@ module.exports = service => {
|
|
|
32
32
|
|
|
33
33
|
const req = new RestRequest(parsed, data, restReq, restRes, service)
|
|
34
34
|
|
|
35
|
-
let result, err,
|
|
35
|
+
let result, err, status, body
|
|
36
36
|
try {
|
|
37
37
|
result = await tx.dispatch(req)
|
|
38
38
|
|
|
@@ -44,14 +44,11 @@ module.exports = service => {
|
|
|
44
44
|
body = _convertCustomOperationReturnValue(operation.returns, result)
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
commit = true
|
|
48
47
|
await tx.commit(result)
|
|
49
48
|
} catch (e) {
|
|
50
49
|
err = e
|
|
51
|
-
if
|
|
52
|
-
|
|
53
|
-
await tx.rollback(e).catch(() => {})
|
|
54
|
-
}
|
|
50
|
+
// REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
|
|
51
|
+
await tx.rollback(e).catch(() => {})
|
|
55
52
|
} finally {
|
|
56
53
|
if (err) next(err)
|
|
57
54
|
else {
|
|
@@ -22,7 +22,7 @@ module.exports = service => {
|
|
|
22
22
|
const tx = service.tx({ user: restReq.user, req: restReq, _model: service.model })
|
|
23
23
|
cds.context = tx
|
|
24
24
|
|
|
25
|
-
let result, err
|
|
25
|
+
let result, err
|
|
26
26
|
try {
|
|
27
27
|
const req = new RestRequest(parsed, data, restReq, restRes, service)
|
|
28
28
|
|
|
@@ -34,14 +34,11 @@ module.exports = service => {
|
|
|
34
34
|
|
|
35
35
|
bufferToBase64(result, segments[0])
|
|
36
36
|
|
|
37
|
-
commit = true
|
|
38
37
|
await tx.commit(result)
|
|
39
38
|
} catch (e) {
|
|
40
39
|
err = e
|
|
41
|
-
if
|
|
42
|
-
|
|
43
|
-
await tx.rollback(e).catch(() => {})
|
|
44
|
-
}
|
|
40
|
+
// REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
|
|
41
|
+
await tx.rollback(e).catch(() => {})
|
|
45
42
|
} finally {
|
|
46
43
|
if (err) next(err)
|
|
47
44
|
else restRes.send(toRestResult(result))
|
|
@@ -52,7 +52,7 @@ const update = service => {
|
|
|
52
52
|
const tx = service.tx({ user: restReq.user, req: restReq, _model: service.model })
|
|
53
53
|
cds.context = tx
|
|
54
54
|
|
|
55
|
-
let result, err,
|
|
55
|
+
let result, err, status
|
|
56
56
|
try {
|
|
57
57
|
// try UPDATE and, on 404 error, try CREATE
|
|
58
58
|
result = await _updateThenCreate(parsed, restReq, restRes, tx)
|
|
@@ -62,14 +62,11 @@ const update = service => {
|
|
|
62
62
|
|
|
63
63
|
bufferToBase64(result, target)
|
|
64
64
|
|
|
65
|
-
commit = true
|
|
66
65
|
await tx.commit(result)
|
|
67
66
|
} catch (e) {
|
|
68
67
|
err = e
|
|
69
|
-
if
|
|
70
|
-
|
|
71
|
-
await tx.rollback(e).catch(() => {})
|
|
72
|
-
}
|
|
68
|
+
// REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
|
|
69
|
+
await tx.rollback(e).catch(() => {})
|
|
73
70
|
} finally {
|
|
74
71
|
if (err) next(err)
|
|
75
72
|
else {
|
|
@@ -4,7 +4,7 @@ const { INSERT, SELECT, UPDATE, DELETE } = cds.ql
|
|
|
4
4
|
|
|
5
5
|
const { createCqlString } = require('./utils')
|
|
6
6
|
const { getColumns } = require('../../../services/utils/columns')
|
|
7
|
-
const { getMaxPageSize } = require('../../../../common/utils/page')
|
|
7
|
+
const { getMaxPageSize, getDefaultPageSize } = require('../../../../common/utils/page')
|
|
8
8
|
|
|
9
9
|
const _readToCQN = ({ isCollection, segments }, target, restReq) => {
|
|
10
10
|
const key = Object.keys(target.keys)[0]
|
|
@@ -20,7 +20,7 @@ const _readToCQN = ({ isCollection, segments }, target, restReq) => {
|
|
|
20
20
|
if (!isCollection) cqn.SELECT.one = true
|
|
21
21
|
|
|
22
22
|
if (isCollection && (restReq.query.$top || restReq.query.$skip)) {
|
|
23
|
-
const top = restReq.query.$top ? parseInt(restReq.query.$top) :
|
|
23
|
+
const top = restReq.query.$top ? parseInt(restReq.query.$top) : getDefaultPageSize(target)
|
|
24
24
|
cqn.limit(Math.min(top, getMaxPageSize(target)), parseInt(restReq.query.$skip) || 0)
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -48,7 +48,7 @@ const _parseEntityOrOperation = part => {
|
|
|
48
48
|
|
|
49
49
|
const _findEntityOrCustomOperation = (customOperation, service, name) => {
|
|
50
50
|
const thing =
|
|
51
|
-
service.entities[name] || service.operations[name] || findCsnTargetFor(name, service.model, service.
|
|
51
|
+
service.entities[name] || service.operations[name] || findCsnTargetFor(name, service.model, service.namespace)
|
|
52
52
|
if (!thing) {
|
|
53
53
|
throw getError(404, 'INVALID_RESOURCE', [name])
|
|
54
54
|
}
|
|
@@ -152,13 +152,15 @@ const _parseCreateOrRead2 = (event, parsed, service, parts) => {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
parsed.segments.push(
|
|
155
|
-
_validateEntity(service.entities[parts[0]] || findCsnTargetFor(parts[0], service.model, service.
|
|
155
|
+
_validateEntity(service.entities[parts[0]] || findCsnTargetFor(parts[0], service.model, service.namespace)),
|
|
156
156
|
parts[1]
|
|
157
157
|
)
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
const _parseCreateOrRead3 = (service, parts, customOperation, parsed) => {
|
|
161
|
-
const entity = _validateEntity(
|
|
161
|
+
const entity = _validateEntity(
|
|
162
|
+
service.entities[parts[0]] || findCsnTargetFor(parts[0], service.model, service.namespace)
|
|
163
|
+
)
|
|
162
164
|
const key = parts[1]
|
|
163
165
|
const { name, params } = _parseEntityOrOperation(parts[2])
|
|
164
166
|
const operation = _validateCustomOperation(entity, name, customOperation)
|
|
@@ -209,7 +211,9 @@ const parseUpdateOrDeleteUrl = (event, service, req) => {
|
|
|
209
211
|
throw getError(400, 'INVALID_PUT')
|
|
210
212
|
}
|
|
211
213
|
|
|
212
|
-
const entity = _validateEntity(
|
|
214
|
+
const entity = _validateEntity(
|
|
215
|
+
service.entities[parts[0]] || findCsnTargetFor(parts[0], service.model, service.namespace)
|
|
216
|
+
)
|
|
213
217
|
const segments = [entity]
|
|
214
218
|
|
|
215
219
|
if (parts[1]) {
|
|
@@ -22,12 +22,6 @@ class ApplicationService extends cds.Service {
|
|
|
22
22
|
this._calculateDiff = this._differ.calculate
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
set model(csn) {
|
|
26
|
-
const m = csn && 'definitions' in csn ? cds.linked(cds.compile.for.odata(csn)) : csn
|
|
27
|
-
cds.alpha_localized(m) // with compiler v2 we always need to localized the csn
|
|
28
|
-
super.model = m
|
|
29
|
-
}
|
|
30
|
-
|
|
31
25
|
init() {
|
|
32
26
|
/*
|
|
33
27
|
* .before handlers (all with _initial === true)
|
|
@@ -116,11 +116,15 @@ const getSearchableColumns = entity => {
|
|
|
116
116
|
/**
|
|
117
117
|
* @returns {import('../../../types/api').ColumnRefs}
|
|
118
118
|
*/
|
|
119
|
-
const computeColumnsToBeSearched = (cqn, entity = { _searchableColumns: [] }) => {
|
|
119
|
+
const computeColumnsToBeSearched = (cqn, entity = { _searchableColumns: [] }, alias) => {
|
|
120
120
|
// if there is a group by clause, only columns in it may be searched
|
|
121
121
|
let toBeSearched = [...entity._searchableColumns]
|
|
122
122
|
if (cqn.SELECT.groupBy) toBeSearched = toBeSearched.filter(tbs => cqn.SELECT.groupBy.some(gb => gb.ref[0] === tbs))
|
|
123
|
-
toBeSearched = toBeSearched.map(c =>
|
|
123
|
+
toBeSearched = toBeSearched.map(c => {
|
|
124
|
+
const col = { ref: [c] }
|
|
125
|
+
if (alias) col.ref.unshift(alias)
|
|
126
|
+
return col
|
|
127
|
+
})
|
|
124
128
|
|
|
125
129
|
// add aggregations
|
|
126
130
|
cqn.SELECT.columns &&
|
|
@@ -141,7 +145,10 @@ const computeColumnsToBeSearched = (cqn, entity = { _searchableColumns: [] }) =>
|
|
|
141
145
|
if (columnRef) {
|
|
142
146
|
const columnName = columnRef[columnRef.length - 1]
|
|
143
147
|
const csnColumn = entity.elements[columnName]
|
|
144
|
-
if (
|
|
148
|
+
if (csnColumn) return
|
|
149
|
+
const col = { ref: [columnName] }
|
|
150
|
+
if (alias) col.ref.unshift(alias)
|
|
151
|
+
toBeSearched.push(col)
|
|
145
152
|
}
|
|
146
153
|
})
|
|
147
154
|
|
|
@@ -131,14 +131,11 @@ const _addToBeDeletedEntriesToResult = (results, entity, keys, newValues, oldVal
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
const _normalizeToArray = value => (Array.isArray(value) ? value : [value])
|
|
134
|
+
const _normalizeToArray = value => (Array.isArray(value) ? value : value === null ? [] : [value])
|
|
135
135
|
|
|
136
136
|
const _addKeysToEntryIfNotExists = (keys, newEntry) => {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
newEntry[key] = undefined
|
|
140
|
-
}
|
|
141
|
-
}
|
|
137
|
+
if (!newEntry) return
|
|
138
|
+
for (const key of keys) if (!(key in newEntry)) newEntry[key] = undefined
|
|
142
139
|
}
|
|
143
140
|
|
|
144
141
|
const _isUnManaged = element => {
|
|
@@ -268,7 +265,7 @@ const compareJson = (newValue, oldValue, entity) => {
|
|
|
268
265
|
return Array.isArray(newValue) ? result : result[0]
|
|
269
266
|
}
|
|
270
267
|
|
|
271
|
-
const _isObject = item => item && typeof item === 'object' && !Array.isArray(item)
|
|
268
|
+
const _isObject = item => item && typeof item === 'object' && !Array.isArray(item) && !Buffer.isBuffer(item)
|
|
272
269
|
|
|
273
270
|
const _mergeArrays = (entity, oldValue, newValue) => {
|
|
274
271
|
const merged = []
|
|
@@ -58,10 +58,13 @@ module.exports = class {
|
|
|
58
58
|
|
|
59
59
|
async _diffUpdate(req, providedData) {
|
|
60
60
|
if (cds.db) {
|
|
61
|
+
// REVISIT: remove try catch with cds^6
|
|
61
62
|
try {
|
|
62
63
|
await this._addPartialPersistentState(req)
|
|
63
64
|
} catch (e) {
|
|
64
|
-
|
|
65
|
+
// NOTE: unofficial compat flag!
|
|
66
|
+
if (cds.env.features.throw_diff_error !== false) throw e
|
|
67
|
+
else LOG._error && LOG.error('Unable to calculate diff due to error: ' + e.message, e)
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
const newQuery = cqn2cqn4sql(req.query, this._srv.model)
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
const cds = require('../../../cds')
|
|
2
|
-
|
|
3
2
|
const { SELECT } = cds.ql
|
|
4
3
|
|
|
5
|
-
const {
|
|
6
|
-
const { processDeep, processDeepAsync } = require('../../util/dataProcessUtils')
|
|
4
|
+
const { processDeep } = require('../../util/dataProcessUtils')
|
|
7
5
|
|
|
8
6
|
const { DRAFT_COLUMNS } = require('../../../common/constants/draft')
|
|
9
7
|
|
|
@@ -145,43 +143,6 @@ const flattenDeepToOneAssociations = (req, csn) => {
|
|
|
145
143
|
)
|
|
146
144
|
}
|
|
147
145
|
|
|
148
|
-
const checkIntegrityWrapper = (req, csn, run) => async (data, entity) => {
|
|
149
|
-
const errors = await checkReferenceIntegrity(entity, data, req, csn, run)
|
|
150
|
-
if (errors && errors.length !== 0) {
|
|
151
|
-
for (const err of errors) {
|
|
152
|
-
req.error(err)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const _isUncheckableInsert = query => {
|
|
158
|
-
return query.INSERT && (query.INSERT.rows || query.INSERT.values || query.INSERT.as)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// REVISIT: lower to db layer, where it's used
|
|
162
|
-
const checkIntegrityUtil = async (req, csn, run) => {
|
|
163
|
-
if (!run) return
|
|
164
|
-
|
|
165
|
-
// REVISIT
|
|
166
|
-
if (typeof req.query === 'string' || req.target._unresolved) return
|
|
167
|
-
|
|
168
|
-
// FIXME: doesn't work for uncheckable inserts
|
|
169
|
-
if (_isUncheckableInsert(req.query)) return
|
|
170
|
-
|
|
171
|
-
// REVISIT: integrity check needs context.data
|
|
172
|
-
if (Object.keys(req.data).length === 0) {
|
|
173
|
-
// REVISIT: We may need to double-check re req.data being undefined or empty
|
|
174
|
-
if (req.query.DELETE) {
|
|
175
|
-
req.data = req._beforeDeleteData || {}
|
|
176
|
-
} else if (req.context && req.context.data && Object.keys(req.context.data).length > 0) {
|
|
177
|
-
req.data = req.context.data
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (Object.keys(req.data).length === 0) return
|
|
181
|
-
|
|
182
|
-
await processDeepAsync(checkIntegrityWrapper(req, csn, run), req.data, req.target, false, true)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
146
|
/*
|
|
186
147
|
* merge CQNs
|
|
187
148
|
*/
|
|
@@ -235,6 +196,5 @@ const getDeepSelect = req => {
|
|
|
235
196
|
module.exports = {
|
|
236
197
|
getDeepSelect,
|
|
237
198
|
allKeysAreProvided,
|
|
238
|
-
checkIntegrityUtil,
|
|
239
199
|
flattenDeepToOneAssociations
|
|
240
200
|
}
|