@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.
Files changed (139) hide show
  1. package/CHANGELOG.md +180 -18
  2. package/apis/cds.d.ts +11 -7
  3. package/apis/log.d.ts +124 -0
  4. package/apis/ql.d.ts +72 -15
  5. package/apis/services.d.ts +13 -2
  6. package/bin/build/buildTaskHandler.js +5 -2
  7. package/bin/build/constants.js +4 -1
  8. package/bin/build/provider/buildTaskHandlerEdmx.js +11 -39
  9. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +14 -34
  10. package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
  11. package/bin/build/provider/buildTaskProviderInternal.js +22 -14
  12. package/bin/build/provider/hana/index.js +12 -9
  13. package/bin/build/provider/java/index.js +18 -8
  14. package/bin/build/provider/mtx/index.js +7 -4
  15. package/bin/build/provider/mtx/resourcesTarBuilder.js +60 -35
  16. package/bin/build/provider/mtx-extension/index.js +57 -0
  17. package/bin/build/provider/mtx-sidecar/index.js +46 -18
  18. package/bin/build/provider/nodejs/index.js +34 -13
  19. package/bin/deploy/to-hana/cfUtil.js +7 -2
  20. package/bin/deploy/to-hana/hana.js +20 -25
  21. package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
  22. package/bin/serve.js +7 -4
  23. package/lib/compile/{index.js → cds-compile.js} +0 -0
  24. package/lib/compile/extend.js +15 -5
  25. package/lib/compile/minify.js +1 -15
  26. package/lib/compile/parse.js +1 -1
  27. package/lib/compile/resolve.js +2 -2
  28. package/lib/compile/to/srvinfo.js +6 -4
  29. package/lib/{deploy.js → dbs/cds-deploy.js} +9 -8
  30. package/lib/env/{index.js → cds-env.js} +1 -17
  31. package/lib/env/{requires.js → cds-requires.js} +24 -3
  32. package/lib/env/defaults.js +7 -1
  33. package/lib/env/schemas/cds-package.json +11 -0
  34. package/lib/env/schemas/cds-rc.json +614 -0
  35. package/lib/index.js +19 -16
  36. package/lib/log/{errors.js → cds-error.js} +1 -1
  37. package/lib/log/{index.js → cds-log.js} +0 -0
  38. package/lib/log/format/kibana.js +19 -1
  39. package/lib/ql/Query.js +9 -3
  40. package/lib/ql/SELECT.js +2 -2
  41. package/lib/ql/UPDATE.js +2 -2
  42. package/lib/ql/{index.js → cds-ql.js} +4 -10
  43. package/lib/req/context.js +49 -17
  44. package/lib/req/locale.js +5 -1
  45. package/lib/{serve → srv}/adapters.js +23 -19
  46. package/lib/{connect → srv}/bindings.js +0 -0
  47. package/lib/{connect/index.js → srv/cds-connect.js} +1 -1
  48. package/lib/{serve/index.js → srv/cds-serve.js} +0 -0
  49. package/lib/{serve → srv}/factory.js +1 -1
  50. package/lib/{serve/Service-api.js → srv/srv-api.js} +22 -6
  51. package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +13 -8
  52. package/lib/{serve/Service-handlers.js → srv/srv-handlers.js} +10 -0
  53. package/lib/{serve/Service-methods.js → srv/srv-methods.js} +10 -8
  54. package/lib/srv/srv-models.js +207 -0
  55. package/lib/{serve/Transaction.js → srv/srv-tx.js} +57 -40
  56. package/lib/utils/{tests.js → cds-test.js} +2 -2
  57. package/lib/utils/cds-utils.js +146 -0
  58. package/lib/utils/index.js +2 -145
  59. package/lib/utils/jest.js +43 -0
  60. package/lib/utils/resources/index.js +15 -25
  61. package/lib/utils/resources/tar.js +18 -41
  62. package/libx/_runtime/auth/index.js +14 -11
  63. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +7 -19
  64. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -4
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -4
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -4
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -2
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +6 -19
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +3 -5
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  72. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -4
  73. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
  74. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
  75. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +38 -4
  76. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -3
  77. package/libx/_runtime/cds-services/services/utils/differ.js +4 -0
  78. package/libx/_runtime/cds-services/util/errors.js +1 -29
  79. package/libx/_runtime/common/i18n/messages.properties +2 -1
  80. package/libx/_runtime/common/perf/index.js +10 -15
  81. package/libx/_runtime/common/utils/binary.js +3 -4
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +0 -1
  83. package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
  84. package/libx/_runtime/common/utils/keys.js +14 -6
  85. package/libx/_runtime/common/utils/resolveView.js +1 -1
  86. package/libx/_runtime/common/utils/template.js +1 -1
  87. package/libx/_runtime/db/Service.js +2 -14
  88. package/libx/_runtime/db/expand/expandCQNToJoin.js +28 -25
  89. package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
  90. package/libx/_runtime/db/generic/input.js +8 -1
  91. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  92. package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
  93. package/libx/_runtime/extensibility/activate.js +47 -47
  94. package/libx/_runtime/extensibility/add.js +22 -13
  95. package/libx/_runtime/extensibility/addExtension.js +17 -13
  96. package/libx/_runtime/extensibility/defaults.js +25 -30
  97. package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
  98. package/libx/_runtime/extensibility/linter/allowlist_checker.js +373 -0
  99. package/libx/_runtime/extensibility/linter/annotations_checker.js +113 -0
  100. package/libx/_runtime/extensibility/linter/checker_base.js +20 -0
  101. package/libx/_runtime/extensibility/linter/namespace_checker.js +180 -0
  102. package/libx/_runtime/extensibility/linter.js +32 -0
  103. package/libx/_runtime/extensibility/push.js +77 -20
  104. package/libx/_runtime/extensibility/service.js +29 -12
  105. package/libx/_runtime/extensibility/token.js +57 -0
  106. package/libx/_runtime/extensibility/utils.js +8 -6
  107. package/libx/_runtime/extensibility/validation.js +6 -9
  108. package/libx/_runtime/fiori/generic/new.js +0 -11
  109. package/libx/_runtime/fiori/utils/where.js +1 -1
  110. package/libx/_runtime/hana/Service.js +0 -1
  111. package/libx/_runtime/hana/conversion.js +12 -1
  112. package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +4 -3
  113. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -0
  114. package/libx/_runtime/hana/pool.js +6 -10
  115. package/libx/_runtime/hana/search2Contains.js +0 -5
  116. package/libx/_runtime/hana/search2cqn4sql.js +1 -0
  117. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  118. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +1 -2
  119. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  120. package/libx/_runtime/messaging/service.js +11 -6
  121. package/libx/_runtime/remote/utils/data.js +5 -0
  122. package/libx/_runtime/sqlite/Service.js +7 -6
  123. package/libx/_runtime/sqlite/execute.js +41 -28
  124. package/libx/odata/afterburner.js +79 -2
  125. package/libx/odata/cqn2odata.js +15 -9
  126. package/libx/odata/grammar.pegjs +157 -76
  127. package/libx/odata/index.js +9 -3
  128. package/libx/odata/parser.js +1 -1
  129. package/libx/odata/utils.js +39 -5
  130. package/libx/rest/RestAdapter.js +3 -7
  131. package/libx/rest/middleware/delete.js +4 -5
  132. package/libx/rest/middleware/parse.js +3 -2
  133. package/package.json +3 -3
  134. package/server.js +1 -1
  135. package/srv/extensibility-service.cds +6 -3
  136. package/srv/model-provider.cds +3 -1
  137. package/srv/model-provider.js +86 -106
  138. package/srv/mtx.js +7 -1
  139. 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 = app => {
8
- if (app._perf_measured) return
9
- else app._perf_measured = true
7
+ module.exports = function sap_statistics(req, res, next) {
8
+ if (!_statisticsRequested(req)) return next()
10
9
 
11
- app.use((req, res, next) => {
12
- if (!_statisticsRequested(req)) return next()
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
- const t0 = performance.now()
15
- const { writeHead } = res
16
- res.writeHead = function (...args) {
17
- const total = Number((performance.now() - t0) / 1000).toFixed(2)
18
- if (res.statusCode < 400) res.setHeader('sap-statistics', `total=${total}`)
19
- writeHead.call(this, ...args)
20
- }
21
-
22
- next()
23
- })
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 base64 = value.replace(/_/g, '/').replace(/-/g, '+')
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
- return !base64.match(BASE64) || base64.replace(/=/g, '') !== normalized.replace(/=/g, '')
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 => {
@@ -698,7 +698,6 @@ const _convertToOneEqNullInFilter = (query, target) => {
698
698
  }
699
699
  }
700
700
  }
701
-
702
701
  // eslint-disable-next-line complexity
703
702
  const _convertSelect = (query, model, _options) => {
704
703
  const _4db = _options.service instanceof cds.DatabaseService
@@ -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
- return cqn.SET.args.map(a => traverseFroms(a, cb))
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, from => res.push({ entityName: from.ref[0].id || from.ref[0], alias: from.as }))
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 _modifyWhereWithNavigations(where, newWhere, entityKey, targetKey) {
31
- if (where) {
30
+ function _mergeWhere(base, additional) {
31
+ if (additional?.length) {
32
32
  // copy where else query will be modified
33
- const whereCopy = deepCopyArray(where)
34
- if (newWhere.length > 0) newWhere.push('and')
35
- newWhere.push(...whereCopy)
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
- return query.UPDATE.where
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) 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) {
@@ -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
- 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 }