@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.
Files changed (131) hide show
  1. package/CHANGELOG.md +165 -18
  2. package/apis/cds.d.ts +11 -7
  3. package/apis/log.d.ts +46 -0
  4. package/apis/ql.d.ts +72 -15
  5. package/bin/build/buildTaskHandler.js +5 -2
  6. package/bin/build/constants.js +4 -1
  7. package/bin/build/provider/buildTaskHandlerEdmx.js +11 -39
  8. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +14 -34
  9. package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
  10. package/bin/build/provider/buildTaskProviderInternal.js +22 -14
  11. package/bin/build/provider/hana/index.js +12 -9
  12. package/bin/build/provider/java/index.js +18 -8
  13. package/bin/build/provider/mtx/index.js +7 -4
  14. package/bin/build/provider/mtx/resourcesTarBuilder.js +60 -35
  15. package/bin/build/provider/mtx-extension/index.js +57 -0
  16. package/bin/build/provider/mtx-sidecar/index.js +46 -18
  17. package/bin/build/provider/nodejs/index.js +34 -13
  18. package/bin/deploy/to-hana/cfUtil.js +7 -2
  19. package/bin/deploy/to-hana/hana.js +20 -25
  20. package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
  21. package/bin/serve.js +7 -4
  22. package/lib/compile/{index.js → cds-compile.js} +0 -0
  23. package/lib/compile/extend.js +15 -5
  24. package/lib/compile/minify.js +1 -15
  25. package/lib/compile/parse.js +1 -1
  26. package/lib/compile/resolve.js +2 -2
  27. package/lib/compile/to/srvinfo.js +6 -4
  28. package/lib/{deploy.js → dbs/cds-deploy.js} +9 -8
  29. package/lib/env/{index.js → cds-env.js} +1 -17
  30. package/lib/env/{requires.js → cds-requires.js} +24 -3
  31. package/lib/env/defaults.js +7 -1
  32. package/lib/env/schemas/cds-package.json +11 -0
  33. package/lib/env/schemas/cds-rc.json +614 -0
  34. package/lib/index.js +19 -16
  35. package/lib/log/{errors.js → cds-error.js} +1 -1
  36. package/lib/log/{index.js → cds-log.js} +0 -0
  37. package/lib/ql/Query.js +9 -3
  38. package/lib/ql/SELECT.js +2 -2
  39. package/lib/ql/{index.js → cds-ql.js} +0 -9
  40. package/lib/req/context.js +49 -17
  41. package/lib/req/locale.js +5 -1
  42. package/lib/{serve → srv}/adapters.js +23 -19
  43. package/lib/{connect → srv}/bindings.js +0 -0
  44. package/lib/{connect/index.js → srv/cds-connect.js} +1 -1
  45. package/lib/{serve/index.js → srv/cds-serve.js} +1 -1
  46. package/lib/{serve → srv}/factory.js +1 -1
  47. package/lib/{serve/Service-api.js → srv/srv-api.js} +22 -6
  48. package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +13 -8
  49. package/lib/{serve/Service-handlers.js → srv/srv-handlers.js} +10 -0
  50. package/lib/{serve/Service-methods.js → srv/srv-methods.js} +10 -8
  51. package/lib/srv/srv-models.js +207 -0
  52. package/lib/{serve/Transaction.js → srv/srv-tx.js} +57 -40
  53. package/lib/utils/{tests.js → cds-test.js} +2 -2
  54. package/lib/utils/cds-utils.js +146 -0
  55. package/lib/utils/index.js +2 -145
  56. package/lib/utils/jest.js +43 -0
  57. package/lib/utils/resources/index.js +15 -25
  58. package/lib/utils/resources/tar.js +18 -41
  59. package/libx/_runtime/auth/index.js +14 -11
  60. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +7 -19
  61. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -4
  62. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -4
  63. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -4
  64. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -4
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -2
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +6 -19
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -4
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -4
  70. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
  71. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
  72. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +38 -4
  73. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -3
  74. package/libx/_runtime/cds-services/services/utils/differ.js +4 -0
  75. package/libx/_runtime/cds-services/util/errors.js +1 -29
  76. package/libx/_runtime/common/i18n/messages.properties +2 -1
  77. package/libx/_runtime/common/perf/index.js +10 -15
  78. package/libx/_runtime/common/utils/binary.js +3 -4
  79. package/libx/_runtime/common/utils/cqn2cqn4sql.js +0 -1
  80. package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
  81. package/libx/_runtime/common/utils/resolveView.js +1 -1
  82. package/libx/_runtime/common/utils/template.js +1 -1
  83. package/libx/_runtime/db/Service.js +2 -14
  84. package/libx/_runtime/db/expand/expandCQNToJoin.js +28 -25
  85. package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
  86. package/libx/_runtime/db/generic/input.js +8 -1
  87. package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
  88. package/libx/_runtime/extensibility/activate.js +47 -47
  89. package/libx/_runtime/extensibility/add.js +22 -13
  90. package/libx/_runtime/extensibility/addExtension.js +17 -13
  91. package/libx/_runtime/extensibility/defaults.js +25 -30
  92. package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
  93. package/libx/_runtime/extensibility/linter/allowlist_checker.js +373 -0
  94. package/libx/_runtime/extensibility/linter/annotations_checker.js +113 -0
  95. package/libx/_runtime/extensibility/linter/checker_base.js +20 -0
  96. package/libx/_runtime/extensibility/linter/namespace_checker.js +180 -0
  97. package/libx/_runtime/extensibility/linter.js +32 -0
  98. package/libx/_runtime/extensibility/push.js +77 -20
  99. package/libx/_runtime/extensibility/service.js +29 -12
  100. package/libx/_runtime/extensibility/token.js +56 -0
  101. package/libx/_runtime/extensibility/utils.js +8 -6
  102. package/libx/_runtime/extensibility/validation.js +6 -9
  103. package/libx/_runtime/fiori/generic/new.js +0 -11
  104. package/libx/_runtime/hana/Service.js +0 -1
  105. package/libx/_runtime/hana/conversion.js +12 -1
  106. package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +4 -3
  107. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -0
  108. package/libx/_runtime/hana/pool.js +6 -10
  109. package/libx/_runtime/hana/search2Contains.js +0 -5
  110. package/libx/_runtime/hana/search2cqn4sql.js +1 -0
  111. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  112. package/libx/_runtime/messaging/service.js +11 -6
  113. package/libx/_runtime/remote/utils/data.js +5 -0
  114. package/libx/_runtime/sqlite/Service.js +7 -6
  115. package/libx/_runtime/sqlite/execute.js +41 -28
  116. package/libx/odata/afterburner.js +79 -2
  117. package/libx/odata/cqn2odata.js +9 -7
  118. package/libx/odata/grammar.pegjs +157 -76
  119. package/libx/odata/index.js +9 -3
  120. package/libx/odata/parser.js +1 -1
  121. package/libx/odata/utils.js +39 -5
  122. package/libx/rest/RestAdapter.js +3 -7
  123. package/libx/rest/middleware/delete.js +4 -5
  124. package/libx/rest/middleware/parse.js +3 -2
  125. package/package.json +3 -3
  126. package/server.js +1 -1
  127. package/srv/extensibility-service.cds +6 -3
  128. package/srv/model-provider.cds +3 -1
  129. package/srv/model-provider.js +84 -104
  130. package/srv/mtx.js +7 -1
  131. 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) cqn.limit = 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
- for (let i = 0, length = this._queries.length; i < length; i++) {
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 = await this._queries[i]
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().catch(err => {
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.virtual) categories.push('virtual')
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
- if (this._obj.SELECT.limit || this._obj.SELECT.one) {
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 where = new this.ExpressionBuilder(
326
- this._obj.SELECT.where,
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
- // limit (no placeholder for statement caching)
427
- this._outputObj.sql.push('LIMIT', this._obj.SELECT.one ? 1 : this._obj.SELECT.limit.rows.val)
428
- // offset
429
- if (this._obj.SELECT.limit && this._obj.SELECT.limit.offset) {
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
- const _calculateExtensions = async function (ID, tag, tenant) {
6
+ // REVISIT: Reuse ratio = 0
7
+ const _calculateExtensions = async function (ID, tag) {
6
8
  let active, inactive
7
- await cds.tx({ tenant }, async tx => {
8
- if (tag || ID) {
9
- const inactiveCqn = SELECT.from('cds.xt.Extensions').where({ activated: 'propertyBag' })
10
- if (ID) {
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
- // activate all
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
- const _restoreExtensions = async function (tenant, active, inactive) {
38
- await cds.tx({ tenant }, async tx => {
39
- // delete all extensions
40
- await tx.run(DELETE.from('cds.xt.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 tx.run(INSERT.into('cds.xt.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, tx)
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
- const activate = async function (ID, tag, tenant) {
61
- const { active, inactive } = await _calculateExtensions(ID, tag, tenant)
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(tenant, active, inactive)
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 = req.tenant || (req.user.is('internal-user') && req.data.tenant)
15
+ const tenant = (req.user.is('internal-user') && req.data.tenant) || req.tenant
15
16
 
16
- const csn = _isCSN(extension) ? JSON.parse(extension) : cds.parse.cdl(extension)
17
- if (csn.requires) delete csn.requires
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.tx({ tenant }, async tx => {
22
- await tx.run(INSERT.into('cds.xt.Extensions').entries([{ ID, tag, csn: JSON.stringify(csn), activated: activate }]))
23
- if (activate === 'propertyBag' && csn.extensions) csn.extensions.forEach(async ext => await handleDefaults(ext, tx))
24
- })
25
-
26
- if (activate === 'database') {
27
- await activateExt(ID, tag, tenant)
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
- await activateExt(null, tag, tenant)
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 = cds.model.definitions[extension.extend]
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
- const tx = cds.tx(req)
46
- await tx.run(
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, tx, false)
54
+ await handleDefaults(extension, appCsn, false)
56
55
  }
57
56
  }
58
57
 
59
58
  const addExtension = async function (req) {
60
- const csn = _getCsn(req)
61
- validateCsn(csn, req)
62
- validateExtensionFields(csn, req)
63
- _addViews(csn, cds)
64
- await validateExtension(csn, req.tenant, req)
65
- await _addExtension(csn, req)
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 { EXT_BACK_PACK } = require('./utils')
6
-
7
- const _needsQuotations = t => t instanceof cds.builtin.classes.string || t instanceof cds.builtin.classes.date
8
-
9
- const _getDraftTable = view => {
10
- return cds.model.definitions[view]._isDraftEnabled ? ensureDraftsSuffix(view) : undefined
11
- }
12
-
13
- const handleDefaults = async (extension, tx, checkDb = true) => {
14
- const target = cds.model.definitions[extension.extend]
15
- const dbEntity = resolveViews(target).name
16
-
17
- if (checkDb && target.name !== dbEntity) return // only db entities
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 element = extension.elements[key]
24
- // .type as ui flex extensions are not linked
25
- const t = cds.model.definitions[element.type] || cds.builtin.types[element.type]
26
- const value = t && _needsQuotations(t) ? `"${element.default.val}"` : element.default.val
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 (ext.length !== 0) {
31
- const extStr = ext.join(',')
32
- const changed = `'{${extStr},' || substr(${EXT_BACK_PACK}, 2, length(${EXT_BACK_PACK})-1)`
33
- const assign = `${EXT_BACK_PACK} = CASE WHEN ${EXT_BACK_PACK} IS NULL THEN '{${extStr}}' ELSE ${changed} END`
34
- await tx.run(UPDATE(dbEntity).with(assign))
35
- if (draft) await tx.run(UPDATE(draft).with(assign))
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
- const name = req.target.name.SET ? req.target.name.SET.args[1]._target.name : req.target.name
46
- const extFields = getExtendedFields(name, model)
47
- _addBackPack(req.query.SELECT.columns, extFields)
48
- _removeExtendedFields(req.query.SELECT.columns, extFields)
49
-
50
- _addBackPack(
51
- req.query.SELECT.from.SET.args[0].SELECT.columns,
52
- extFields,
53
- req.query.SELECT.from.SET.args[0].SELECT.from.args[0].as
54
- )
55
- _addBackPack(req.query.SELECT.from.SET.args[1].SELECT.columns, extFields)
56
- _removeExtendedFields(
57
- req.query.SELECT.from.SET.args[0].SELECT.columns,
58
- extFields,
59
- req.query.SELECT.from.SET.args[0].SELECT.from.args[0].as
60
- )
61
- _removeExtendedFields(req.query.SELECT.from.SET.args[1].SELECT.columns, extFields)
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 = {