@sap/cds 5.7.5 → 5.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -0
- package/app/fiori/routes.js +1 -1
- package/bin/deploy/to-hana/cfUtil.js +251 -138
- package/bin/deploy/to-hana/gitUtil.js +55 -0
- package/bin/deploy/to-hana/hana.js +92 -93
- package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
- package/bin/deploy/to-hana/index.js +14 -13
- package/bin/mtx/in-cds.js +1 -0
- package/bin/serve.js +1 -1
- package/bin/version.js +1 -0
- package/lib/compile/cdsc.js +0 -6
- package/lib/compile/resolve.js +1 -1
- package/lib/compile/to/srvinfo.js +1 -1
- package/lib/core/classes.js +21 -1
- package/lib/env/index.js +3 -2
- package/lib/env/requires.js +4 -0
- package/lib/i18n/localize.js +5 -8
- package/lib/index.js +1 -0
- package/lib/log/errors.js +1 -1
- package/lib/ql/SELECT.js +2 -2
- package/lib/req/cds-context.js +1 -1
- package/lib/req/context.js +1 -1
- package/lib/serve/Transaction.js +9 -5
- package/lib/serve/index.js +13 -21
- package/lib/utils/tests.js +90 -66
- package/libx/_runtime/audit/generic/personal/modification.js +0 -8
- package/libx/_runtime/auth/index.js +7 -6
- package/libx/_runtime/auth/strategies/dwc.js +43 -0
- package/libx/_runtime/auth/utils.js +24 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -32
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +60 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +7 -5
- package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
- package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
- package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
- package/libx/_runtime/common/aspects/Association.js +16 -0
- package/libx/_runtime/common/composition/data.js +28 -37
- package/libx/_runtime/common/composition/delete.js +107 -58
- package/libx/_runtime/common/composition/index.js +2 -1
- package/libx/_runtime/common/composition/insert.js +13 -13
- package/libx/_runtime/common/composition/update.js +39 -34
- package/libx/_runtime/common/error/frontend.js +17 -2
- package/libx/_runtime/common/generic/auth.js +20 -85
- package/libx/_runtime/common/generic/crud.js +22 -1
- package/libx/_runtime/common/i18n/messages.properties +2 -1
- package/libx/_runtime/common/utils/cqn.js +2 -6
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
- package/libx/_runtime/common/utils/csn.js +14 -3
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -1
- package/libx/_runtime/common/utils/keys.js +2 -1
- package/libx/_runtime/common/utils/path.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +12 -4
- package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
- package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
- package/libx/_runtime/common/utils/vcap.js +27 -10
- package/libx/_runtime/db/data-conversion/post-processing.js +20 -13
- package/libx/_runtime/db/expand/expand-v2.js +21 -12
- package/libx/_runtime/db/expand/expandCQNToJoin.js +8 -6
- package/libx/_runtime/db/expand/index.js +3 -0
- package/libx/_runtime/db/generic/create.js +0 -10
- package/libx/_runtime/db/generic/index.js +3 -0
- package/libx/_runtime/db/generic/read.js +2 -24
- package/libx/_runtime/db/generic/rewrite.js +1 -3
- package/libx/_runtime/db/generic/update.js +1 -1
- package/libx/_runtime/db/query/delete.js +10 -4
- package/libx/_runtime/db/query/insert.js +3 -3
- package/libx/_runtime/db/query/read.js +4 -1
- package/libx/_runtime/db/query/update.js +5 -5
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
- package/libx/_runtime/db/sql-builder/index.js +3 -0
- package/libx/_runtime/db/utils/columns.js +5 -2
- package/libx/_runtime/db/utils/deep.js +6 -8
- package/libx/_runtime/db/utils/generateAliases.js +56 -6
- package/libx/_runtime/fiori/generic/before.js +73 -49
- package/libx/_runtime/fiori/generic/edit.js +14 -18
- package/libx/_runtime/fiori/generic/patch.js +8 -11
- package/libx/_runtime/fiori/generic/read.js +19 -16
- package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/conversion.js +10 -0
- package/libx/_runtime/hana/execute.js +33 -16
- package/libx/_runtime/hana/search.js +3 -3
- package/libx/_runtime/hana/search2cqn4sql.js +22 -21
- package/libx/_runtime/hana/searchToContains.js +1 -1
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
- package/libx/_runtime/messaging/file-based.js +3 -1
- package/libx/_runtime/messaging/service.js +4 -1
- package/libx/_runtime/remote/utils/client.js +33 -20
- package/libx/_runtime/remote/utils/data.js +52 -11
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/conversion.js +10 -0
- package/libx/_runtime/types/api.js +2 -2
- package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
- package/libx/odata/afterburner.js +29 -6
- package/libx/odata/cqn2odata.js +9 -0
- package/libx/odata/grammar.pegjs +49 -21
- package/libx/odata/index.js +2 -2
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +2 -2
- package/libx/rest/RestAdapter.js +29 -1
- package/libx/rest/middleware/auth.js +1 -3
- package/libx/rest/middleware/parse.js +1 -0
- package/package.json +1 -1
- package/server.js +1 -1
- package/bin/deploy/to-hana/logger.js +0 -27
- package/bin/deploy/to-hana/runCommand.js +0 -113
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
- package/libx/_runtime/common/utils/auth.js +0 -16
|
@@ -5,13 +5,15 @@ const {
|
|
|
5
5
|
Components: { DATA_CREATE_HANDLER }
|
|
6
6
|
} = require('../okra/odata-server')
|
|
7
7
|
|
|
8
|
-
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
9
8
|
const { validateResourcePath } = require('../utils/request')
|
|
10
9
|
const { isReturnMinimal } = require('../utils/handlerUtils')
|
|
11
10
|
const readAfterWrite = require('../utils/readAfterWrite')
|
|
12
11
|
const { toODataResult, postProcess, postProcessMinimal } = require('../utils/result')
|
|
12
|
+
|
|
13
13
|
const { mergeJson } = require('../../../services/utils/compareJson')
|
|
14
14
|
|
|
15
|
+
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
16
|
+
|
|
15
17
|
/**
|
|
16
18
|
* The handler that will be registered with odata-v4.
|
|
17
19
|
*
|
|
@@ -21,6 +23,7 @@ const { mergeJson } = require('../../../services/utils/compareJson')
|
|
|
21
23
|
const create = service => {
|
|
22
24
|
return async (odataReq, odataRes, next) => {
|
|
23
25
|
let req
|
|
26
|
+
|
|
24
27
|
try {
|
|
25
28
|
validateResourcePath(odataReq, service)
|
|
26
29
|
req = new ODataRequest(DATA_CREATE_HANDLER, service, odataReq, odataRes)
|
|
@@ -33,9 +36,12 @@ const create = service => {
|
|
|
33
36
|
cds.context = tx
|
|
34
37
|
|
|
35
38
|
let result, err
|
|
39
|
+
|
|
36
40
|
try {
|
|
37
41
|
result = await tx.dispatch(req)
|
|
38
42
|
|
|
43
|
+
// REVISIT readAfterWrite -> commit -> postProcessing
|
|
44
|
+
|
|
39
45
|
if (isReturnMinimal(req)) {
|
|
40
46
|
postProcessMinimal(req, result)
|
|
41
47
|
} else {
|
|
@@ -60,12 +66,13 @@ const create = service => {
|
|
|
60
66
|
}
|
|
61
67
|
} catch (e) {
|
|
62
68
|
err = e
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
await tx.rollback(e).catch(() => {})
|
|
66
|
-
} else if (changeset) {
|
|
69
|
+
|
|
70
|
+
if (changeset) {
|
|
67
71
|
// for passing into rollback
|
|
68
72
|
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
73
|
+
} else {
|
|
74
|
+
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
75
|
+
await tx.rollback(e).catch(() => {})
|
|
69
76
|
}
|
|
70
77
|
} finally {
|
|
71
78
|
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req._.req))
|
|
@@ -17,6 +17,7 @@ const { validateResourcePath } = require('../utils/request')
|
|
|
17
17
|
const del = service => {
|
|
18
18
|
return async (odataReq, odataRes, next) => {
|
|
19
19
|
let req
|
|
20
|
+
|
|
20
21
|
try {
|
|
21
22
|
validateResourcePath(odataReq, service)
|
|
22
23
|
req = new ODataRequest(DATA_DELETE_HANDLER, service, odataReq, odataRes)
|
|
@@ -29,6 +30,7 @@ const del = service => {
|
|
|
29
30
|
cds.context = tx
|
|
30
31
|
|
|
31
32
|
let err
|
|
33
|
+
|
|
32
34
|
try {
|
|
33
35
|
await tx.dispatch(req)
|
|
34
36
|
const result = null
|
|
@@ -41,12 +43,13 @@ const del = service => {
|
|
|
41
43
|
}
|
|
42
44
|
} catch (e) {
|
|
43
45
|
err = e
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
await tx.rollback(e).catch(() => {})
|
|
47
|
-
} else if (changeset) {
|
|
46
|
+
|
|
47
|
+
if (changeset) {
|
|
48
48
|
// for passing into rollback
|
|
49
49
|
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
50
|
+
} else {
|
|
51
|
+
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
52
|
+
await tx.rollback(e).catch(() => {})
|
|
50
53
|
}
|
|
51
54
|
} finally {
|
|
52
55
|
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req._.req))
|
|
@@ -49,6 +49,19 @@ const _betterOkraError = err => {
|
|
|
49
49
|
if (err.name === 'DeserializationError') {
|
|
50
50
|
err.message = err.message.replace('Error while deserializing payload.', 'Deserialization Error:')
|
|
51
51
|
err.message = err.message.replace(' An error occurred during deserialization of the entity.', '')
|
|
52
|
+
|
|
53
|
+
// add parsed data to error for req.data
|
|
54
|
+
let e = err
|
|
55
|
+
let depth = 0
|
|
56
|
+
while (e._rootCause && depth < 10) {
|
|
57
|
+
const rootCause = e._rootCause
|
|
58
|
+
if (rootCause._data) {
|
|
59
|
+
err._data = rootCause._data
|
|
60
|
+
break
|
|
61
|
+
}
|
|
62
|
+
e = rootCause
|
|
63
|
+
depth++
|
|
64
|
+
}
|
|
52
65
|
} else if (err.name === 'SerializationError') {
|
|
53
66
|
err.message = err.message.replace('An error occurred during serialization of the entity.', 'Serialization Error:')
|
|
54
67
|
err.message = err.message.replace(
|
|
@@ -68,10 +81,16 @@ const _betterOkraError = err => {
|
|
|
68
81
|
* @returns {Function}
|
|
69
82
|
*/
|
|
70
83
|
const getErrorHandler = (crashOnError = true, srv) => {
|
|
71
|
-
return (odataReq, odataRes, next, err) => {
|
|
84
|
+
return async (odataReq, odataRes, next, err) => {
|
|
72
85
|
// REVISIT: crashOnError
|
|
73
86
|
if (isStandardError(err) && crashOnError) {
|
|
74
|
-
|
|
87
|
+
// first rollback in case of atomicity groups
|
|
88
|
+
const changeset = odataReq.getAtomicityGroupId()
|
|
89
|
+
if (changeset) {
|
|
90
|
+
const tx = odataReq.getBatchApplicationData().txs[changeset]
|
|
91
|
+
await tx.rollback().catch(() => {})
|
|
92
|
+
}
|
|
93
|
+
|
|
75
94
|
throw err
|
|
76
95
|
}
|
|
77
96
|
|
|
@@ -84,9 +103,11 @@ const getErrorHandler = (crashOnError = true, srv) => {
|
|
|
84
103
|
req = odataReq.getIncomingRequest()
|
|
85
104
|
}
|
|
86
105
|
|
|
87
|
-
if (
|
|
106
|
+
if (typeof err.getRootCause === 'function') {
|
|
88
107
|
// > an OKRA error
|
|
89
108
|
err = _betterOkraError(err)
|
|
109
|
+
// add req.data for use in custom error handler in case of okra deserialization error
|
|
110
|
+
if ('_data' in err && !('data' in req)) req.data = err._data
|
|
90
111
|
}
|
|
91
112
|
|
|
92
113
|
// invoke srv.on('error', function (err, req) { ... }) here in special situations
|
|
@@ -14,12 +14,13 @@ const {
|
|
|
14
14
|
}
|
|
15
15
|
} = require('../okra/odata-server')
|
|
16
16
|
|
|
17
|
-
const { isCustomOperation
|
|
17
|
+
const { isCustomOperation } = require('../utils/request')
|
|
18
18
|
const { actionAndFunctionQueries, getActionOrFunctionReturnType } = require('../utils/handlerUtils')
|
|
19
19
|
const { validateResourcePath } = require('../utils/request')
|
|
20
20
|
const { toODataResult, postProcess } = require('../utils/result')
|
|
21
21
|
const { isStreaming, getStreamProperties } = require('../utils/stream')
|
|
22
22
|
const { resolveStructuredName } = require('../utils/handlerUtils')
|
|
23
|
+
|
|
23
24
|
const getError = require('../../../../common/error')
|
|
24
25
|
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
25
26
|
|
|
@@ -46,7 +47,7 @@ const _selectOrExpandInQueryOptions = queryOptions => {
|
|
|
46
47
|
* @private
|
|
47
48
|
*/
|
|
48
49
|
const _invokeFunction = async (tx, req, odataReq) => {
|
|
49
|
-
|
|
50
|
+
let result = await tx.dispatch(req)
|
|
50
51
|
|
|
51
52
|
const functionReturnType = getActionOrFunctionReturnType(
|
|
52
53
|
odataReq.getUriInfo().getPathSegments(),
|
|
@@ -59,7 +60,7 @@ const _invokeFunction = async (tx, req, odataReq) => {
|
|
|
59
60
|
functionReturnType.kind === 'entity' &&
|
|
60
61
|
_selectOrExpandInQueryOptions(odataReq.getQueryOptions())
|
|
61
62
|
) {
|
|
62
|
-
await actionAndFunctionQueries(req, odataReq, result, tx, functionReturnType)
|
|
63
|
+
result = await actionAndFunctionQueries(req, odataReq, result, tx, functionReturnType)
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
return toODataResult(result, req)
|
|
@@ -142,9 +143,9 @@ const _isCollection = segments => {
|
|
|
142
143
|
* @returns {boolean}
|
|
143
144
|
* @private
|
|
144
145
|
*/
|
|
145
|
-
const _isNavigationToOne = segments =>
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
const _isNavigationToOne = segments =>
|
|
147
|
+
segments[segments.length - 1].getKind() === NAVIGATION_TO_ONE &&
|
|
148
|
+
segments[segments.length - 1].getKeyPredicates().length === 0
|
|
148
149
|
|
|
149
150
|
const _hasRedirectProperty = elements => {
|
|
150
151
|
return Object.values(elements).some(val => {
|
|
@@ -229,22 +230,23 @@ const _readEntityOrProperty = async (tx, req, segments) => {
|
|
|
229
230
|
|
|
230
231
|
if (propertyElement === null) {
|
|
231
232
|
_transformRedirectProperties(req, result)
|
|
233
|
+
|
|
232
234
|
return toODataResult(result[0])
|
|
233
235
|
}
|
|
234
236
|
|
|
235
|
-
|
|
236
|
-
const res = _getResult(name, result[0])
|
|
237
|
+
result = _getResult(resolveStructuredName(segments, segments.length - 2), result[0])
|
|
237
238
|
|
|
238
|
-
|
|
239
|
-
|
|
239
|
+
if (req.target._etag) result.$etag = result[req.target._etag]
|
|
240
|
+
|
|
241
|
+
const odataResult = toODataResult(result, req)
|
|
240
242
|
|
|
241
243
|
// property is read via a to one association and last segment is not $value
|
|
242
244
|
if (index !== 2 && segments.length > 2 && segments[segments.length - 2].getKind() === NAVIGATION_TO_ONE) {
|
|
243
245
|
// find keys in result
|
|
244
|
-
const keys = Object.keys(result
|
|
246
|
+
const keys = Object.keys(result)
|
|
245
247
|
.filter(k => segments[segments.length - index - 1].getEdmType().getOwnKeyPropertyRefs().has(k))
|
|
246
248
|
.reduce((res, curr) => {
|
|
247
|
-
res[curr] = result[
|
|
249
|
+
res[curr] = result[curr]
|
|
248
250
|
return res
|
|
249
251
|
}, {})
|
|
250
252
|
|
|
@@ -266,20 +268,18 @@ const _readEntityOrProperty = async (tx, req, segments) => {
|
|
|
266
268
|
*/
|
|
267
269
|
const _readCollection = async (tx, req, odataReq) => {
|
|
268
270
|
const result = (await tx.dispatch(req)) || []
|
|
269
|
-
const odataResult = toODataResult(result, req)
|
|
270
271
|
|
|
271
|
-
|
|
272
|
-
if (!odataResult['*@odata.count'] && req.query.SELECT.count) {
|
|
273
|
-
odataResult['*@odata.count'] = 0
|
|
274
|
-
}
|
|
272
|
+
if (req.query.SELECT.count && !('$count' in result)) result.$count = 0
|
|
275
273
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
274
|
+
const limit = req.query.SELECT.limit && req.query.SELECT.limit.rows && req.query.SELECT.limit.rows.val
|
|
275
|
+
const top = odataReq.getUriInfo().getQueryOption(QueryOptions.TOP)
|
|
276
|
+
if (limit && limit === result.length && limit !== top && !('$nextLink' in result)) {
|
|
277
|
+
const token = odataReq.getUriInfo().getQueryOption(QueryOptions.SKIPTOKEN)
|
|
278
|
+
result.$nextLink = (token ? parseInt(token) : 0) + limit
|
|
281
279
|
}
|
|
282
280
|
|
|
281
|
+
const odataResult = toODataResult(result, req)
|
|
282
|
+
|
|
283
283
|
_transformRedirectProperties(req, result)
|
|
284
284
|
|
|
285
285
|
return odataResult
|
|
@@ -298,7 +298,7 @@ const _readCollection = async (tx, req, odataReq) => {
|
|
|
298
298
|
const _readStream = async (tx, req) => {
|
|
299
299
|
req.query._streaming = true
|
|
300
300
|
|
|
301
|
-
const { contentType,
|
|
301
|
+
const { contentType, contentDispositionFilename, contentDispositionType } = await getStreamProperties(req, tx.model)
|
|
302
302
|
|
|
303
303
|
let result = await tx.dispatch(req)
|
|
304
304
|
|
|
@@ -312,14 +312,14 @@ const _readStream = async (tx, req) => {
|
|
|
312
312
|
|
|
313
313
|
if (result[0] === null) return null
|
|
314
314
|
|
|
315
|
-
|
|
316
|
-
const
|
|
315
|
+
result = result[0]
|
|
316
|
+
const { value: readable } = result
|
|
317
317
|
|
|
318
|
-
if (
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
//
|
|
322
|
-
|
|
318
|
+
if (readable) {
|
|
319
|
+
readable.on('error', () => {
|
|
320
|
+
readable.removeAllListeners('error')
|
|
321
|
+
// readable.destroy() does not end stream in node 10 and 12
|
|
322
|
+
readable.push(null)
|
|
323
323
|
})
|
|
324
324
|
}
|
|
325
325
|
|
|
@@ -336,11 +336,13 @@ const _readStream = async (tx, req) => {
|
|
|
336
336
|
req.reject(406, `Content type "${contentType}" not listed in accept header "${headers.accept}".`)
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
-
if (contentType)
|
|
340
|
-
if (
|
|
341
|
-
|
|
339
|
+
if (contentType) result.$mediaContentType = contentType
|
|
340
|
+
if (contentDispositionFilename) {
|
|
341
|
+
result.$mediaContentDispositionFilename = contentDispositionFilename
|
|
342
|
+
if (contentDispositionType) result.$mediaContentDispositionType = contentDispositionType
|
|
343
|
+
}
|
|
342
344
|
|
|
343
|
-
return
|
|
345
|
+
return toODataResult(result, req)
|
|
344
346
|
}
|
|
345
347
|
|
|
346
348
|
const _readSingleton = async (tx, req, lastSegment) => {
|
|
@@ -439,6 +441,7 @@ const _removeKeysForParams = result => {
|
|
|
439
441
|
const read = service => {
|
|
440
442
|
return async (odataReq, odataRes, next) => {
|
|
441
443
|
let req
|
|
444
|
+
|
|
442
445
|
try {
|
|
443
446
|
validateResourcePath(odataReq, service)
|
|
444
447
|
req = new ODataRequest(DATA_READ_HANDLER, service, odataReq, odataRes)
|
|
@@ -452,6 +455,7 @@ const read = service => {
|
|
|
452
455
|
|
|
453
456
|
let result, err
|
|
454
457
|
let additional = {}
|
|
458
|
+
|
|
455
459
|
try {
|
|
456
460
|
// REVISIT: refactor _readAndTransform
|
|
457
461
|
result = await _readAndTransform(tx, req, odataReq)
|
|
@@ -471,12 +475,13 @@ const read = service => {
|
|
|
471
475
|
}
|
|
472
476
|
} catch (e) {
|
|
473
477
|
err = e
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
await tx.rollback(e).catch(() => {})
|
|
477
|
-
} else if (changeset) {
|
|
478
|
+
|
|
479
|
+
if (changeset) {
|
|
478
480
|
// for passing into rollback
|
|
479
481
|
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
482
|
+
} else {
|
|
483
|
+
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
484
|
+
await tx.rollback(e).catch(() => {})
|
|
480
485
|
}
|
|
481
486
|
} finally {
|
|
482
487
|
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req._.req))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
2
|
|
|
3
|
-
const { UNAUTHORIZED, FORBIDDEN, getRequiresAsArray } = require('../../../../
|
|
3
|
+
const { UNAUTHORIZED, FORBIDDEN, getRequiresAsArray } = require('../../../../auth/utils')
|
|
4
4
|
|
|
5
5
|
module.exports = srv => {
|
|
6
6
|
const requires = getRequiresAsArray(srv.definition)
|
|
@@ -6,14 +6,16 @@ const {
|
|
|
6
6
|
Components: { DATA_UPDATE_HANDLER, DATA_CREATE_HANDLER }
|
|
7
7
|
} = require('../okra/odata-server')
|
|
8
8
|
|
|
9
|
-
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
10
9
|
const { validateResourcePath } = require('../utils/request')
|
|
11
10
|
const { isReturnMinimal } = require('../utils/handlerUtils')
|
|
12
11
|
const readAfterWrite = require('../utils/readAfterWrite')
|
|
13
12
|
const { toODataResult, postProcess, postProcessMinimal } = require('../utils/result')
|
|
14
13
|
const { hasOmitValuesPreference } = require('../utils/omitValues')
|
|
14
|
+
|
|
15
15
|
const { mergeJson } = require('../../../services/utils/compareJson')
|
|
16
16
|
|
|
17
|
+
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
18
|
+
|
|
17
19
|
const _isUpsertAllowed = target => {
|
|
18
20
|
return !(cds.env.runtime && cds.env.runtime.allow_upsert === false) && !(target && target._isDraftEnabled)
|
|
19
21
|
}
|
|
@@ -126,6 +128,7 @@ const _shouldReadPreviousResult = req =>
|
|
|
126
128
|
const update = service => {
|
|
127
129
|
return async (odataReq, odataRes, next) => {
|
|
128
130
|
let req
|
|
131
|
+
|
|
129
132
|
try {
|
|
130
133
|
validateResourcePath(odataReq, service)
|
|
131
134
|
req = new ODataRequest(DATA_UPDATE_HANDLER, service, odataReq, odataRes)
|
|
@@ -141,8 +144,10 @@ const update = service => {
|
|
|
141
144
|
const primitive = odataReq.getUriInfo().getLastSegment().getKind() === 'PRIMITIVE.PROPERTY'
|
|
142
145
|
|
|
143
146
|
let result, err
|
|
147
|
+
|
|
144
148
|
try {
|
|
145
149
|
let previousResult
|
|
150
|
+
|
|
146
151
|
if (_shouldReadPreviousResult(req)) {
|
|
147
152
|
previousResult = await _readAfterWriteAndVirtuals(req, service, result)
|
|
148
153
|
}
|
|
@@ -173,12 +178,13 @@ const update = service => {
|
|
|
173
178
|
}
|
|
174
179
|
} catch (e) {
|
|
175
180
|
err = e
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
await tx.rollback(e).catch(() => {})
|
|
179
|
-
} else if (changeset) {
|
|
181
|
+
|
|
182
|
+
if (changeset) {
|
|
180
183
|
// for passing into rollback
|
|
181
184
|
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
185
|
+
} else {
|
|
186
|
+
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
187
|
+
await tx.rollback(e).catch(() => {})
|
|
182
188
|
}
|
|
183
189
|
} finally {
|
|
184
190
|
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req._.req))
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
|
|
4
|
-
const { isPathSupported } = require('./
|
|
5
|
-
const { convertUrlPathToCqn } = require('./utils')
|
|
4
|
+
const { convertUrlPathToCqn, isPathSupported } = require('./utils')
|
|
6
5
|
|
|
7
6
|
const {
|
|
8
7
|
BOUND_ACTION,
|
|
@@ -26,7 +26,6 @@ const deleteToCQN = (service, odataReq) => {
|
|
|
26
26
|
if (SUPPORTED_KINDS[segment.getKind()]) {
|
|
27
27
|
return DELETE.from(convertUrlPathToCqn(segments, service))
|
|
28
28
|
}
|
|
29
|
-
|
|
30
29
|
if (segment.getKind() === 'PRIMITIVE.PROPERTY') {
|
|
31
30
|
return UPDATE(convertUrlPathToCqn(segments, service)).data({ [segment.getProperty().getName()]: null })
|
|
32
31
|
}
|
|
@@ -13,7 +13,6 @@ const { getColumns } = require('../../../services/utils/columns')
|
|
|
13
13
|
const { addLimit, isSameArray } = require('./utils')
|
|
14
14
|
const { findCsnTargetFor } = require('../../../../common/utils/csn')
|
|
15
15
|
const getError = require('../../../../common/error')
|
|
16
|
-
|
|
17
16
|
/**
|
|
18
17
|
* Check which element(s) of the entity has been expanded.
|
|
19
18
|
*
|
|
@@ -234,7 +233,7 @@ const expandToCQN = (model, expandItems, type, options) => {
|
|
|
234
233
|
const allElements = []
|
|
235
234
|
const isAll = expandItems.some(item => item.isAll())
|
|
236
235
|
|
|
237
|
-
for (const [name, navigationProperty] of type.getNavigationProperties()) {
|
|
236
|
+
for (const [name, navigationProperty] of type.getNavigationProperties(isAll)) {
|
|
238
237
|
const expandItem = _getExpandItem(isAll, expandItems, name)
|
|
239
238
|
|
|
240
239
|
if (isAll || expandItem) {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
const ExpressionToCQN = require('./ExpressionToCQN')
|
|
2
2
|
const { getFeatureNotSupportedError } = require('../../../util/errors')
|
|
3
3
|
const odata = require('../okra/odata-server')
|
|
4
|
+
const { ResourceKind } = require('../okra/odata-commons/uri/UriResource')
|
|
4
5
|
const {
|
|
5
6
|
QueryOptions: { ORDERBY }
|
|
6
7
|
} = odata
|
|
7
8
|
const ExpressionKind = odata.uri.Expression.ExpressionKind
|
|
9
|
+
const getError = require('../../../../common/error')
|
|
8
10
|
|
|
9
11
|
const _buildNavRef = pathSegment => {
|
|
10
12
|
return pathSegment.getProperty() ? pathSegment.getProperty().getName() : pathSegment.getNavigationProperty().getName()
|
|
@@ -13,6 +15,13 @@ const _buildNavRef = pathSegment => {
|
|
|
13
15
|
const _orderExpression = order => {
|
|
14
16
|
if (order.getExpression().getKind() === ExpressionKind.MEMBER) {
|
|
15
17
|
const ref = []
|
|
18
|
+
const lastSegment = order.getExpression().getPathSegments()[order.getExpression().getPathSegments().length - 1]
|
|
19
|
+
if (
|
|
20
|
+
lastSegment.getKind() === ResourceKind.ANY_EXPRESSION ||
|
|
21
|
+
lastSegment.getKind() === ResourceKind.ALL_EXPRESSION
|
|
22
|
+
) {
|
|
23
|
+
throw getError(501, 'ORDERBY_LAMBDA_UNSUPPORTED')
|
|
24
|
+
}
|
|
16
25
|
for (let i = 0; i < order.getExpression().getPathSegments().length; i++) {
|
|
17
26
|
ref.push(_buildNavRef(order.getExpression().getPathSegments()[i]))
|
|
18
27
|
}
|
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
|
|
4
|
-
const QueryOptions = require('../okra/odata-server').QueryOptions
|
|
5
|
-
const { isNavigation, isPathSupported } = require('./selectHelper')
|
|
6
|
-
const { isViewWithParams, getValidationQuery } = require('./selectHelper')
|
|
7
|
-
const { ensureUnlocalized } = require('../../../../fiori/utils/handler')
|
|
8
4
|
const ExpressionToCQN = require('./ExpressionToCQN')
|
|
9
5
|
const orderByToCQN = require('./orderByToCQN')
|
|
10
6
|
const selectToCQN = require('./selectToCQN')
|
|
11
7
|
const searchToCQN = require('./searchToCQN')
|
|
12
8
|
const applyToCQN = require('./applyToCQN')
|
|
13
|
-
const {
|
|
14
|
-
const { resolveStructuredName } = require('../utils/handlerUtils')
|
|
15
|
-
const { isStreaming } = require('../utils/stream')
|
|
16
|
-
const { convertUrlPathToCqn, getAllKeys } = require('./utils')
|
|
17
|
-
const { getMaxPageSize, getDefaultPageSize } = require('../../../../common/utils/page')
|
|
18
|
-
const { isAsteriskColumn } = require('../../../../common/utils/rewriteAsterisks')
|
|
9
|
+
const { convertUrlPathToCqn, getAllKeys, isPathSupported } = require('./utils')
|
|
19
10
|
|
|
11
|
+
const QueryOptions = require('../okra/odata-server').QueryOptions
|
|
20
12
|
const {
|
|
21
13
|
COUNT,
|
|
22
14
|
ENTITY,
|
|
@@ -28,7 +20,6 @@ const {
|
|
|
28
20
|
VALUE,
|
|
29
21
|
SINGLETON
|
|
30
22
|
} = require('../okra/odata-server').uri.UriResource.ResourceKind
|
|
31
|
-
|
|
32
23
|
const SUPPORTED_SEGMENT_KINDS = {
|
|
33
24
|
[ENTITY]: 1,
|
|
34
25
|
[ENTITY_COLLECTION]: 1,
|
|
@@ -41,9 +32,16 @@ const SUPPORTED_SEGMENT_KINDS = {
|
|
|
41
32
|
[SINGLETON]: 1
|
|
42
33
|
}
|
|
43
34
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
}
|
|
35
|
+
const { _expand } = require('../utils/handlerUtils')
|
|
36
|
+
const { resolveStructuredName } = require('../utils/handlerUtils')
|
|
37
|
+
const { isStreaming } = require('../utils/stream')
|
|
38
|
+
|
|
39
|
+
const { getMaxPageSize, getDefaultPageSize } = require('../../../../common/utils/page')
|
|
40
|
+
const { isAsteriskColumn } = require('../../../../common/utils/rewriteAsterisks')
|
|
41
|
+
|
|
42
|
+
const { ensureUnlocalized } = require('../../../../fiori/utils/handler')
|
|
43
|
+
|
|
44
|
+
const _applyOnlyContainsFilter = apply => Object.keys(apply).length === 1 && apply.filter
|
|
47
45
|
|
|
48
46
|
/**
|
|
49
47
|
*
|
|
@@ -232,14 +230,6 @@ const _checkViewWithParamCall = (isView, segments, kind, name) => {
|
|
|
232
230
|
}
|
|
233
231
|
}
|
|
234
232
|
|
|
235
|
-
const addValidationQueryIfRequired = (segments, isView, cqn, service, kind) => {
|
|
236
|
-
if (isNavigation(segments) && !isView && (kind === NAVIGATION_TO_MANY || kind === NAVIGATION_TO_ONE)) {
|
|
237
|
-
cqn._validationQuery = getValidationQuery(cqn.SELECT.from.ref, service.model)
|
|
238
|
-
cqn._validationQuery.__navToManyWithKeys =
|
|
239
|
-
kind === NAVIGATION_TO_ONE && segments[segments.length - 1].getKeyPredicates().length !== 0
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
233
|
const _addKeysToSelectIfNoStreaming = (entity, select, streaming) => {
|
|
244
234
|
// might also be singleton w/o keys
|
|
245
235
|
if (!streaming && entity.keys) {
|
|
@@ -381,7 +371,7 @@ const readToCQN = (service, target, odataReq) => {
|
|
|
381
371
|
select.push(...expand)
|
|
382
372
|
}
|
|
383
373
|
|
|
384
|
-
const isView =
|
|
374
|
+
const isView = target.params && Object.keys(target.params).length > 0
|
|
385
375
|
const kind = segments[segments.length - 1].getKind()
|
|
386
376
|
const isCollectionOrToMany = _isCollectionOrToMany(kind)
|
|
387
377
|
|
|
@@ -390,13 +380,10 @@ const readToCQN = (service, target, odataReq) => {
|
|
|
390
380
|
|
|
391
381
|
// keep target as input because of localized view
|
|
392
382
|
const cqn = SELECT.from(isView ? _convertUrlPathToViewCqn(segments) : convertUrlPathToCqn(segments, service), select)
|
|
393
|
-
addValidationQueryIfRequired(segments, isView, cqn, service, kind)
|
|
394
383
|
|
|
395
384
|
if (isCollectionOrToMany && queryOptions && queryOptions[QueryOptions.COUNT]) cqn.SELECT.count = true
|
|
396
385
|
|
|
397
|
-
if (Object.keys(apply).length)
|
|
398
|
-
_extendCqnWithApply(cqn, apply, entity)
|
|
399
|
-
}
|
|
386
|
+
if (Object.keys(apply).length) _extendCqnWithApply(cqn, apply, entity)
|
|
400
387
|
|
|
401
388
|
if (isCollectionOrToMany || _isCount(kind)) {
|
|
402
389
|
_filter(service.model, entity, uriInfo, apply, cqn)
|
|
@@ -408,13 +395,13 @@ const readToCQN = (service, target, odataReq) => {
|
|
|
408
395
|
_orderby(uriInfo, cqn)
|
|
409
396
|
}
|
|
410
397
|
|
|
411
|
-
if (!isCollectionOrToMany || entity._isSingleton)
|
|
412
|
-
cqn.SELECT.one = true
|
|
413
|
-
}
|
|
398
|
+
if (!isCollectionOrToMany || entity._isSingleton) cqn.SELECT.one = true
|
|
414
399
|
|
|
415
400
|
_cleanupForApply(apply, cqn)
|
|
401
|
+
|
|
416
402
|
// just like in new parser
|
|
417
403
|
if (cqn.SELECT.columns.length === 1 && cqn.SELECT.columns[0] === '*') delete cqn.SELECT.columns
|
|
404
|
+
|
|
418
405
|
return cqn
|
|
419
406
|
}
|
|
420
407
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { getFeatureNotSupportedError } = require('../../../util/errors')
|
|
2
|
+
|
|
1
3
|
const { findCsnTargetFor } = require('../../../../common/utils/csn')
|
|
2
4
|
|
|
3
5
|
const CDL_KEYWORDS = new Set(require('@sap/cds-compiler/lib/base/keywords').cdl)
|
|
@@ -108,9 +110,18 @@ const getAllKeys = (entity, joinStructured = true) => {
|
|
|
108
110
|
return allKeys
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
const isPathSupported = (supported, pathSegments) => {
|
|
114
|
+
for (const segment of pathSegments) {
|
|
115
|
+
if (!supported[segment.getKind()]) {
|
|
116
|
+
throw getFeatureNotSupportedError(`Request parameter "${segment.getKind()}"`)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
111
121
|
module.exports = {
|
|
112
122
|
addLimit,
|
|
113
123
|
convertUrlPathToCqn,
|
|
114
124
|
isSameArray,
|
|
115
|
-
getAllKeys
|
|
125
|
+
getAllKeys,
|
|
126
|
+
isPathSupported
|
|
116
127
|
}
|
|
@@ -190,10 +190,11 @@ class AbstractEdmStructuredType extends EdmType {
|
|
|
190
190
|
* Returns a map of navigation properties including those from base type.
|
|
191
191
|
* @returns {Map.<string, EdmNavigationProperty>} A Map of navigation properties
|
|
192
192
|
*/
|
|
193
|
-
getNavigationProperties () {
|
|
193
|
+
getNavigationProperties (ignoreSibling) {
|
|
194
194
|
if (!this._navigationProperties) {
|
|
195
195
|
this._navigationProperties = new Map(this.getBaseType() ? this.getBaseType().getNavigationProperties() : [])
|
|
196
196
|
for (const [name, property] of this.getOwnNavigationProperties()) {
|
|
197
|
+
if (ignoreSibling && name === 'SiblingEntity') continue;
|
|
197
198
|
this._navigationProperties.set(name, property)
|
|
198
199
|
}
|
|
199
200
|
}
|
|
@@ -22,12 +22,13 @@ class UriHelper {
|
|
|
22
22
|
return uriLiteral.substring(1, uriLiteral.length - 1).replace(REGEXP_TWO_SINGLE_QUOTES, "'")
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
if (edmType === EdmPrimitiveTypeKind.Binary) {
|
|
26
|
+
// convert the URL-safe base64 encoding to the standard variant (with padding, if necessary)
|
|
27
|
+
let val = uriLiteral.substring(uriLiteral.indexOf("'") + 1, uriLiteral.length - 1).replace(/_/g, '/').replace(/-/g, '+')
|
|
28
|
+
return val.padEnd(val.length + val.length % 4, '=')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (edmType === EdmPrimitiveTypeKind.Duration || edmType.getKind() === EdmTypeKind.ENUM || edmType.getName().startsWith('Geo')) {
|
|
31
32
|
return uriLiteral.substring(uriLiteral.indexOf("'") + 1, uriLiteral.length - 1)
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -19,11 +19,8 @@ const GUID_VALUE_REGEXP = new RegExp(
|
|
|
19
19
|
'^(?:' + HEX_DIGIT + '{8}-' + HEX_DIGIT + '{4}-' + HEX_DIGIT + '{4}-' + HEX_DIGIT + '{4}-' + HEX_DIGIT + '{12})'
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const BASE64B8 = BASE64 + '[AQgw](?:==)?'
|
|
25
|
-
const BINARY = '(?:' + BASE64 + '{4})*(?:' + BASE64B16 + '|' + BASE64B8 + ')?'
|
|
26
|
-
const BINARY_VALUE_REGEXP = new RegExp("^[Bb][Ii][Nn][Aa][Rr][Yy]'" + BINARY + "'")
|
|
22
|
+
// regex for url-safe base64
|
|
23
|
+
const BINARY_VALUE_REGEXP = /^[Bb][Ii][Nn][Aa][Rr][Yy]'(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{4}|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{2}={2}|[A-Za-z0-9-_]{2,3})'/
|
|
27
24
|
|
|
28
25
|
const UNSIGNED_INTEGER_VALUE_REGEXP = new RegExp('^\\d+')
|
|
29
26
|
const INTEGER_VALUE_REGEXP = new RegExp('^[-+]?\\d+')
|