@sap/cds 6.0.3 → 6.1.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 +165 -18
- package/apis/cds.d.ts +11 -7
- package/apis/log.d.ts +46 -0
- package/apis/ql.d.ts +72 -15
- 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/ql/Query.js +9 -3
- package/lib/ql/SELECT.js +2 -2
- package/lib/ql/{index.js → cds-ql.js} +0 -9
- 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} +1 -1
- 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 +1 -4
- 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/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/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 +56 -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/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/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 +9 -7
- 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 +84 -104
- package/srv/mtx.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +0 -240
|
@@ -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 = expandedEntity._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) {
|
|
@@ -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 }
|
|
@@ -18,9 +18,9 @@ const _addAnnotation = extension => {
|
|
|
18
18
|
})
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const _addViews = csn => {
|
|
21
|
+
const _addViews = (csn, appCsn) => {
|
|
22
22
|
csn.extensions.forEach(extension => {
|
|
23
|
-
const target =
|
|
23
|
+
const target = appCsn.definitions[extension.extend]
|
|
24
24
|
const views_ = []
|
|
25
25
|
const view = resolveViews(target, views_)
|
|
26
26
|
extension.extend = view && view.name
|
|
@@ -41,28 +41,32 @@ const _addViews = csn => {
|
|
|
41
41
|
})
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const _addExtension = async function (csn, req) {
|
|
45
|
-
|
|
46
|
-
await
|
|
44
|
+
const _addExtension = async function (csn, appCsn, req) {
|
|
45
|
+
if (req.tenant) cds.context = { tenant: req.tenant }
|
|
46
|
+
await cds.db.run(
|
|
47
47
|
INSERT.into('cds.xt.Extensions').entries([
|
|
48
48
|
{ ID: cds.utils.uuid(), tag: 'uiflex', csn: JSON.stringify(csn), activated: 'propertyBag' }
|
|
49
49
|
])
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
-
// defaults
|
|
53
52
|
for (const ext of req.data.extensions) {
|
|
54
53
|
const extension = JSON.parse(ext)
|
|
55
|
-
await handleDefaults(extension,
|
|
54
|
+
await handleDefaults(extension, appCsn, false)
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
const addExtension = async function (req) {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
const { 'cds.xt.ModelProviderService': mps } = cds.services
|
|
60
|
+
const csn = await mps.getCsn(req.tenant, ['*'])
|
|
61
|
+
const extCsn = _getCsn(req)
|
|
62
|
+
const njCsn = cds.compile.for['nodejs'](csn)
|
|
63
|
+
// REVISIT: Optimize the validations
|
|
64
|
+
// REVISIT: Why do we need njCsn?
|
|
65
|
+
validateCsn(extCsn, njCsn, req)
|
|
66
|
+
validateExtensionFields(extCsn, njCsn, req)
|
|
67
|
+
_addViews(extCsn, njCsn)
|
|
68
|
+
validateExtension(extCsn, csn, req)
|
|
69
|
+
await _addExtension(extCsn, njCsn, req)
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
module.exports = addExtension
|
|
@@ -1,38 +1,33 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
|
-
|
|
3
|
-
const { ensureDraftsSuffix } = require('../common/utils/draft')
|
|
4
2
|
const resolveViews = require('./views')
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const draft = _getDraftTable(extension.extend)
|
|
20
|
-
const ext = Object.keys(extension.elements)
|
|
21
|
-
.filter(key => extension.elements[key].default)
|
|
3
|
+
const builtin = cds.builtin.types
|
|
4
|
+
const needsQuotes = Symbol()
|
|
5
|
+
builtin.string[needsQuotes] = true
|
|
6
|
+
builtin.date[needsQuotes] = true
|
|
7
|
+
|
|
8
|
+
const handleDefaults = async (extension, { definitions }, checkDb = true) => {
|
|
9
|
+
const target = definitions[extension.extend]
|
|
10
|
+
const entity = resolveViews(target)
|
|
11
|
+
if (checkDb && target !== entity) return // only db entities
|
|
12
|
+
|
|
13
|
+
const elements = extension.elements
|
|
14
|
+
const defaults = Object.keys(elements)
|
|
15
|
+
.filter(key => elements[key].default)
|
|
22
16
|
.map(key => {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const t =
|
|
26
|
-
|
|
27
|
-
return `"${key}":${value}`
|
|
17
|
+
const e = elements[key],
|
|
18
|
+
{ val } = e.default
|
|
19
|
+
const t = definitions[e.type] || builtin[e.type]
|
|
20
|
+
return `"${key}":${t && t[needsQuotes] ? `"${val}"` : val}`
|
|
28
21
|
})
|
|
29
22
|
|
|
30
|
-
if (
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
if (defaults.length) {
|
|
24
|
+
const newDefaults = defaults.join(',')
|
|
25
|
+
const newBagpack = `extensions__ = CASE
|
|
26
|
+
WHEN extensions__ is null THEN '{${newDefaults}}'
|
|
27
|
+
ELSE '{${newDefaults},' || substr(extensions__, 2, length(extensions__)-1)
|
|
28
|
+
END`
|
|
29
|
+
await Promise.all([UPDATE(entity).with(newBagpack), entity.drafts && UPDATE(entity.drafts).with(newBagpack)])
|
|
30
|
+
// NOTE: We don't need the model definitions for `entity` in cds.db.model to run these queries
|
|
36
31
|
}
|
|
37
32
|
}
|
|
38
33
|
|
|
@@ -42,23 +42,25 @@ const _removeExtendedFields = (columns, extFields, alias) => {
|
|
|
42
42
|
|
|
43
43
|
const _transformUnion = (req, model) => {
|
|
44
44
|
// second element is active entity
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
45
|
+
if (req.target) {
|
|
46
|
+
const name = req.target.name.SET ? req.target.name.SET.args[1]._target.name : req.target.name
|
|
47
|
+
const extFields = getExtendedFields(name, model)
|
|
48
|
+
_addBackPack(req.query.SELECT.columns, extFields)
|
|
49
|
+
_removeExtendedFields(req.query.SELECT.columns, extFields)
|
|
50
|
+
|
|
51
|
+
_addBackPack(
|
|
52
|
+
req.query.SELECT.from.SET.args[0].SELECT.columns,
|
|
53
|
+
extFields,
|
|
54
|
+
req.query.SELECT.from.SET.args[0].SELECT.from.args[0].as
|
|
55
|
+
)
|
|
56
|
+
_addBackPack(req.query.SELECT.from.SET.args[1].SELECT.columns, extFields)
|
|
57
|
+
_removeExtendedFields(
|
|
58
|
+
req.query.SELECT.from.SET.args[0].SELECT.columns,
|
|
59
|
+
extFields,
|
|
60
|
+
req.query.SELECT.from.SET.args[0].SELECT.from.args[0].as
|
|
61
|
+
)
|
|
62
|
+
_removeExtendedFields(req.query.SELECT.from.SET.args[1].SELECT.columns, extFields)
|
|
63
|
+
}
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
const _getAliasedEntitiesForJoin = (args, model) => {
|
|
@@ -111,7 +113,7 @@ function transformExtendedFieldsREAD(req) {
|
|
|
111
113
|
_transformColumns(req.query.SELECT.columns, target.name, this.model)
|
|
112
114
|
|
|
113
115
|
if (req.query.SELECT.from.SET) return _transformUnion(req, this.model) // union
|
|
114
|
-
if (req.query.SELECT.from.join) return _transformJoin(req, this.model) // join
|
|
116
|
+
if (req.query.SELECT.from.join && req.query.SELECT.from.args) return _transformJoin(req, this.model) // join
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
module.exports = {
|