@sap/cds 5.6.1 → 5.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -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 +1 -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/invocation/SetResponseHeadersCommand.js +1 -0
- 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/result.js +18 -15
- 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 +27 -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 +61 -30
- 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 +289 -114
- 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 +82 -61
- package/libx/_runtime/db/expand/index.js +3 -1
- package/libx/_runtime/db/generic/arrayed.js +14 -27
- 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 +2 -1
- package/libx/_runtime/fiori/generic/patch.js +1 -1
- package/libx/_runtime/fiori/generic/read.js +151 -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 +21 -2
- 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/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 +13 -10
- 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
|
@@ -8,7 +8,8 @@ const { SELECT } = cds.ql
|
|
|
8
8
|
const { getRequiresAsArray } = require('../utils/auth')
|
|
9
9
|
const { cqn2cqn4sql } = require('../utils/cqn2cqn4sql')
|
|
10
10
|
const { isActiveEntityRequested, removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
|
|
11
|
-
const { ensureDraftsSuffix } = require('../../fiori/utils/handler')
|
|
11
|
+
const { ensureDraftsSuffix, ensureNoDraftsSuffix } = require('../../fiori/utils/handler')
|
|
12
|
+
const { rewriteExpandAsterisk } = require('../../common/utils/rewriteAsterisks')
|
|
12
13
|
|
|
13
14
|
const WRITE = ['CREATE', 'UPDATE', 'DELETE']
|
|
14
15
|
const MOD = { UPDATE: 1, DELETE: 1, EDIT: 1 }
|
|
@@ -41,6 +42,48 @@ const _reject = req => {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
const _getTarget = (ref, target, definitions) => {
|
|
46
|
+
if (cds.env.effective.odata.proxies) {
|
|
47
|
+
const target_ = target.elements[ref[0]]
|
|
48
|
+
|
|
49
|
+
if (ref.length === 1) {
|
|
50
|
+
return definitions[ensureNoDraftsSuffix(target_.target)]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return _getTarget(ref.slice(1), target_, definitions)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const target_ = target.elements[ref.join('_')]
|
|
57
|
+
return definitions[ensureNoDraftsSuffix(target_.target)]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const _getRestrictedExpand = (columns, target, definitions) => {
|
|
61
|
+
if (!columns || !target || columns === '*') return
|
|
62
|
+
|
|
63
|
+
const annotation = target['@Capabilities.ExpandRestrictions.NonExpandableProperties']
|
|
64
|
+
const restrictions = annotation && annotation.map(element => element['='])
|
|
65
|
+
|
|
66
|
+
rewriteExpandAsterisk(columns, target)
|
|
67
|
+
|
|
68
|
+
for (const col of columns) {
|
|
69
|
+
if (col.expand) {
|
|
70
|
+
if (restrictions && restrictions.length !== 0) {
|
|
71
|
+
const ref = col.ref.join('_')
|
|
72
|
+
const ref_ = restrictions.find(element => element.replace(/\./g, '_') === ref)
|
|
73
|
+
if (ref_) return ref_
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// expand: '**' or '*3' is only possible within custom handler, no check needed
|
|
77
|
+
if (typeof col.expand === 'string' && /^\*{1}[\d|*]+/.test(col.expand)) {
|
|
78
|
+
continue
|
|
79
|
+
} else {
|
|
80
|
+
const restricted = _getRestrictedExpand(col.expand, _getTarget(col.ref, target, definitions), definitions)
|
|
81
|
+
if (restricted) return restricted
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
44
87
|
const _getCurrentSubClause = (next, restrict) => {
|
|
45
88
|
const escaped = next[0].replace(/\$/g, '\\$').replace(/\./g, '\\.')
|
|
46
89
|
const re1 = new RegExp(`([\\w\\.']*)\\s*=\\s*(${escaped})|(${escaped})\\s*=\\s*([\\w\\.']*)`)
|
|
@@ -367,7 +410,7 @@ const _getRestrictionForTarget = (resolvedApplicables, target) => {
|
|
|
367
410
|
|
|
368
411
|
const _addRestrictionsToRead = async (req, model, resolvedApplicables) => {
|
|
369
412
|
if (req.target._isDraftEnabled) {
|
|
370
|
-
req.query._draftRestrictions = resolvedApplicables
|
|
413
|
+
req.query._draftRestrictions = resolvedApplicables
|
|
371
414
|
return
|
|
372
415
|
}
|
|
373
416
|
|
|
@@ -541,36 +584,9 @@ const _addNormalizedRestrictPerGrant = (grant, where, restrict, restricts, defin
|
|
|
541
584
|
}
|
|
542
585
|
|
|
543
586
|
const _addNormalizedRestrict = (restrict, restricts, definition, definitions) => {
|
|
544
|
-
|
|
587
|
+
const where = restrict.where
|
|
545
588
|
? restrict.where.replace(/\$user/g, '$user.id').replace(/\$user\.id\./g, '$user.')
|
|
546
589
|
: undefined
|
|
547
|
-
|
|
548
|
-
// NOTE: "exists toMany.toOne[prop = $user]" -> "exists toMany[exists toOne[prop = $user]]"
|
|
549
|
-
try {
|
|
550
|
-
if (where) {
|
|
551
|
-
// operate on a copy
|
|
552
|
-
let _where = where
|
|
553
|
-
// find all path expressions in order to normalize shorthand (i.e., inject "[exists ...]")
|
|
554
|
-
const paths = (where.match(/ (\w\.*)*/g) || []).filter(m => m.match(/\./) && m !== ' ')
|
|
555
|
-
for (let i = 0; i < paths.length; i++) {
|
|
556
|
-
const parts = paths[i].trim().split('.')
|
|
557
|
-
let current = definition
|
|
558
|
-
while (parts.length) {
|
|
559
|
-
current = current.elements[parts.shift()]
|
|
560
|
-
if (current.isAssociation && _where.includes(current.name + '.')) {
|
|
561
|
-
const matches = _where.match(new RegExp(`(${current.name}).(.*)]`))
|
|
562
|
-
_where = _where.replace(`${matches[1]}.`, `${current.name}[exists `)
|
|
563
|
-
_where = _where.replace(matches[2], `${matches[2]}]`)
|
|
564
|
-
}
|
|
565
|
-
if (current.target) current = definitions[current.target]
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
where = _where
|
|
569
|
-
}
|
|
570
|
-
} catch (e) {
|
|
571
|
-
// ignore
|
|
572
|
-
}
|
|
573
|
-
|
|
574
590
|
restrict.grant = Array.isArray(restrict.grant) ? restrict.grant : [restrict.grant || '*']
|
|
575
591
|
restrict.grant.forEach(grant => _addNormalizedRestrictPerGrant(grant, where, restrict, restricts, definition))
|
|
576
592
|
}
|
|
@@ -888,9 +904,24 @@ const _secureDependentEntities = srv => {
|
|
|
888
904
|
}
|
|
889
905
|
}
|
|
890
906
|
|
|
907
|
+
const _restrictExpand = service => {
|
|
908
|
+
service.on('READ', '*', (req, next) => {
|
|
909
|
+
const restricted = _getRestrictedExpand(
|
|
910
|
+
req.query.SELECT && req.query.SELECT.columns,
|
|
911
|
+
req.target,
|
|
912
|
+
service.model.definitions
|
|
913
|
+
)
|
|
914
|
+
if (restricted) {
|
|
915
|
+
return req.reject(400, 'EXPAND_IS_RESTRICTED', [restricted])
|
|
916
|
+
}
|
|
917
|
+
return next()
|
|
918
|
+
})
|
|
919
|
+
}
|
|
920
|
+
|
|
891
921
|
module.exports = cds.service.impl(function () {
|
|
892
922
|
// @restrict, @requires, @readonly, @insertonly, and @Capabilities for entities
|
|
893
923
|
_secureDependentEntities(this)
|
|
924
|
+
_restrictExpand(this)
|
|
894
925
|
for (const k in this.entities) {
|
|
895
926
|
const entity = this.entities[k]
|
|
896
927
|
if (!_authDependsOnParents(entity)) _registerAuthHandlers(entity, this)
|
|
@@ -9,7 +9,6 @@ const onlyKeysRemain = require('../utils/onlyKeysRemain')
|
|
|
9
9
|
|
|
10
10
|
const _targetEntityDoesNotExist = async req => {
|
|
11
11
|
const { query } = req
|
|
12
|
-
|
|
13
12
|
const cqn = SELECT.from(query.UPDATE.entity, [1])
|
|
14
13
|
|
|
15
14
|
if (query.UPDATE.entity.as) {
|
|
@@ -22,7 +21,6 @@ const _targetEntityDoesNotExist = async req => {
|
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
const exists = await cds.tx(req).run(cqn)
|
|
25
|
-
|
|
26
24
|
return exists.length === 0
|
|
27
25
|
}
|
|
28
26
|
|
|
@@ -54,6 +52,7 @@ const _pick = element => {
|
|
|
54
52
|
|
|
55
53
|
const _updateReqData = (req, that) => {
|
|
56
54
|
const template = getTemplate('app-output', that, req.target, { pick: _pick })
|
|
55
|
+
|
|
57
56
|
if (template.elements.size > 0) {
|
|
58
57
|
const arrayData = Array.isArray(req.data) ? req.data : [req.data]
|
|
59
58
|
for (const row of arrayData) {
|
|
@@ -73,26 +72,19 @@ module.exports = cds.service.impl(function () {
|
|
|
73
72
|
req.reject(501, 'PERSISTENCE_SKIP_NO_GENERIC_CRUD', [req.target.name])
|
|
74
73
|
}
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
req.reject(501, 'NO_DATABASE_CONNECTION')
|
|
79
|
-
}
|
|
75
|
+
// REVISIT: error message
|
|
76
|
+
if (!cds.db) req.reject(501, 'NO_DATABASE_CONNECTION')
|
|
80
77
|
|
|
81
78
|
let result
|
|
82
79
|
|
|
83
80
|
// no changes, no op (otherwise, @cds.on.update gets new values), but we need to check existence
|
|
84
81
|
if (req.event === 'UPDATE' && onlyKeysRemain(req)) {
|
|
85
|
-
if (await _targetEntityDoesNotExist(req))
|
|
86
|
-
req.reject(404)
|
|
87
|
-
}
|
|
88
|
-
|
|
82
|
+
if (await _targetEntityDoesNotExist(req)) req.reject(404)
|
|
89
83
|
result = req.data
|
|
90
84
|
}
|
|
91
85
|
|
|
92
86
|
if (req.event === 'DELETE' && req.target._isSingleton) {
|
|
93
|
-
if (!req.target['@odata.singleton.nullable'])
|
|
94
|
-
req.reject(400, 'SINGLETON_NOT_NULLABLE')
|
|
95
|
-
}
|
|
87
|
+
if (!req.target['@odata.singleton.nullable']) req.reject(400, 'SINGLETON_NOT_NULLABLE')
|
|
96
88
|
|
|
97
89
|
const singleton = await cds.tx(req).run(SELECT.one(req.target))
|
|
98
90
|
if (!singleton) req.reject(404)
|
|
@@ -103,21 +95,17 @@ module.exports = cds.service.impl(function () {
|
|
|
103
95
|
result = await cds.tx(req).run(req.query, req.data)
|
|
104
96
|
}
|
|
105
97
|
|
|
106
|
-
if (req.event === 'READ')
|
|
107
|
-
return result
|
|
108
|
-
}
|
|
98
|
+
if (req.event === 'READ') return result
|
|
109
99
|
|
|
110
100
|
if (req.event === 'DELETE') {
|
|
111
|
-
if (result === 0)
|
|
112
|
-
req.reject(404)
|
|
113
|
-
}
|
|
114
|
-
|
|
101
|
+
if (result === 0) req.reject(404)
|
|
115
102
|
return result
|
|
116
103
|
}
|
|
117
104
|
|
|
118
|
-
// case: no authorization check and payload more than just keys but no changes
|
|
119
|
-
|
|
120
|
-
|
|
105
|
+
// case: no authorization check and payload more than just keys but no changes
|
|
106
|
+
// -> affected rows === 0 -> no change or not exists?
|
|
107
|
+
if (req.event === 'UPDATE' && result === 0 && !req._authChecked) {
|
|
108
|
+
if (await _targetEntityDoesNotExist(req)) req.reject(404)
|
|
121
109
|
}
|
|
122
110
|
|
|
123
111
|
// flag to trigger read after write in protocol adapter
|
|
@@ -59,6 +59,14 @@ const _isDraftCoreComputed = (req, element, event) =>
|
|
|
59
59
|
element['@Core.Computed'] &&
|
|
60
60
|
!((event === 'CREATE' && element['@cds.on.insert']) || element['@cds.on.update'])
|
|
61
61
|
|
|
62
|
+
const _getMediaTypeProperty = element => element['@Core.MediaType'] && element['@Core.MediaType']['=']
|
|
63
|
+
|
|
64
|
+
const _getMediaTypeValue = req =>
|
|
65
|
+
req._.req &&
|
|
66
|
+
req._.req.headers['content-type'] &&
|
|
67
|
+
!req._.req.headers['content-type'].match(/json|multipart/i) &&
|
|
68
|
+
req._.req.headers['content-type']
|
|
69
|
+
|
|
62
70
|
const _processCategory = ({ row, key, category, isRoot, event, value, req, element }) => {
|
|
63
71
|
category = getSimpleCategory(category)
|
|
64
72
|
|
|
@@ -91,6 +99,11 @@ const _processCategory = ({ row, key, category, isRoot, event, value, req, eleme
|
|
|
91
99
|
// REVISIT: remove delay_assert_deep_assoc with cds^6
|
|
92
100
|
if (!cds.env.features.delay_assert_deep_assoc) checkIfAssocDeep(element, value.val, req)
|
|
93
101
|
}
|
|
102
|
+
|
|
103
|
+
// set media type from content-type header if streaming
|
|
104
|
+
const mtProperty = _getMediaTypeProperty(element)
|
|
105
|
+
const mtValue = _getMediaTypeValue(req)
|
|
106
|
+
if (category === 'stream' && row[key] && mtProperty && mtValue) row[mtProperty] = mtValue
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
const processorFn = (errors, req) => {
|
|
@@ -144,6 +157,8 @@ const _pick = element => {
|
|
|
144
157
|
categories.push('uuid')
|
|
145
158
|
}
|
|
146
159
|
|
|
160
|
+
if (element['@Core.MediaType']) categories.push('stream')
|
|
161
|
+
|
|
147
162
|
if (categories.length) return { categories }
|
|
148
163
|
}
|
|
149
164
|
|
|
@@ -289,6 +304,7 @@ function _actionFunctionHandler(req) {
|
|
|
289
304
|
for (const row of arrayData) {
|
|
290
305
|
_processActionFunction(row, eventParams, errors, req.event, this)
|
|
291
306
|
}
|
|
307
|
+
|
|
292
308
|
_callError(req, errors)
|
|
293
309
|
}
|
|
294
310
|
|
|
@@ -302,18 +318,22 @@ module.exports = cds.service.impl(function () {
|
|
|
302
318
|
for (const operation of this.operations) {
|
|
303
319
|
operationNames.push(operation.name.substring(this.name.length + 1))
|
|
304
320
|
}
|
|
321
|
+
|
|
305
322
|
if (operationNames.length > 0) {
|
|
306
323
|
this.before(operationNames, _actionFunctionHandler)
|
|
307
324
|
}
|
|
308
325
|
|
|
309
326
|
for (const entity of this.entities) {
|
|
310
327
|
const boundOps = []
|
|
328
|
+
|
|
311
329
|
if (entity.actions) {
|
|
312
330
|
boundOps.push(...Object.keys(entity.actions))
|
|
313
331
|
}
|
|
332
|
+
|
|
314
333
|
if (entity.functions) {
|
|
315
334
|
boundOps.push(...Object.keys(entity.functions))
|
|
316
335
|
}
|
|
336
|
+
|
|
317
337
|
if (boundOps.length > 0) {
|
|
318
338
|
this.before(boundOps, entity.name, _actionFunctionHandler)
|
|
319
339
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
|
-
const { getDefaultPageSize } = require('../utils/page')
|
|
2
|
+
const { getDefaultPageSize, getMaxPageSize } = require('../utils/page')
|
|
3
3
|
|
|
4
4
|
const _handler = function (req) {
|
|
5
5
|
// only if http request
|
|
@@ -11,7 +11,7 @@ const _handler = function (req) {
|
|
|
11
11
|
let { rows, offset } = req.query.SELECT.limit || {}
|
|
12
12
|
rows = rows && 'val' in rows ? rows.val : getDefaultPageSize(req.target)
|
|
13
13
|
offset = offset && 'val' in offset ? offset.val : 0
|
|
14
|
-
req.query.limit(...[rows, offset])
|
|
14
|
+
req.query.limit(...[Math.min(rows, getMaxPageSize(req.target)), offset])
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -40,24 +40,18 @@ const processorFn = req => {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/* istanbul ignore else */
|
|
44
43
|
if (category === 'default') {
|
|
45
44
|
row[key] = args.val
|
|
46
|
-
} else if (category === 'null') {
|
|
47
|
-
|
|
45
|
+
} else if (category === 'null' && !element._isStructured) {
|
|
46
|
+
row[key] = null
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
// params: element, target, parent, templateElements
|
|
53
51
|
const _pick = element => {
|
|
54
52
|
if (!element.isAssociation && !element.key && !element._isReadOnly) {
|
|
55
|
-
|
|
56
|
-
if (element.
|
|
57
|
-
return { category: 'default', args: element.default }
|
|
58
|
-
} else if (!element.notNull) {
|
|
59
|
-
return { category: 'null' }
|
|
60
|
-
}
|
|
53
|
+
if (element.default) return { category: 'default', args: element.default }
|
|
54
|
+
if (!element.notNull) return { category: 'null' }
|
|
61
55
|
}
|
|
62
56
|
}
|
|
63
57
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const LOG = cds.log('app')
|
|
3
|
+
const { getAllKeys } = require('../../cds-services/adapter/odata-v4/odata-to-cqn/utils')
|
|
3
4
|
|
|
4
5
|
const DRAFT_COLUMNS = ['IsActiveEntity', 'HasDraftEntity', 'HasActiveEntity']
|
|
5
6
|
|
|
@@ -17,14 +18,11 @@ const _getStaticOrders = req => {
|
|
|
17
18
|
|
|
18
19
|
// implicit sorting?
|
|
19
20
|
if (cds.env.features.implicit_sorting !== false && (req.target._isSingleton || query.SELECT.limit)) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
!defaultOrders.some(o => o.by['='] === keyName)
|
|
26
|
-
)
|
|
27
|
-
ordersFromKeys.push({ by: { '=': keyName } })
|
|
21
|
+
const keys = getAllKeys(entity, true)
|
|
22
|
+
for (const key of keys) {
|
|
23
|
+
if (!DRAFT_COLUMNS.includes(key) && !defaultOrders.some(o => o.by['='] === key)) {
|
|
24
|
+
ordersFromKeys.push({ by: { '=': key } })
|
|
25
|
+
}
|
|
28
26
|
}
|
|
29
27
|
}
|
|
30
28
|
|
|
@@ -61,28 +59,12 @@ const _handler = function (req) {
|
|
|
61
59
|
if (select.groupBy && select.groupBy.length > 0) {
|
|
62
60
|
staticOrders = staticOrders.filter(d => select.groupBy.find(e => e.ref[0] === d.by['=']))
|
|
63
61
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const managedKey = orderBy.ref && orderBy.ref.length > 1 && orderBy.ref.join('_')
|
|
71
|
-
const element = managedKey && req.target.elements[managedKey]
|
|
72
|
-
const isManagedKey = element && element.key && !element.is2one
|
|
73
|
-
|
|
74
|
-
// don't add duplicates
|
|
75
|
-
return (
|
|
76
|
-
(orderBy.ref && orderBy.ref.length === 1 && orderBy.ref[0] === defaultOrder.by['=']) ||
|
|
77
|
-
(isManagedKey && managedKey === defaultOrder.by['='])
|
|
78
|
-
)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
if (!some) {
|
|
82
|
-
const orderByItem = { ref: [defaultOrder.by['=']], sort: defaultOrder.desc ? 'desc' : 'asc' }
|
|
83
|
-
select.orderBy.push(orderByItem)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
62
|
+
if (!staticOrders.length) return
|
|
63
|
+
;(select.orderBy || (select.orderBy = [])).push(
|
|
64
|
+
...staticOrders
|
|
65
|
+
.filter(d => !select.orderBy.find(o => o.ref && o.ref.join('_') === d.by['=']))
|
|
66
|
+
.map(d => ({ ref: [d.by['=']], sort: d.desc ? 'desc' : 'asc' }))
|
|
67
|
+
)
|
|
86
68
|
}
|
|
87
69
|
|
|
88
70
|
/**
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const { performance } = require('perf_hooks')
|
|
2
|
+
|
|
3
|
+
const _statisticsRequested = req =>
|
|
4
|
+
(req.query && req.query['sap-statistics'] === 'true') ||
|
|
5
|
+
(req.headers && req.headers['sap-statistics'] === 'true' && (!req.query || !req.query['sap-statistics']))
|
|
6
|
+
|
|
7
|
+
module.exports = app => {
|
|
8
|
+
if (app._perf_measured) return
|
|
9
|
+
else app._perf_measured = true
|
|
10
|
+
|
|
11
|
+
app.use((req, res, next) => {
|
|
12
|
+
if (!_statisticsRequested(req)) return next()
|
|
13
|
+
|
|
14
|
+
const t0 = performance.now()
|
|
15
|
+
const { writeHead } = res
|
|
16
|
+
res.writeHead = function (...args) {
|
|
17
|
+
const total = Number((performance.now() - t0) / 1000).toFixed(2)
|
|
18
|
+
if (res.statusCode < 400) res.setHeader('sap-statistics', `total=${total}`)
|
|
19
|
+
writeHead.call(this, ...args)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
next()
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -16,6 +16,15 @@ const getEntityNameFromUpdateCQN = cqn => {
|
|
|
16
16
|
return (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) || cqn.UPDATE.entity.name || cqn.UPDATE.entity
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const addToWhere = (cqn, where) => {
|
|
20
|
+
const partial = cqn.SELECT || cqn.UPDATE || cqn.DELETE
|
|
21
|
+
if (!partial.where) partial.where = where
|
|
22
|
+
else {
|
|
23
|
+
partial.where.unshift('(')
|
|
24
|
+
partial.where.push(')', 'and', '(', ...where, ')')
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
// scope: simple wheres à la "[{ ref: ['foo'] }, '=', { val: 'bar' }, 'and', ... ]"
|
|
20
29
|
function where2obj(where, target = null) {
|
|
21
30
|
const data = {}
|
|
@@ -34,8 +43,56 @@ function where2obj(where, target = null) {
|
|
|
34
43
|
return data
|
|
35
44
|
}
|
|
36
45
|
|
|
46
|
+
function targetFromPath(path, model) {
|
|
47
|
+
const { definitions } = model
|
|
48
|
+
let current
|
|
49
|
+
for (const r of path) {
|
|
50
|
+
if (r.id) {
|
|
51
|
+
current = current
|
|
52
|
+
? definitions[current.elements[r.id].target]
|
|
53
|
+
: definitions[r.id] || definitions[r.id.replace(/_drafts$/, '')]
|
|
54
|
+
} else {
|
|
55
|
+
const next = current.elements[r]
|
|
56
|
+
if (next.isAssociation) {
|
|
57
|
+
current = definitions[next.target]
|
|
58
|
+
} else {
|
|
59
|
+
current = next
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return current
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isPathToDraft(path, model) {
|
|
67
|
+
const { definitions } = model
|
|
68
|
+
let draft = false
|
|
69
|
+
let current
|
|
70
|
+
for (const r of path) {
|
|
71
|
+
if (r.id) {
|
|
72
|
+
const isaIndex = r.where.findIndex(ele => ele.ref && ele.ref[0] === 'IsActiveEntity')
|
|
73
|
+
if (isaIndex) draft = !r.where[isaIndex + 2].val
|
|
74
|
+
current = current ? definitions[current.elements[r.id].target] : definitions[r.id]
|
|
75
|
+
} else {
|
|
76
|
+
if (r === 'SiblingEntity') draft = !!draft
|
|
77
|
+
else {
|
|
78
|
+
const next = current.elements[r]
|
|
79
|
+
if (next.isAssociation) {
|
|
80
|
+
if (next._isAssociationStrict) draft = false
|
|
81
|
+
current = definitions[next.target]
|
|
82
|
+
} else {
|
|
83
|
+
current = next
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return draft
|
|
89
|
+
}
|
|
90
|
+
|
|
37
91
|
module.exports = {
|
|
92
|
+
addToWhere,
|
|
38
93
|
getEntityNameFromDeleteCQN,
|
|
39
94
|
getEntityNameFromUpdateCQN,
|
|
40
|
-
where2obj
|
|
95
|
+
where2obj,
|
|
96
|
+
targetFromPath,
|
|
97
|
+
isPathToDraft
|
|
41
98
|
}
|