@sap/cds 6.0.4 → 6.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +180 -18
- package/apis/cds.d.ts +11 -7
- package/apis/log.d.ts +124 -0
- package/apis/ql.d.ts +72 -15
- package/apis/services.d.ts +13 -2
- package/bin/build/buildTaskHandler.js +5 -2
- package/bin/build/constants.js +4 -1
- package/bin/build/provider/buildTaskHandlerEdmx.js +11 -39
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +14 -34
- package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
- package/bin/build/provider/buildTaskProviderInternal.js +22 -14
- package/bin/build/provider/hana/index.js +12 -9
- package/bin/build/provider/java/index.js +18 -8
- package/bin/build/provider/mtx/index.js +7 -4
- package/bin/build/provider/mtx/resourcesTarBuilder.js +60 -35
- package/bin/build/provider/mtx-extension/index.js +57 -0
- package/bin/build/provider/mtx-sidecar/index.js +46 -18
- package/bin/build/provider/nodejs/index.js +34 -13
- package/bin/deploy/to-hana/cfUtil.js +7 -2
- package/bin/deploy/to-hana/hana.js +20 -25
- package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
- package/bin/serve.js +7 -4
- package/lib/compile/{index.js → cds-compile.js} +0 -0
- package/lib/compile/extend.js +15 -5
- package/lib/compile/minify.js +1 -15
- package/lib/compile/parse.js +1 -1
- package/lib/compile/resolve.js +2 -2
- package/lib/compile/to/srvinfo.js +6 -4
- package/lib/{deploy.js → dbs/cds-deploy.js} +9 -8
- package/lib/env/{index.js → cds-env.js} +1 -17
- package/lib/env/{requires.js → cds-requires.js} +24 -3
- package/lib/env/defaults.js +7 -1
- package/lib/env/schemas/cds-package.json +11 -0
- package/lib/env/schemas/cds-rc.json +614 -0
- package/lib/index.js +19 -16
- package/lib/log/{errors.js → cds-error.js} +1 -1
- package/lib/log/{index.js → cds-log.js} +0 -0
- package/lib/log/format/kibana.js +19 -1
- package/lib/ql/Query.js +9 -3
- package/lib/ql/SELECT.js +2 -2
- package/lib/ql/UPDATE.js +2 -2
- package/lib/ql/{index.js → cds-ql.js} +4 -10
- package/lib/req/context.js +49 -17
- package/lib/req/locale.js +5 -1
- package/lib/{serve → srv}/adapters.js +23 -19
- package/lib/{connect → srv}/bindings.js +0 -0
- package/lib/{connect/index.js → srv/cds-connect.js} +1 -1
- package/lib/{serve/index.js → srv/cds-serve.js} +0 -0
- package/lib/{serve → srv}/factory.js +1 -1
- package/lib/{serve/Service-api.js → srv/srv-api.js} +22 -6
- package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +13 -8
- package/lib/{serve/Service-handlers.js → srv/srv-handlers.js} +10 -0
- package/lib/{serve/Service-methods.js → srv/srv-methods.js} +10 -8
- package/lib/srv/srv-models.js +207 -0
- package/lib/{serve/Transaction.js → srv/srv-tx.js} +57 -40
- package/lib/utils/{tests.js → cds-test.js} +2 -2
- package/lib/utils/cds-utils.js +146 -0
- package/lib/utils/index.js +2 -145
- package/lib/utils/jest.js +43 -0
- package/lib/utils/resources/index.js +15 -25
- package/lib/utils/resources/tar.js +18 -41
- package/libx/_runtime/auth/index.js +14 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +7 -19
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +6 -19
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +3 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +38 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -3
- package/libx/_runtime/cds-services/services/utils/differ.js +4 -0
- package/libx/_runtime/cds-services/util/errors.js +1 -29
- package/libx/_runtime/common/i18n/messages.properties +2 -1
- package/libx/_runtime/common/perf/index.js +10 -15
- package/libx/_runtime/common/utils/binary.js +3 -4
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +0 -1
- package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
- package/libx/_runtime/common/utils/keys.js +14 -6
- package/libx/_runtime/common/utils/resolveView.js +1 -1
- package/libx/_runtime/common/utils/template.js +1 -1
- package/libx/_runtime/db/Service.js +2 -14
- package/libx/_runtime/db/expand/expandCQNToJoin.js +28 -25
- package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
- package/libx/_runtime/db/generic/input.js +8 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
- package/libx/_runtime/extensibility/activate.js +47 -47
- package/libx/_runtime/extensibility/add.js +22 -13
- package/libx/_runtime/extensibility/addExtension.js +17 -13
- package/libx/_runtime/extensibility/defaults.js +25 -30
- package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
- package/libx/_runtime/extensibility/linter/allowlist_checker.js +373 -0
- package/libx/_runtime/extensibility/linter/annotations_checker.js +113 -0
- package/libx/_runtime/extensibility/linter/checker_base.js +20 -0
- package/libx/_runtime/extensibility/linter/namespace_checker.js +180 -0
- package/libx/_runtime/extensibility/linter.js +32 -0
- package/libx/_runtime/extensibility/push.js +77 -20
- package/libx/_runtime/extensibility/service.js +29 -12
- package/libx/_runtime/extensibility/token.js +57 -0
- package/libx/_runtime/extensibility/utils.js +8 -6
- package/libx/_runtime/extensibility/validation.js +6 -9
- package/libx/_runtime/fiori/generic/new.js +0 -11
- package/libx/_runtime/fiori/utils/where.js +1 -1
- package/libx/_runtime/hana/Service.js +0 -1
- package/libx/_runtime/hana/conversion.js +12 -1
- package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +4 -3
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -0
- package/libx/_runtime/hana/pool.js +6 -10
- package/libx/_runtime/hana/search2Contains.js +0 -5
- package/libx/_runtime/hana/search2cqn4sql.js +1 -0
- package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +1 -2
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/service.js +11 -6
- package/libx/_runtime/remote/utils/data.js +5 -0
- package/libx/_runtime/sqlite/Service.js +7 -6
- package/libx/_runtime/sqlite/execute.js +41 -28
- package/libx/odata/afterburner.js +79 -2
- package/libx/odata/cqn2odata.js +15 -9
- package/libx/odata/grammar.pegjs +157 -76
- package/libx/odata/index.js +9 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +39 -5
- package/libx/rest/RestAdapter.js +3 -7
- package/libx/rest/middleware/delete.js +4 -5
- package/libx/rest/middleware/parse.js +3 -2
- package/package.json +3 -3
- package/server.js +1 -1
- package/srv/extensibility-service.cds +6 -3
- package/srv/model-provider.cds +3 -1
- package/srv/model-provider.js +86 -106
- package/srv/mtx.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +0 -240
|
@@ -4,21 +4,16 @@ const _statisticsRequested = req =>
|
|
|
4
4
|
(req.query && req.query['sap-statistics'] === 'true') ||
|
|
5
5
|
(req.headers && req.headers['sap-statistics'] === 'true' && (!req.query || !req.query['sap-statistics']))
|
|
6
6
|
|
|
7
|
-
module.exports =
|
|
8
|
-
if (
|
|
9
|
-
else app._perf_measured = true
|
|
7
|
+
module.exports = function sap_statistics(req, res, next) {
|
|
8
|
+
if (!_statisticsRequested(req)) return next()
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const t0 = performance.now()
|
|
11
|
+
const { writeHead } = res
|
|
12
|
+
res.writeHead = function (...args) {
|
|
13
|
+
const total = Number((performance.now() - t0) / 1000).toFixed(2)
|
|
14
|
+
if (res.statusCode < 400) res.setHeader('sap-statistics', `total=${total}`)
|
|
15
|
+
writeHead.call(this, ...args)
|
|
16
|
+
}
|
|
13
17
|
|
|
14
|
-
|
|
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
|
-
})
|
|
18
|
+
next()
|
|
24
19
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
const getTemplate = require('./template')
|
|
2
2
|
const templateProcessor = require('./templateProcessor')
|
|
3
3
|
|
|
4
|
-
const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}={0,1}|[A-Za-z0-9+/]{2}={0,2})$/
|
|
5
|
-
|
|
6
4
|
// convert the standard base64 encoding to the URL-safe variant
|
|
7
5
|
const toBase64url = value =>
|
|
8
6
|
(Buffer.isBuffer(value) ? value.toString('base64') : value).replace(/\//g, '_').replace(/\+/g, '-')
|
|
@@ -17,12 +15,13 @@ const isInvalidBase64string = value => {
|
|
|
17
15
|
if (Buffer.isBuffer(value)) return // ok
|
|
18
16
|
|
|
19
17
|
// convert to standard base64 string; let it crash if typeof value !== 'string'
|
|
20
|
-
const
|
|
18
|
+
const base64value = value.replace(/_/g, '/').replace(/-/g, '+')
|
|
21
19
|
const normalized = normalizeBase64string(value)
|
|
22
20
|
|
|
23
21
|
// example of invalid base64 string --> 'WTGTdDsD/k21LnFRb+uNcAi=' <-- '...i=' must be '...g='
|
|
24
22
|
// see https://datatracker.ietf.org/doc/html/rfc4648#section-4
|
|
25
|
-
|
|
23
|
+
if (base64value.replace(/=/g, '') !== normalized.replace(/=/g, '')) return true
|
|
24
|
+
return base64value.length > normalized.length
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
const _picker = element => {
|
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
|
|
2
2
|
|
|
3
|
-
const traverseFroms = (cqn, cb) => {
|
|
3
|
+
const traverseFroms = (cqn, cb, aliasForSet) => {
|
|
4
4
|
while (cqn.SELECT) cqn = cqn.SELECT.from
|
|
5
5
|
|
|
6
6
|
// Do the most likely first -> {ref}
|
|
7
7
|
if (cqn.ref) {
|
|
8
|
-
return cb(cqn)
|
|
8
|
+
return cb(cqn, aliasForSet)
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
if (cqn.SET) {
|
|
12
|
-
|
|
12
|
+
// if a union has an alias, we should use it for the columns we get out of the union
|
|
13
|
+
return cqn.SET.args.map(a => traverseFroms(a, cb, cqn.as))
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
if (cqn.join) {
|
|
16
|
-
return cqn.args.map(a => traverseFroms(a, cb))
|
|
17
|
+
return cqn.args.map(a => traverseFroms(a, cb, aliasForSet))
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
const getEntityNameFromCQN = cqn => {
|
|
21
22
|
const res = []
|
|
22
|
-
traverseFroms(cqn,
|
|
23
|
+
traverseFroms(cqn, (from, aliasForSet) =>
|
|
24
|
+
res.push({ entityName: from.ref[0].id || from.ref[0], alias: aliasForSet || from.as })
|
|
25
|
+
)
|
|
23
26
|
return res.length === 1 ? res[0] : res.find(n => n.entityName !== 'DRAFT.DraftAdministrativeData') || {}
|
|
24
27
|
}
|
|
25
28
|
|
|
@@ -27,13 +27,18 @@ function _getOnCondElements(onCond, onCondElements = []) {
|
|
|
27
27
|
return onCondElements
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
function
|
|
31
|
-
if (
|
|
30
|
+
function _mergeWhere(base, additional) {
|
|
31
|
+
if (additional?.length) {
|
|
32
32
|
// copy where else query will be modified
|
|
33
|
-
const whereCopy = deepCopyArray(
|
|
34
|
-
if (
|
|
35
|
-
|
|
33
|
+
const whereCopy = deepCopyArray(additional)
|
|
34
|
+
if (base.length > 0) base.push('and')
|
|
35
|
+
base.push(...whereCopy)
|
|
36
36
|
}
|
|
37
|
+
return base
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function _modifyWhereWithNavigations(where, newWhere, entityKey, targetKey) {
|
|
41
|
+
_mergeWhere(newWhere, where)
|
|
37
42
|
|
|
38
43
|
newWhere.forEach(element => {
|
|
39
44
|
if (element.ref && element.ref[0] === targetKey) {
|
|
@@ -85,7 +90,10 @@ function _getWhereFromUpdate(query, target, model) {
|
|
|
85
90
|
return where
|
|
86
91
|
}
|
|
87
92
|
|
|
88
|
-
|
|
93
|
+
const where = query.UPDATE.where || []
|
|
94
|
+
if (query.UPDATE.entity.ref?.length === 1 && query.UPDATE.entity.ref[0].where)
|
|
95
|
+
return _mergeWhere(query.UPDATE.entity.ref[0].where, where)
|
|
96
|
+
return where
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
// params: data, req, service/tx
|
|
@@ -612,7 +612,7 @@ const _newQuery = (query, event, model, service) => {
|
|
|
612
612
|
}[event]
|
|
613
613
|
const newQuery = Object.create(query)
|
|
614
614
|
const transitions = _entityTransitionsForTarget(query[event][_prop], model, service)
|
|
615
|
-
newQuery[event] = (transitions[0] && _func(newQuery, transitions, service)) || { ...query[event] }
|
|
615
|
+
newQuery[event] = (transitions?.[0] && _func(newQuery, transitions, service)) || { ...query[event] }
|
|
616
616
|
return newQuery
|
|
617
617
|
}
|
|
618
618
|
|
|
@@ -134,7 +134,7 @@ const getCache = (anything, cache, newCacheFn) => {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
module.exports = (usecase, tx, target, ...args) => {
|
|
137
|
-
// get model first as it may be added to tx (cf. "_ensureModel")
|
|
137
|
+
// get model first as it may be added to tx (cf. "_ensureModel") // REVISIT: _ensureModel is gone
|
|
138
138
|
const model = tx.model
|
|
139
139
|
if (!model) return
|
|
140
140
|
|
|
@@ -24,28 +24,16 @@ class DatabaseService extends cds.Service {
|
|
|
24
24
|
this[`_${each}`] = generic[each]
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// REVISIT: ensures tenant-aware this.model if this is a transaction -> this should be fixed in mtx integration, not here
|
|
28
|
-
this._ensureModel = function (req) {
|
|
29
|
-
if (this.context) {
|
|
30
|
-
// if the tx was initiated in messaging, then this.context._model is not unfolded
|
|
31
|
-
// -> use this.context._model._4odata if present
|
|
32
|
-
const { _model } = this.context
|
|
33
|
-
if (_model) this.model = _model._4odata || _model
|
|
34
|
-
else this.model = req._model
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
this._ensureModel._initial = true
|
|
38
|
-
|
|
39
27
|
// REVISIT: how to generic handler registration?
|
|
40
28
|
}
|
|
41
29
|
|
|
42
30
|
/** Database services don't support custom-defined operations */
|
|
43
|
-
operations() {
|
|
31
|
+
get operations() {
|
|
44
32
|
return []
|
|
45
33
|
}
|
|
46
34
|
|
|
47
35
|
/** Database services don't support custom-defined events */
|
|
48
|
-
events() {
|
|
36
|
+
get events() {
|
|
49
37
|
return []
|
|
50
38
|
}
|
|
51
39
|
|
|
@@ -12,6 +12,8 @@ const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
|
|
|
12
12
|
|
|
13
13
|
const { filterKeys } = require('../../fiori/utils/handler')
|
|
14
14
|
|
|
15
|
+
const getError = require('../../common/error')
|
|
16
|
+
|
|
15
17
|
// Symbols are used to add extra information in response structure
|
|
16
18
|
const GET_KEY_VALUE = Symbol.for('sap.cds.getKeyValue')
|
|
17
19
|
const TO_MANY = Symbol.for('sap.cds.toMany')
|
|
@@ -94,6 +96,16 @@ class JoinCQNFromExpanded {
|
|
|
94
96
|
return this._isDraft
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
// There can be a limit/offset for the target entity.
|
|
100
|
+
// The current expand implementation applys a `DISTINCT` on
|
|
101
|
+
// `filterExpand`, which changes the sorting in absence of `ORDER BY`.
|
|
102
|
+
// Therefore, add an implicit `ORDER BY` in those cases.
|
|
103
|
+
_addImplicitOrderBy(cqn, entity, alias) {
|
|
104
|
+
if (cqn.orderBy || !cqn.limit || !entity) return // not needed
|
|
105
|
+
const orderByColumns = cqn.groupBy || getAllKeys(entity).map(key => ({ ref: [alias, key] }))
|
|
106
|
+
cqn.orderBy = orderByColumns
|
|
107
|
+
}
|
|
108
|
+
|
|
97
109
|
/**
|
|
98
110
|
* Build first level of expanding regarding to many and all to one if not part of a nested to many expand.
|
|
99
111
|
*
|
|
@@ -123,6 +135,7 @@ class JoinCQNFromExpanded {
|
|
|
123
135
|
})
|
|
124
136
|
// expand to one
|
|
125
137
|
const entity = this._csn.definitions[joinArgs[0].SELECT.from.SET.args[1].SELECT.from.ref[0]]
|
|
138
|
+
this._addImplicitOrderBy(readToOneCQN, entity, tableAlias)
|
|
126
139
|
const givenColumns = readToOneCQN.columns
|
|
127
140
|
readToOneCQN.columns = []
|
|
128
141
|
this._expandedToFlat({ entity, givenColumns, readToOneCQN, tableAlias, toManyTree, defaultLanguage })
|
|
@@ -130,7 +143,7 @@ class JoinCQNFromExpanded {
|
|
|
130
143
|
const table = unionTable || this._getRef(SELECT).table
|
|
131
144
|
const isDraftTree = this._isDraftTree(table)
|
|
132
145
|
const entity = this._getEntityForTable(table)
|
|
133
|
-
|
|
146
|
+
this._addImplicitOrderBy(readToOneCQN, entity, tableAlias)
|
|
134
147
|
if (unionTable) readToOneCQN[IS_UNION_DRAFT] = true
|
|
135
148
|
|
|
136
149
|
readToOneCQN[IS_ACTIVE] = isDraftTree ? this._isDraftTargetActive(table) : true
|
|
@@ -455,14 +468,10 @@ class JoinCQNFromExpanded {
|
|
|
455
468
|
|
|
456
469
|
if (element.ref) {
|
|
457
470
|
element.ref[1] = Object.assign({}, element.ref[1])
|
|
458
|
-
element.ref[1].args = element.ref[1].args.map(arg =>
|
|
459
|
-
return this._mapArg(arg, cqn, tableAlias)
|
|
460
|
-
})
|
|
471
|
+
element.ref[1].args = element.ref[1].args.map(arg => this._mapArg(arg, cqn, tableAlias))
|
|
461
472
|
} else {
|
|
462
473
|
element.args = element.args.slice(0)
|
|
463
|
-
element.args = element.args.map(arg =>
|
|
464
|
-
return this._mapArg(arg, cqn, tableAlias)
|
|
465
|
-
})
|
|
474
|
+
element.args = element.args.map(arg => this._mapArg(arg, cqn, tableAlias))
|
|
466
475
|
}
|
|
467
476
|
}
|
|
468
477
|
|
|
@@ -684,6 +693,17 @@ class JoinCQNFromExpanded {
|
|
|
684
693
|
c => !expandedEntity.keys[c].isAssociation && !(c in DRAFT_COLUMNS_MAP)
|
|
685
694
|
)
|
|
686
695
|
const user = (cds.context && cds.context.user && cds.context.user.id) || 'anonymous'
|
|
696
|
+
|
|
697
|
+
const assoc = entity.associations[column.ref[0]]
|
|
698
|
+
if (assoc.is2one && assoc.on) {
|
|
699
|
+
const onCond = entity._relations[assoc.name].join('target', 'source')
|
|
700
|
+
const xpr = onCond[0].xpr
|
|
701
|
+
const fks = (xpr && xpr.filter(e => e.ref && e.ref[0] === 'target').map(e => e.ref[1])) || []
|
|
702
|
+
for (const k of fks) {
|
|
703
|
+
if (!cols.includes(k)) cols.push(k)
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
687
707
|
const unionFrom = getCQNUnionFrom(cols, expandedEntity.name, expandedEntity.name + '.drafts', ks, user)
|
|
688
708
|
readToOneCQN.from.args[1] = {
|
|
689
709
|
SELECT: {
|
|
@@ -1162,7 +1182,7 @@ class JoinCQNFromExpanded {
|
|
|
1162
1182
|
cqn.orderBy = this._copyOrderBy(column.orderBy, tableAlias, expandedEntity)
|
|
1163
1183
|
}
|
|
1164
1184
|
|
|
1165
|
-
if (column.limit)
|
|
1185
|
+
if (column.limit) throw getError(501, 'Pagination is not supported in expand')
|
|
1166
1186
|
|
|
1167
1187
|
cqn = this._adaptWhereOrderBy(cqn, tableAlias)
|
|
1168
1188
|
|
|
@@ -1227,23 +1247,6 @@ class JoinCQNFromExpanded {
|
|
|
1227
1247
|
return assoc.target + '_drafts'
|
|
1228
1248
|
}
|
|
1229
1249
|
|
|
1230
|
-
_getLimitInSelect(cqn, columns, limit, orderBy, expandedEntity) {
|
|
1231
|
-
const select = {
|
|
1232
|
-
SELECT: {
|
|
1233
|
-
columns: this._copyColumns(columns, 'limitFilter'),
|
|
1234
|
-
from: { ref: [cqn.from.args[0].ref[0]], as: 'limitFilter' },
|
|
1235
|
-
where: this._convertOnToWhere(cqn.from.on, cqn.from.args[0].as, 'limitFilter'),
|
|
1236
|
-
limit: limit
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
if (orderBy) {
|
|
1241
|
-
select.SELECT.orderBy = this._copyOrderBy(orderBy, 'limitFilter', expandedEntity)
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
return select
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
1250
|
_isPathExpressionToOne(ref, entity) {
|
|
1248
1251
|
const ref0 = ref[0]
|
|
1249
1252
|
const el = entity.elements[ref0]
|
|
@@ -30,9 +30,13 @@ class RawToExpanded {
|
|
|
30
30
|
if (each._conversionMapper) for (const [k, v] of [...each._conversionMapper]) conversionMapper.set(k, v)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
const queryResults = await Promise.all(this._queries)
|
|
34
|
+
// NOTE: this doesn't work:
|
|
35
|
+
// for (let each of this._queries) await each
|
|
36
|
+
|
|
37
|
+
for (let i = 0, length = queryResults.length; i < length; i++) {
|
|
34
38
|
const { _toManyTree: toManyTree = [] } = queries[i]
|
|
35
|
-
const result =
|
|
39
|
+
const result = queryResults[i]
|
|
36
40
|
if (toManyTree.length === 0) {
|
|
37
41
|
this._parseMainResult(result, mappings, conversionMapper, toManyTree)
|
|
38
42
|
} else {
|
|
@@ -249,10 +253,7 @@ class RawToExpanded {
|
|
|
249
253
|
* @returns {Promise<Array>} The complete expanded result set.
|
|
250
254
|
*/
|
|
251
255
|
const rawToExpanded = (configs, queries, one, rootEntity) => {
|
|
252
|
-
return new RawToExpanded(configs, queries, one, rootEntity).toExpanded()
|
|
253
|
-
Promise.all(queries).catch(() => {})
|
|
254
|
-
throw err
|
|
255
|
-
})
|
|
256
|
+
return new RawToExpanded(configs, queries, one, rootEntity).toExpanded()
|
|
256
257
|
}
|
|
257
258
|
|
|
258
259
|
module.exports = rawToExpanded
|
|
@@ -176,7 +176,14 @@ const _pickDraft = element => {
|
|
|
176
176
|
// collect actions to apply
|
|
177
177
|
const categories = []
|
|
178
178
|
|
|
179
|
-
if (element
|
|
179
|
+
if (_isVirtualOrCalculated(element)) {
|
|
180
|
+
categories.push('virtual')
|
|
181
|
+
return { categories } // > no need to continue
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (element.default && !DRAFT_COLUMNS_MAP[element.name]) {
|
|
185
|
+
categories.push({ category: 'default', args: element })
|
|
186
|
+
}
|
|
180
187
|
|
|
181
188
|
// REVISIT: element._foreignKeys.length seems to be a very broad check
|
|
182
189
|
if (element.isAssociation && element._foreignKeys.length) {
|
|
@@ -306,7 +306,7 @@ class InsertBuilder extends BaseBuilder {
|
|
|
306
306
|
const purelyManagedColumnValues = this._getAnnotatedInsertColumnValues(annotatedColumns, purelyManagedColumns)
|
|
307
307
|
|
|
308
308
|
this._addUuidToColumns(columns, flattenColumnMap)
|
|
309
|
-
columns.push(...flattenColumnMap.keys())
|
|
309
|
+
columns.push(...Array.from(flattenColumnMap.keys()).filter(k => !columns.includes(k)))
|
|
310
310
|
|
|
311
311
|
this._addEntries(valuesArray, { columns, flattenColumnMap, purelyManagedColumnValues, insertAnnotatedColumns })
|
|
312
312
|
|
|
@@ -98,9 +98,7 @@ class SelectBuilder extends BaseBuilder {
|
|
|
98
98
|
this._orderBy(noQuoting)
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
this._limit()
|
|
103
|
-
}
|
|
101
|
+
this._limit()
|
|
104
102
|
|
|
105
103
|
if (this._obj.SELECT.forUpdate) {
|
|
106
104
|
this._forUpdate()
|
|
@@ -322,11 +320,8 @@ class SelectBuilder extends BaseBuilder {
|
|
|
322
320
|
|
|
323
321
|
_where() {
|
|
324
322
|
const entityName = this._obj.SELECT.from.ref && this._obj.SELECT.from.ref[0]
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
entityName ? { ...this._options, entityName } : this._options,
|
|
328
|
-
this._csn
|
|
329
|
-
).build()
|
|
323
|
+
const options = entityName ? { ...this._options, entityName } : this._options
|
|
324
|
+
const where = new this.ExpressionBuilder(this._obj.SELECT.where, options, this._csn).build()
|
|
330
325
|
this._outputObj.sql.push('WHERE', where.sql)
|
|
331
326
|
this._outputObj.values.push(...where.values)
|
|
332
327
|
}
|
|
@@ -415,6 +410,34 @@ class SelectBuilder extends BaseBuilder {
|
|
|
415
410
|
this._outputObj.sql.push(sqls.join(', '))
|
|
416
411
|
}
|
|
417
412
|
|
|
413
|
+
_addRows() {
|
|
414
|
+
if (this._obj.SELECT.limit) {
|
|
415
|
+
if (this._obj.SELECT.limit.rows !== undefined) {
|
|
416
|
+
// limit (no placeholder for statement caching)
|
|
417
|
+
this._outputObj.sql.push('LIMIT', this._obj.SELECT.limit.rows.val)
|
|
418
|
+
} else {
|
|
419
|
+
// rows parameter is mandatory for SQL
|
|
420
|
+
throw new Error('Rows parameter is missing in SELECT.limit(rows, offset)')
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
_addOne() {
|
|
426
|
+
this._outputObj.sql.push('LIMIT', 1)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
_addOffset() {
|
|
430
|
+
// offset
|
|
431
|
+
if (this._obj.SELECT.limit && this._obj.SELECT.limit.offset !== undefined) {
|
|
432
|
+
if (typeof this._obj.SELECT.limit.offset.val === 'number' && !this._parameterizedNumbers) {
|
|
433
|
+
this._outputObj.sql.push('OFFSET', this._obj.SELECT.limit.offset.val)
|
|
434
|
+
} else {
|
|
435
|
+
this._outputObj.sql.push('OFFSET', '?')
|
|
436
|
+
this._outputObj.values.push(this._obj.SELECT.limit.offset.val)
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
418
441
|
/**
|
|
419
442
|
* sql limit clause will be generated without placeholders.
|
|
420
443
|
* reason is optimizing paging queries. number of rows does not change.
|
|
@@ -423,17 +446,13 @@ class SelectBuilder extends BaseBuilder {
|
|
|
423
446
|
* offset will still use placeholders, as it'll change during the paging queries.
|
|
424
447
|
*/
|
|
425
448
|
_limit() {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (typeof this._obj.SELECT.limit.offset.val === 'number' && !this._parameterizedNumbers) {
|
|
431
|
-
this._outputObj.sql.push('OFFSET', this._obj.SELECT.limit.offset.val)
|
|
432
|
-
} else {
|
|
433
|
-
this._outputObj.sql.push('OFFSET', '?')
|
|
434
|
-
this._outputObj.values.push(this._obj.SELECT.limit.offset.val)
|
|
435
|
-
}
|
|
449
|
+
if (this._obj.SELECT.one) {
|
|
450
|
+
this._addOne()
|
|
451
|
+
} else {
|
|
452
|
+
this._addRows()
|
|
436
453
|
}
|
|
454
|
+
|
|
455
|
+
this._addOffset()
|
|
437
456
|
}
|
|
438
457
|
|
|
439
458
|
_parameters() {
|
|
@@ -1,69 +1,69 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
2
|
|
|
3
3
|
const handleDefaults = require('./defaults')
|
|
4
|
+
const Extensions = 'cds.xt.Extensions'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
// REVISIT: Reuse ratio = 0
|
|
7
|
+
const _calculateExtensions = async function (ID, tag) {
|
|
6
8
|
let active, inactive
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
inactiveCqn.where('ID !=', ID)
|
|
12
|
-
} else {
|
|
13
|
-
inactiveCqn.where('tag !=', tag)
|
|
14
|
-
}
|
|
15
|
-
inactive = await tx.run(inactiveCqn)
|
|
16
|
-
const activeCqn = SELECT.from('cds.xt.Extensions').where({ activated: 'database' })
|
|
17
|
-
if (ID) {
|
|
18
|
-
activeCqn.or({ ID })
|
|
19
|
-
} else {
|
|
20
|
-
if (tag) activeCqn.or({ tag })
|
|
21
|
-
}
|
|
22
|
-
active = await tx.run(activeCqn)
|
|
23
|
-
if (inactive.length) {
|
|
24
|
-
const deleteCqn = DELETE.from('cds.xt.Extensions').where(inactiveCqn.SELECT.where)
|
|
25
|
-
await tx.run(deleteCqn)
|
|
26
|
-
}
|
|
9
|
+
if (tag || ID) {
|
|
10
|
+
const inactiveCqn = SELECT.from(Extensions).where({ activated: 'propertyBag' })
|
|
11
|
+
if (ID) {
|
|
12
|
+
inactiveCqn.where('ID !=', ID)
|
|
27
13
|
} else {
|
|
28
|
-
|
|
29
|
-
inactive = []
|
|
30
|
-
active = await tx.run(SELECT.from('cds.xt.Extensions'))
|
|
14
|
+
inactiveCqn.where('(tag !=', tag, 'or tag =', null, ')')
|
|
31
15
|
}
|
|
32
|
-
|
|
16
|
+
inactive = await cds.db.run(inactiveCqn)
|
|
17
|
+
const activeCqn = SELECT.from(Extensions).where({ activated: 'database' })
|
|
18
|
+
if (ID) {
|
|
19
|
+
activeCqn.or({ ID })
|
|
20
|
+
} else if (tag) {
|
|
21
|
+
activeCqn.or({ tag })
|
|
22
|
+
}
|
|
23
|
+
active = await cds.db.run(activeCqn)
|
|
24
|
+
if (inactive.length) {
|
|
25
|
+
const deleteCqn = DELETE.from(Extensions).where(inactiveCqn.SELECT.where)
|
|
26
|
+
await cds.db.run(deleteCqn)
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
// activate all
|
|
30
|
+
inactive = []
|
|
31
|
+
active = await cds.db.run(SELECT.from(Extensions))
|
|
32
|
+
}
|
|
33
33
|
|
|
34
34
|
return { active, inactive }
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
37
|
+
// REVISIT: Reuse ratio = 0
|
|
38
|
+
const _restoreExtensions = async function (active, inactive, appCsn) {
|
|
39
|
+
// delete all extensions
|
|
40
|
+
await cds.db.run(DELETE.from(Extensions))
|
|
41
|
+
// active
|
|
42
|
+
active.forEach(row => {
|
|
43
|
+
row.csn = row.csn.replace(/,"@cds.extension":true/g, '')
|
|
44
|
+
row.activated = 'database'
|
|
45
|
+
row.timestamp = '$now'
|
|
46
|
+
})
|
|
47
|
+
await cds.db.run(INSERT.into(Extensions).entries(active))
|
|
48
|
+
// inactive
|
|
49
|
+
if (inactive.length) {
|
|
50
|
+
for (const na of inactive) {
|
|
51
|
+
for (const extension of JSON.parse(na.csn).extensions) {
|
|
52
|
+
await handleDefaults(extension, appCsn, cds.db)
|
|
54
53
|
}
|
|
55
|
-
await tx.run(INSERT.into('cds.xt.Extensions').entries(inactive))
|
|
56
54
|
}
|
|
57
|
-
|
|
55
|
+
await cds.db.run(INSERT.into(Extensions).entries(inactive))
|
|
56
|
+
}
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
// REVISIT: Review with Vitaly: (1) Delete Inactives > (2) DS.extend(t) > (3) Delete All > (4) Restore All ???
|
|
60
|
+
const activate = async function (ID, tag, tenant, appCsn) {
|
|
61
|
+
const { active, inactive } = await _calculateExtensions(ID, tag)
|
|
62
62
|
|
|
63
63
|
const { 'cds.xt.DeploymentService': ds } = cds.services
|
|
64
64
|
await ds.extend(tenant)
|
|
65
65
|
|
|
66
|
-
await _restoreExtensions(
|
|
66
|
+
await _restoreExtensions(active, inactive, appCsn)
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
module.exports = activate
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
|
+
const LOG = cds.log('mtx')
|
|
2
3
|
|
|
3
4
|
const { validateExtension } = require('./validation')
|
|
4
5
|
const handleDefaults = require('./defaults')
|
|
@@ -11,21 +12,26 @@ const add = async function (req) {
|
|
|
11
12
|
if (!extension || !extension.length) req.reject(400, 'Missing extension')
|
|
12
13
|
if (!activate) activate = 'database'
|
|
13
14
|
if (!tag) tag = null
|
|
14
|
-
const tenant =
|
|
15
|
+
const tenant = (req.user.is('internal-user') && req.data.tenant) || req.tenant
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
18
|
-
await validateExtension(csn, tenant, req)
|
|
17
|
+
const extCsn = _isCSN(extension) ? JSON.parse(extension) : cds.parse.cdl(extension)
|
|
18
|
+
if (extCsn.requires) delete extCsn.requires
|
|
19
19
|
|
|
20
|
+
LOG.info(`validating extension '${tag}' ...`)
|
|
21
|
+
const { 'cds.xt.ModelProviderService': mps } = cds.services
|
|
22
|
+
const csn = await mps.getCsn(tenant, ['*'])
|
|
23
|
+
validateExtension(extCsn, csn, req)
|
|
24
|
+
|
|
25
|
+
if (tenant) cds.context = { tenant }
|
|
20
26
|
const ID = cds.utils.uuid()
|
|
21
|
-
await cds.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (activate === '
|
|
27
|
-
await
|
|
28
|
-
|
|
27
|
+
await cds.db.run(
|
|
28
|
+
INSERT.into('cds.xt.Extensions').entries([{ ID, tag, csn: JSON.stringify(extCsn), activated: activate }])
|
|
29
|
+
)
|
|
30
|
+
const njCsn = cds.compile.for.nodejs(csn)
|
|
31
|
+
LOG.info(`activating extension to '${activate}' ...`)
|
|
32
|
+
if (activate === 'propertyBag' && extCsn.extensions)
|
|
33
|
+
extCsn.extensions.forEach(async ext => await handleDefaults(ext, njCsn))
|
|
34
|
+
if (activate === 'database') await activateExt(ID, tag, tenant, njCsn)
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
const promote = async function (req) {
|
|
@@ -35,7 +41,10 @@ const promote = async function (req) {
|
|
|
35
41
|
const tenant = req.tenant || (req.user.is('internal-user') && req.data.tenant) || ''
|
|
36
42
|
if (activate !== 'database') req.reject(400, 'Promote to propertyBag is not supported')
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
const { 'cds.xt.ModelProviderService': mps } = cds.services
|
|
45
|
+
const njCsn = await mps.getCsn(tenant, ['*'], 'nodejs')
|
|
46
|
+
if (tenant) cds.context = { tenant }
|
|
47
|
+
await activateExt(null, tag, tenant, njCsn)
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
module.exports = { add, promote }
|