@sap/cds 5.6.4 → 5.7.4
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 +134 -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 +3 -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 +8 -3
- 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 +3 -1
- package/lib/log/index.js +2 -2
- 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 -69
- 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 +29 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +4 -0
- 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/utils/parse-url.js +8 -4
- package/libx/_runtime/cds-services/services/Service.js +1 -7
- package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
- package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -6
- 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 -38
- 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/put.js +4 -10
- package/libx/_runtime/common/generic/sorting.js +12 -30
- package/libx/_runtime/common/i18n/messages.properties +2 -0
- 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 +298 -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/data-conversion/post-processing.js +22 -22
- package/libx/_runtime/db/expand/expand-v2.js +130 -0
- package/libx/_runtime/db/expand/expandCQNToJoin.js +57 -75
- package/libx/_runtime/db/expand/index.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/read.js +12 -8
- 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 +128 -57
- package/libx/_runtime/fiori/uiflex/index.js +1 -1
- package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +3 -3
- 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-utils/options-messaging.js +1 -0
- 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} +19 -15
- package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
- package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +217 -148
- package/libx/odata/index.js +21 -13
- 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
|
@@ -218,14 +218,6 @@ class ODataRequest extends cds.Request {
|
|
|
218
218
|
}
|
|
219
219
|
})
|
|
220
220
|
|
|
221
|
-
if (this._.req.performanceMeasurement) {
|
|
222
|
-
this.performanceMeasurement = this._.req.performanceMeasurement
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (this._.req.dynatrace) {
|
|
226
|
-
this.dynatrace = this._.req.dynatrace
|
|
227
|
-
}
|
|
228
|
-
|
|
229
221
|
/*
|
|
230
222
|
* req.isConcurrentResource
|
|
231
223
|
*/
|
|
@@ -89,7 +89,7 @@ const action = service => {
|
|
|
89
89
|
const tx = changeset ? odataReq.getBatchApplicationData().txs[changeset] : service.tx(req)
|
|
90
90
|
cds.context = tx
|
|
91
91
|
|
|
92
|
-
let result, err
|
|
92
|
+
let result, err
|
|
93
93
|
try {
|
|
94
94
|
result = await tx.dispatch(req)
|
|
95
95
|
|
|
@@ -107,13 +107,12 @@ const action = service => {
|
|
|
107
107
|
// for passing into commit
|
|
108
108
|
odataReq.getBatchApplicationData().results[changeset].push({ result, req })
|
|
109
109
|
} else {
|
|
110
|
-
commit = true
|
|
111
110
|
await tx.commit(result)
|
|
112
111
|
}
|
|
113
112
|
} catch (e) {
|
|
114
113
|
err = e
|
|
115
|
-
if (!changeset
|
|
116
|
-
//
|
|
114
|
+
if (!changeset) {
|
|
115
|
+
// REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
|
|
117
116
|
await tx.rollback(e).catch(() => {})
|
|
118
117
|
} else if (changeset) {
|
|
119
118
|
// for passing into rollback
|
|
@@ -32,11 +32,13 @@ const create = service => {
|
|
|
32
32
|
const tx = changeset ? odataReq.getBatchApplicationData().txs[changeset] : service.tx(req)
|
|
33
33
|
cds.context = tx
|
|
34
34
|
|
|
35
|
-
let result, err
|
|
35
|
+
let result, err
|
|
36
36
|
try {
|
|
37
37
|
result = await tx.dispatch(req)
|
|
38
38
|
|
|
39
|
-
if (
|
|
39
|
+
if (isReturnMinimal(req)) {
|
|
40
|
+
postProcessMinimal(req, result)
|
|
41
|
+
} else {
|
|
40
42
|
// REVISIT: find better solution
|
|
41
43
|
if (req._.readAfterWrite) {
|
|
42
44
|
const dataInDb = await readAfterWrite(req, service)
|
|
@@ -44,15 +46,12 @@ const create = service => {
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
postProcess(req, odataRes, service, result)
|
|
47
|
-
} else {
|
|
48
|
-
postProcessMinimal(req, result)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
if (changeset) {
|
|
52
52
|
// for passing into commit
|
|
53
53
|
odataReq.getBatchApplicationData().results[changeset].push({ result, req })
|
|
54
54
|
} else {
|
|
55
|
-
commit = true
|
|
56
55
|
await tx.commit(result)
|
|
57
56
|
}
|
|
58
57
|
|
|
@@ -61,8 +60,8 @@ const create = service => {
|
|
|
61
60
|
}
|
|
62
61
|
} catch (e) {
|
|
63
62
|
err = e
|
|
64
|
-
if (!changeset
|
|
65
|
-
//
|
|
63
|
+
if (!changeset) {
|
|
64
|
+
// REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
|
|
66
65
|
await tx.rollback(e).catch(() => {})
|
|
67
66
|
} else if (changeset) {
|
|
68
67
|
// for passing into rollback
|
|
@@ -28,7 +28,7 @@ const del = service => {
|
|
|
28
28
|
const tx = changeset ? odataReq.getBatchApplicationData().txs[changeset] : service.tx(req)
|
|
29
29
|
cds.context = tx
|
|
30
30
|
|
|
31
|
-
let err
|
|
31
|
+
let err
|
|
32
32
|
try {
|
|
33
33
|
await tx.dispatch(req)
|
|
34
34
|
const result = null
|
|
@@ -37,13 +37,12 @@ const del = service => {
|
|
|
37
37
|
// for passing into commit
|
|
38
38
|
odataReq.getBatchApplicationData().results[changeset].push({ result, req })
|
|
39
39
|
} else {
|
|
40
|
-
commit = true
|
|
41
40
|
await tx.commit(result)
|
|
42
41
|
}
|
|
43
42
|
} catch (e) {
|
|
44
43
|
err = e
|
|
45
|
-
if (!changeset
|
|
46
|
-
//
|
|
44
|
+
if (!changeset) {
|
|
45
|
+
// REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
|
|
47
46
|
await tx.rollback(e).catch(() => {})
|
|
48
47
|
} else if (changeset) {
|
|
49
48
|
// for passing into rollback
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
|
+
const { isStandardError } = require('../../../../common/error/standardError')
|
|
2
3
|
|
|
3
4
|
const { StatusCodes: HttpStatusCodes } = require('../okra/odata-commons/http/HttpStatusCode')
|
|
4
5
|
|
|
@@ -24,16 +25,6 @@ const ERROR_TO_HTTP_CODE = {
|
|
|
24
25
|
|
|
25
26
|
const { normalizeError } = require('../../../../common/error/frontend')
|
|
26
27
|
|
|
27
|
-
const _isStandardError = err => {
|
|
28
|
-
return (
|
|
29
|
-
err instanceof TypeError ||
|
|
30
|
-
err instanceof ReferenceError ||
|
|
31
|
-
err instanceof SyntaxError ||
|
|
32
|
-
err instanceof RangeError ||
|
|
33
|
-
err instanceof URIError
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
28
|
const _beautifyMessage = msg => (msg.endsWith('.') ? msg : msg + '.')
|
|
38
29
|
|
|
39
30
|
const _buildRootCauseMessage = (message, rootCause) => {
|
|
@@ -79,7 +70,7 @@ const _betterOkraError = err => {
|
|
|
79
70
|
const getErrorHandler = (crashOnError = true, srv) => {
|
|
80
71
|
return (odataReq, odataRes, next, err) => {
|
|
81
72
|
// REVISIT: crashOnError
|
|
82
|
-
if (
|
|
73
|
+
if (isStandardError(err) && crashOnError) {
|
|
83
74
|
err.__crashOnError = true
|
|
84
75
|
throw err
|
|
85
76
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
|
+
const LOG = cds.log('odata')
|
|
2
3
|
|
|
3
4
|
const { toODataResult } = require('../utils/result')
|
|
4
5
|
|
|
6
|
+
const { normalizeError } = require('../../../../common/error/frontend')
|
|
7
|
+
|
|
5
8
|
let _mps
|
|
6
9
|
|
|
7
10
|
const _get4Tenant = async (tenant, locale, service) => {
|
|
@@ -33,13 +36,14 @@ const _get4Toggles = async (tenant, locale, service, req) => {
|
|
|
33
36
|
*/
|
|
34
37
|
const metadata = service => {
|
|
35
38
|
return async (odataReq, odataRes, next) => {
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
const req = odataReq.getIncomingRequest()
|
|
40
|
+
|
|
41
|
+
const tenant = req.user && req.user.tenant
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const locale = odataRes.getContract().getLocale()
|
|
43
|
+
// REVISIT: can we take locale from user, or is there some odata special wrt metadata?
|
|
44
|
+
const locale = odataRes.getContract().getLocale()
|
|
42
45
|
|
|
46
|
+
try {
|
|
43
47
|
let edmx
|
|
44
48
|
|
|
45
49
|
if (tenant) {
|
|
@@ -60,7 +64,13 @@ const metadata = service => {
|
|
|
60
64
|
|
|
61
65
|
return next(null, toODataResult(edmx))
|
|
62
66
|
} catch (e) {
|
|
63
|
-
|
|
67
|
+
if (LOG._error) {
|
|
68
|
+
e.message = 'Unable to get EDMX for tenant ' + tenant + ' due to error: ' + e.message
|
|
69
|
+
LOG.error(e)
|
|
70
|
+
}
|
|
71
|
+
// return 503 to client
|
|
72
|
+
const { error, statusCode } = normalizeError(Object.assign(e, { statusCode: 503 }), req)
|
|
73
|
+
return next(Object.assign(error, { statusCode }))
|
|
64
74
|
}
|
|
65
75
|
}
|
|
66
76
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
|
+
|
|
2
3
|
const { SELECT } = cds.ql
|
|
4
|
+
|
|
3
5
|
const ODataRequest = require('../ODataRequest')
|
|
4
|
-
const { rewriteExpandAsterisk } = require('../../../../common/utils/rewriteAsterisks')
|
|
5
6
|
|
|
6
7
|
const {
|
|
7
8
|
QueryOptions,
|
|
@@ -13,15 +14,14 @@ const {
|
|
|
13
14
|
}
|
|
14
15
|
} = require('../okra/odata-server')
|
|
15
16
|
|
|
16
|
-
const getError = require('../../../../common/error')
|
|
17
|
-
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
18
17
|
const { isCustomOperation, skipToken } = require('../utils/request')
|
|
19
18
|
const { actionAndFunctionQueries, getActionOrFunctionReturnType } = require('../utils/handlerUtils')
|
|
20
19
|
const { validateResourcePath } = require('../utils/request')
|
|
21
20
|
const { toODataResult, postProcess } = require('../utils/result')
|
|
22
21
|
const { isStreaming, getStreamProperties } = require('../utils/stream')
|
|
23
22
|
const { resolveStructuredName } = require('../utils/handlerUtils')
|
|
24
|
-
const
|
|
23
|
+
const getError = require('../../../../common/error')
|
|
24
|
+
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Checks whether a bound function or function import is invoked.
|
|
@@ -32,6 +32,10 @@ const { ensureNoDraftsSuffix } = require('../../../../common/utils/draft')
|
|
|
32
32
|
*/
|
|
33
33
|
const _isFunction = segments => [BOUND_FUNCTION, FUNCTION_IMPORT].includes(segments[segments.length - 1].getKind())
|
|
34
34
|
|
|
35
|
+
const _selectOrExpandInQueryOptions = queryOptions => {
|
|
36
|
+
return queryOptions && (queryOptions.$select || queryOptions.$expand)
|
|
37
|
+
}
|
|
38
|
+
|
|
35
39
|
/**
|
|
36
40
|
* Invoke a function.
|
|
37
41
|
*
|
|
@@ -49,7 +53,12 @@ const _invokeFunction = async (tx, req, odataReq) => {
|
|
|
49
53
|
tx.model.definitions
|
|
50
54
|
)
|
|
51
55
|
|
|
52
|
-
if
|
|
56
|
+
// if $select or $expand is present, do it
|
|
57
|
+
if (
|
|
58
|
+
functionReturnType &&
|
|
59
|
+
functionReturnType.kind === 'entity' &&
|
|
60
|
+
_selectOrExpandInQueryOptions(odataReq.getQueryOptions())
|
|
61
|
+
) {
|
|
53
62
|
await actionAndFunctionQueries(req, odataReq, result, tx, functionReturnType)
|
|
54
63
|
}
|
|
55
64
|
|
|
@@ -283,13 +292,14 @@ const _readCollection = async (tx, req, odataReq) => {
|
|
|
283
292
|
*
|
|
284
293
|
* @param {object} tx
|
|
285
294
|
* @param {object} req
|
|
286
|
-
* @param {Array} segments
|
|
287
295
|
* @returns {Promise}
|
|
288
296
|
* @private
|
|
289
297
|
*/
|
|
290
|
-
const _readStream = async (tx, req
|
|
298
|
+
const _readStream = async (tx, req) => {
|
|
291
299
|
req.query._streaming = true
|
|
292
300
|
|
|
301
|
+
const { contentType, contentDisposition } = await getStreamProperties(req, tx.model)
|
|
302
|
+
|
|
293
303
|
let result = await tx.dispatch(req)
|
|
294
304
|
|
|
295
305
|
// REVISIT: compat, should actually be treated as object
|
|
@@ -313,13 +323,12 @@ const _readStream = async (tx, req, segments) => {
|
|
|
313
323
|
})
|
|
314
324
|
}
|
|
315
325
|
|
|
316
|
-
const { contentType, contentDisposition } = await getStreamProperties(segments, tx, req)
|
|
317
|
-
|
|
318
326
|
const headers = req._.odataReq.getHeaders()
|
|
327
|
+
|
|
319
328
|
if (
|
|
329
|
+
contentType &&
|
|
320
330
|
headers &&
|
|
321
331
|
headers.accept &&
|
|
322
|
-
contentType &&
|
|
323
332
|
!headers.accept.includes('*/*') &&
|
|
324
333
|
!headers.accept.includes(contentType) &&
|
|
325
334
|
!headers.accept.includes(contentType.split('/')[0] + '/*')
|
|
@@ -328,9 +337,9 @@ const _readStream = async (tx, req, segments) => {
|
|
|
328
337
|
}
|
|
329
338
|
|
|
330
339
|
if (contentType) streamObj['*@odata.mediaContentType'] = contentType
|
|
331
|
-
if (contentDisposition)
|
|
340
|
+
if (contentDisposition)
|
|
332
341
|
req._.odataRes.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(contentDisposition)}"`)
|
|
333
|
-
|
|
342
|
+
|
|
334
343
|
return streamObj
|
|
335
344
|
}
|
|
336
345
|
|
|
@@ -368,10 +377,6 @@ const _readAndTransform = (tx, req, odataReq) => {
|
|
|
368
377
|
}
|
|
369
378
|
|
|
370
379
|
if (_isCollection(segments)) {
|
|
371
|
-
if (odataReq.getUriInfo().getQueryOption(QueryOptions.COUNT)) {
|
|
372
|
-
req.query.SELECT.count = true
|
|
373
|
-
}
|
|
374
|
-
|
|
375
380
|
return _readCollection(tx, req, odataReq)
|
|
376
381
|
}
|
|
377
382
|
|
|
@@ -384,11 +389,11 @@ const _readAndTransform = (tx, req, odataReq) => {
|
|
|
384
389
|
}
|
|
385
390
|
}
|
|
386
391
|
|
|
387
|
-
return _readStream(tx, req
|
|
392
|
+
return _readStream(tx, req)
|
|
388
393
|
}
|
|
389
394
|
|
|
390
395
|
if (isStreaming(segments)) {
|
|
391
|
-
return _readStream(tx, req
|
|
396
|
+
return _readStream(tx, req)
|
|
392
397
|
}
|
|
393
398
|
|
|
394
399
|
if (req.target._isSingleton) {
|
|
@@ -418,43 +423,6 @@ const _removeKeysForParams = result => {
|
|
|
418
423
|
return options
|
|
419
424
|
}
|
|
420
425
|
|
|
421
|
-
const _getTarget = (ref, target, definitions) => {
|
|
422
|
-
if (cds.env.effective.odata.proxies) {
|
|
423
|
-
const target_ = target.elements[ref[0]]
|
|
424
|
-
|
|
425
|
-
if (ref.length === 1) {
|
|
426
|
-
return definitions[ensureNoDraftsSuffix(target_.target)]
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return _getTarget(ref.slice(1), target_, definitions)
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const target_ = target.elements[ref.join('_')]
|
|
433
|
-
return definitions[ensureNoDraftsSuffix(target_.target)]
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const _getRestrictedExpand = (columns, target, definitions) => {
|
|
437
|
-
if (!columns || !target || columns === '*') return
|
|
438
|
-
|
|
439
|
-
const annotation = target['@Capabilities.ExpandRestrictions.NonExpandableProperties']
|
|
440
|
-
const restrictions = annotation && annotation.map(element => element['='])
|
|
441
|
-
|
|
442
|
-
rewriteExpandAsterisk(columns, target)
|
|
443
|
-
|
|
444
|
-
for (const col of columns) {
|
|
445
|
-
if (col.expand) {
|
|
446
|
-
if (restrictions && restrictions.length !== 0) {
|
|
447
|
-
const ref = col.ref.join('_')
|
|
448
|
-
const ref_ = restrictions.find(element => element.replace(/\./g, '_') === ref)
|
|
449
|
-
if (ref_) return ref_
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
const restricted = _getRestrictedExpand(col.expand, _getTarget(col.ref, target, definitions), definitions)
|
|
453
|
-
if (restricted) return restricted
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
426
|
/**
|
|
459
427
|
* The handler that will be registered with odata-v4.
|
|
460
428
|
*
|
|
@@ -478,21 +446,11 @@ const read = service => {
|
|
|
478
446
|
return next(e)
|
|
479
447
|
}
|
|
480
448
|
|
|
481
|
-
// REVISIT: this should be in common/generic/auth.js with the rest of the access control stuff
|
|
482
|
-
const restricted = _getRestrictedExpand(
|
|
483
|
-
req.query.SELECT && req.query.SELECT.columns,
|
|
484
|
-
req.target,
|
|
485
|
-
service.model.definitions
|
|
486
|
-
)
|
|
487
|
-
if (restricted) {
|
|
488
|
-
return next(getError(400, 'EXPAND_IS_RESTRICTED', [restricted]))
|
|
489
|
-
}
|
|
490
|
-
|
|
491
449
|
const changeset = odataReq.getAtomicityGroupId()
|
|
492
450
|
const tx = changeset ? odataReq.getBatchApplicationData().txs[changeset] : service.tx(req)
|
|
493
451
|
cds.context = tx
|
|
494
452
|
|
|
495
|
-
let result, err
|
|
453
|
+
let result, err
|
|
496
454
|
let additional = {}
|
|
497
455
|
try {
|
|
498
456
|
// REVISIT: refactor _readAndTransform
|
|
@@ -509,13 +467,12 @@ const read = service => {
|
|
|
509
467
|
// for passing into commit
|
|
510
468
|
odataReq.getBatchApplicationData().results[changeset].push({ result, req })
|
|
511
469
|
} else {
|
|
512
|
-
commit = true
|
|
513
470
|
await tx.commit(result)
|
|
514
471
|
}
|
|
515
472
|
} catch (e) {
|
|
516
473
|
err = e
|
|
517
|
-
if (!changeset
|
|
518
|
-
//
|
|
474
|
+
if (!changeset) {
|
|
475
|
+
// REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
|
|
519
476
|
await tx.rollback(e).catch(() => {})
|
|
520
477
|
} else if (changeset) {
|
|
521
478
|
// for passing into rollback
|
|
@@ -2,8 +2,6 @@ const cds = require('../../../../cds')
|
|
|
2
2
|
|
|
3
3
|
const { UNAUTHORIZED, FORBIDDEN, getRequiresAsArray } = require('../../../../common/utils/auth')
|
|
4
4
|
|
|
5
|
-
const measurePerformance = require('../../perf/performance')
|
|
6
|
-
|
|
7
5
|
module.exports = srv => {
|
|
8
6
|
const requires = getRequiresAsArray(srv.definition)
|
|
9
7
|
|
|
@@ -55,11 +53,6 @@ module.exports = srv => {
|
|
|
55
53
|
odataReq.setApplicationData({ req })
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
// in case of batch request with sap-statistics=true also measure performance of batched requests
|
|
59
|
-
if (odataReq.getBatchApplicationData()) {
|
|
60
|
-
measurePerformance(req, res)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
56
|
next()
|
|
64
57
|
}
|
|
65
58
|
}
|
|
@@ -14,13 +14,6 @@ const { toODataResult, postProcess, postProcessMinimal } = require('../utils/res
|
|
|
14
14
|
const { hasOmitValuesPreference } = require('../utils/omitValues')
|
|
15
15
|
const { mergeJson } = require('../../../services/utils/compareJson')
|
|
16
16
|
|
|
17
|
-
/*
|
|
18
|
-
const { isStreaming } = require('../utils/stream')
|
|
19
|
-
const { findCsnTargetFor } = require('../../../../common/utils/csn')
|
|
20
|
-
const { isActiveEntityRequested, removeIsActiveEntityRecursively } = require('../../../../fiori/utils/where')
|
|
21
|
-
const { ensureDraftsSuffix } = require('../../../../fiori/utils/handler')
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
17
|
const _isUpsertAllowed = target => {
|
|
25
18
|
return !(cds.env.runtime && cds.env.runtime.allow_upsert === false) && !(target && target._isDraftEnabled)
|
|
26
19
|
}
|
|
@@ -121,58 +114,6 @@ const _readAfterWriteAndVirtuals = async (req, service, result) => {
|
|
|
121
114
|
const _shouldReadPreviousResult = req =>
|
|
122
115
|
req.event === 'UPDATE' && !isReturnMinimal(req) && hasOmitValuesPreference(req.headers.prefer, 'defaults')
|
|
123
116
|
|
|
124
|
-
/*
|
|
125
|
-
const _getEntity = (segments, model) => {
|
|
126
|
-
let entityName, namespace
|
|
127
|
-
const previous = segments[segments.length - 2]
|
|
128
|
-
if (previous.getKind() === 'ENTITY') {
|
|
129
|
-
entityName = previous.getEntitySet().getName()
|
|
130
|
-
namespace = previous.getEdmType().getFullQualifiedName().namespace
|
|
131
|
-
} else if (previous.getKind() === 'NAVIGATION.TO.ONE') {
|
|
132
|
-
entityName = previous.getTarget().getName()
|
|
133
|
-
namespace = previous.getTarget().getEntityType().getFullQualifiedName().namespace
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (entityName) {
|
|
137
|
-
return findCsnTargetFor(entityName, model, namespace)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const _getMediaType = entity => {
|
|
142
|
-
if (entity._hasPersistenceSkip) return
|
|
143
|
-
|
|
144
|
-
return Object.values(entity.elements).find(ele => ele['@Core.IsMediaType'])
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const _getMediaTypeCQN = (mediaType, contentType, entity, req) => {
|
|
148
|
-
const where = req.query.UPDATE.entity.ref[0].where
|
|
149
|
-
const isActive = isActiveEntityRequested(where)
|
|
150
|
-
const data = {}
|
|
151
|
-
data[mediaType.name] = contentType
|
|
152
|
-
const cqn = UPDATE(entity).set(data)
|
|
153
|
-
cqn.UPDATE.where = removeIsActiveEntityRecursively(where)
|
|
154
|
-
if (!isActive) {
|
|
155
|
-
cqn.UPDATE.entity = ensureDraftsSuffix(entity.name)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return cqn
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const _handleMediaType = async (odataReq, model, tx, req) => {
|
|
162
|
-
const segments = odataReq.getUriInfo().getPathSegments()
|
|
163
|
-
const contentType = odataReq._inRequest.headers['content-type']
|
|
164
|
-
if (isStreaming(segments) && contentType) {
|
|
165
|
-
const entity = _getEntity(segments, model)
|
|
166
|
-
if (entity && !entity['@cds.persistence.skip']) {
|
|
167
|
-
const mediaType = _getMediaType(entity)
|
|
168
|
-
if (mediaType) {
|
|
169
|
-
await tx.run(_getMediaTypeCQN(mediaType, contentType, entity, req))
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
*/
|
|
175
|
-
|
|
176
117
|
/**
|
|
177
118
|
* The handler that will be registered with odata-v4.
|
|
178
119
|
*
|
|
@@ -199,11 +140,8 @@ const update = service => {
|
|
|
199
140
|
// putting a property?
|
|
200
141
|
const primitive = odataReq.getUriInfo().getLastSegment().getKind() === 'PRIMITIVE.PROPERTY'
|
|
201
142
|
|
|
202
|
-
let result, err
|
|
143
|
+
let result, err
|
|
203
144
|
try {
|
|
204
|
-
// // REVISIT: should be handled somewhere else
|
|
205
|
-
// await _handleMediaType(odataReq, service.model, tx, req)
|
|
206
|
-
|
|
207
145
|
let previousResult
|
|
208
146
|
if (_shouldReadPreviousResult(req)) {
|
|
209
147
|
previousResult = await _readAfterWriteAndVirtuals(req, service, result)
|
|
@@ -227,7 +165,6 @@ const update = service => {
|
|
|
227
165
|
// for passing into commit
|
|
228
166
|
odataReq.getBatchApplicationData().results[changeset].push({ result, req })
|
|
229
167
|
} else {
|
|
230
|
-
commit = true
|
|
231
168
|
await tx.commit(result)
|
|
232
169
|
}
|
|
233
170
|
|
|
@@ -236,8 +173,8 @@ const update = service => {
|
|
|
236
173
|
}
|
|
237
174
|
} catch (e) {
|
|
238
175
|
err = e
|
|
239
|
-
if (!changeset
|
|
240
|
-
//
|
|
176
|
+
if (!changeset) {
|
|
177
|
+
// REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
|
|
241
178
|
await tx.rollback(e).catch(() => {})
|
|
242
179
|
} else if (changeset) {
|
|
243
180
|
// for passing into rollback
|
|
@@ -64,7 +64,10 @@ class ExpressionToCQN {
|
|
|
64
64
|
const nav =
|
|
65
65
|
pathSegments.length > 2 ? pathSegments.slice(0, pathSegments.length - 2).map(this._segmentFromMember) : []
|
|
66
66
|
const navName = this._segmentFromMember(pathSegments[pathSegments.length - 2])
|
|
67
|
-
|
|
67
|
+
let condition = this._segmentFromMember(pathSegments[pathSegments.length - 1])
|
|
68
|
+
|
|
69
|
+
// in case of functions, condition is an object
|
|
70
|
+
if (condition && !Array.isArray(condition)) condition = [condition]
|
|
68
71
|
|
|
69
72
|
return pathSegments[pathSegments.length - 1].getKind() === 'ALL.EXPRESSION'
|
|
70
73
|
? ['not', 'exists', { ref: [...nav, { id: navName, where: ['not', { xpr: condition }] }] }]
|
|
@@ -84,6 +87,8 @@ class ExpressionToCQN {
|
|
|
84
87
|
case ResourceKind.ALL_EXPRESSION:
|
|
85
88
|
case ResourceKind.ANY_EXPRESSION:
|
|
86
89
|
return segment.getExpression() ? this.parse(segment.getExpression()) : undefined
|
|
90
|
+
case ResourceKind.COUNT:
|
|
91
|
+
return '$count'
|
|
87
92
|
default:
|
|
88
93
|
throw getFeatureNotSupportedError(`Segment kind "${segment.getKind()}" in $filter query option`)
|
|
89
94
|
}
|
|
@@ -95,6 +100,21 @@ class ExpressionToCQN {
|
|
|
95
100
|
if (!segment) return []
|
|
96
101
|
|
|
97
102
|
if (segment.getKind() === ResourceKind.NAVIGATION_TO_ONE) {
|
|
103
|
+
const name = this._segmentFromMember(segment)
|
|
104
|
+
const where =
|
|
105
|
+
nextSegments &&
|
|
106
|
+
nextSegments.length &&
|
|
107
|
+
nextSegments[nextSegments.length - 1].getKind() === ResourceKind.COUNT &&
|
|
108
|
+
segment.getKeyPredicates().reduce((prev, curr) => {
|
|
109
|
+
if (prev.length > 0) prev.push('and')
|
|
110
|
+
prev.push({ ref: [curr.getEdmRef().getName()] }, '=', { val: curr.getText() })
|
|
111
|
+
return prev
|
|
112
|
+
}, [])
|
|
113
|
+
|
|
114
|
+
return [where ? { id: name, where } : name, ...this._getMemberRecursively(nextSegments)]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (segment.getKind() === ResourceKind.NAVIGATION_TO_MANY) {
|
|
98
118
|
return [this._segmentFromMember(segment), ...this._getMemberRecursively(nextSegments)]
|
|
99
119
|
}
|
|
100
120
|
|
|
@@ -102,6 +122,10 @@ class ExpressionToCQN {
|
|
|
102
122
|
return [...this._getMemberRecursively(nextSegments)]
|
|
103
123
|
}
|
|
104
124
|
|
|
125
|
+
if (segment.getKind() === ResourceKind.COUNT) {
|
|
126
|
+
return [this._segmentFromMember(segment), ...this._getMemberRecursively(nextSegments)]
|
|
127
|
+
}
|
|
128
|
+
|
|
105
129
|
if (segment.getKind() === ResourceKind.COMPLEX_PROPERTY) {
|
|
106
130
|
if (nextSegments.length) {
|
|
107
131
|
return [this._segmentFromMember(segment), ...this._getMemberRecursively(nextSegments)]
|
|
@@ -130,6 +154,10 @@ class ExpressionToCQN {
|
|
|
130
154
|
return entry
|
|
131
155
|
}
|
|
132
156
|
}
|
|
157
|
+
if (members.length > 1 && members[members.length - 1] === '$count') {
|
|
158
|
+
return { func: 'count', args: [{ ref: members.slice(0, members.length - 1) }], as: '$count' }
|
|
159
|
+
}
|
|
160
|
+
|
|
133
161
|
return { ref: members }
|
|
134
162
|
}
|
|
135
163
|
|
|
@@ -12,6 +12,8 @@ const ExpressionToCQN = require('./ExpressionToCQN')
|
|
|
12
12
|
const { getColumns } = require('../../../services/utils/columns')
|
|
13
13
|
const { addLimit, isSameArray } = require('./utils')
|
|
14
14
|
const { findCsnTargetFor } = require('../../../../common/utils/csn')
|
|
15
|
+
const getError = require('../../../../common/error')
|
|
16
|
+
|
|
15
17
|
/**
|
|
16
18
|
* Check which element(s) of the entity has been expanded.
|
|
17
19
|
*
|
|
@@ -28,7 +30,11 @@ const _getExpandItem = (isAll, expandItems, name) => {
|
|
|
28
30
|
|
|
29
31
|
return expandItems.find(item => {
|
|
30
32
|
const pathSegments = item.getPathSegments()
|
|
31
|
-
|
|
33
|
+
if (pathSegments[pathSegments.length - 1].getKind() === 'COUNT') {
|
|
34
|
+
throw getError(501, 'EXPAND_COUNT_UNSUPPORTED')
|
|
35
|
+
}
|
|
36
|
+
const navigation = pathSegments[pathSegments.length - 1].getNavigationProperty()
|
|
37
|
+
return navigation && navigation.getName() === name
|
|
32
38
|
})
|
|
33
39
|
}
|
|
34
40
|
|
|
@@ -184,6 +184,7 @@ const _extendCqnWithApply = (cqn, apply, entity) => {
|
|
|
184
184
|
|
|
185
185
|
// REVISIT only execute on HANA?
|
|
186
186
|
cqn.SELECT.columns = _groupByPathExpressionsToExpand(cqn, entity)
|
|
187
|
+
if (cqn.SELECT.count) cqn.SELECT.__countAggregated = true
|
|
187
188
|
}
|
|
188
189
|
|
|
189
190
|
const _containsSelectedColumn = (o, selectColumns) => {
|
|
@@ -340,6 +341,7 @@ const _handleApply = (apply, select) => {
|
|
|
340
341
|
* @param {object} odataReq - OKRA's req
|
|
341
342
|
* @private
|
|
342
343
|
*/
|
|
344
|
+
// eslint-disable-next-line complexity
|
|
343
345
|
const readToCQN = (service, target, odataReq) => {
|
|
344
346
|
const uriInfo = odataReq.getUriInfo()
|
|
345
347
|
const segments = uriInfo.getPathSegments()
|
|
@@ -390,6 +392,8 @@ const readToCQN = (service, target, odataReq) => {
|
|
|
390
392
|
const cqn = SELECT.from(isView ? _convertUrlPathToViewCqn(segments) : convertUrlPathToCqn(segments, service), select)
|
|
391
393
|
addValidationQueryIfRequired(segments, isView, cqn, service, kind)
|
|
392
394
|
|
|
395
|
+
if (isCollectionOrToMany && queryOptions && queryOptions[QueryOptions.COUNT]) cqn.SELECT.count = true
|
|
396
|
+
|
|
393
397
|
if (Object.keys(apply).length) {
|
|
394
398
|
_extendCqnWithApply(cqn, apply, entity)
|
|
395
399
|
}
|
|
@@ -55,13 +55,13 @@ const convertUrlPathToCqn = (segments, service) => {
|
|
|
55
55
|
const entity = segment.getEntitySet().getEntityType().getFullQualifiedName()
|
|
56
56
|
const keys = convertKeyPredicatesToStringExpr(segment.getKeyPredicates())
|
|
57
57
|
|
|
58
|
-
return `${findCsnTargetFor(entity.name, service.model, service.
|
|
58
|
+
return `${findCsnTargetFor(entity.name, service.model, service.namespace).name}${keys}`
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
if (segment.getKind() === 'SINGLETON') {
|
|
62
62
|
const singleton = segment.getSingleton().getEntityType().getFullQualifiedName()
|
|
63
63
|
|
|
64
|
-
return `${findCsnTargetFor(singleton.name, service.model, service.
|
|
64
|
+
return `${findCsnTargetFor(singleton.name, service.model, service.namespace).name}`
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
if (segment.getKind() === 'COMPLEX.PROPERTY') {
|