@sap/cds 5.6.1 → 5.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/_i18n/i18n_fr.properties +4 -4
  3. package/apis/cds.d.ts +7 -10
  4. package/apis/connect.d.ts +3 -3
  5. package/apis/core.d.ts +2 -4
  6. package/apis/models.d.ts +2 -3
  7. package/apis/ql.d.ts +0 -1
  8. package/apis/services.d.ts +7 -3
  9. package/bin/build/buildTaskFactory.js +16 -10
  10. package/bin/build/buildTaskProviderFactory.js +3 -3
  11. package/bin/build/constants.js +2 -1
  12. package/bin/build/provider/buildTaskProviderInternal.js +14 -14
  13. package/bin/build/provider/hana/2migration.js +2 -3
  14. package/bin/build/provider/hana/index.js +34 -0
  15. package/bin/build/provider/hana/migrationtable.js +90 -22
  16. package/bin/build/provider/hana/template/undeploy.json +5 -0
  17. package/bin/build/provider/node-cf/index.js +9 -2
  18. package/bin/serve.js +16 -18
  19. package/lib/compile/cdsc.js +15 -5
  20. package/lib/compile/etc/_localized.js +4 -4
  21. package/lib/compile/extend.js +8 -0
  22. package/lib/compile/index.js +3 -1
  23. package/lib/compile/minify.js +61 -0
  24. package/lib/compile/resolve.js +4 -1
  25. package/lib/compile/to/gql.js +9 -0
  26. package/lib/compile/to/sql.js +26 -30
  27. package/lib/connect/index.js +1 -1
  28. package/lib/core/entities.js +0 -3
  29. package/lib/core/infer.js +1 -0
  30. package/lib/core/reflect.js +1 -34
  31. package/lib/deploy.js +25 -17
  32. package/lib/env/defaults.js +3 -1
  33. package/lib/env/index.js +13 -4
  34. package/lib/env/presets.js +38 -0
  35. package/lib/env/requires.js +16 -11
  36. package/lib/index.js +13 -11
  37. package/lib/log/format/kibana.js +4 -2
  38. package/lib/log/index.js +2 -2
  39. package/lib/ql/Whereable.js +1 -0
  40. package/lib/req/cds-context.js +79 -0
  41. package/lib/req/context.js +5 -77
  42. package/lib/req/request.js +1 -1
  43. package/lib/serve/Service-api.js +8 -4
  44. package/lib/serve/Service-dispatch.js +0 -7
  45. package/lib/serve/Service-methods.js +6 -8
  46. package/lib/serve/Transaction.js +35 -30
  47. package/lib/serve/adapters.js +1 -4
  48. package/lib/utils/axios.js +1 -1
  49. package/libx/_runtime/audit/Service.js +44 -20
  50. package/libx/_runtime/audit/generic/personal/access.js +16 -11
  51. package/libx/_runtime/audit/generic/personal/modification.js +5 -5
  52. package/libx/_runtime/audit/generic/personal/utils.js +46 -37
  53. package/libx/_runtime/{common/auth → auth}/index.js +21 -7
  54. package/libx/_runtime/{common/auth → auth}/strategies/JWT.js +2 -2
  55. package/libx/_runtime/{common/auth → auth}/strategies/basic.js +2 -2
  56. package/libx/_runtime/{common/auth → auth}/strategies/dummy.js +1 -1
  57. package/libx/_runtime/{common/auth → auth}/strategies/mock.js +2 -2
  58. package/libx/_runtime/{common/auth → auth}/strategies/utils/uaa.js +1 -1
  59. package/libx/_runtime/{common/auth → auth}/strategies/utils/xssec.js +0 -0
  60. package/libx/_runtime/{common/auth → auth}/strategies/xsuaa.js +2 -2
  61. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +7 -2
  62. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +0 -7
  63. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +0 -8
  64. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +3 -4
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +6 -7
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +3 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -11
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +16 -6
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -65
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +0 -7
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +3 -66
  72. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +26 -0
  73. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +5 -5
  74. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +2 -2
  75. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +13 -10
  76. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -7
  77. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/SetResponseHeadersCommand.js +1 -0
  78. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -8
  79. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +18 -15
  80. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +54 -76
  81. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
  82. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
  83. package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
  84. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
  85. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
  86. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
  87. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -2
  88. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
  89. package/libx/_runtime/cds-services/services/Service.js +0 -6
  90. package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
  91. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -7
  92. package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
  93. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
  94. package/libx/_runtime/cds-services/util/assert.js +1 -262
  95. package/libx/_runtime/cds.js +6 -9
  96. package/libx/_runtime/common/aspects/entity.js +1 -1
  97. package/libx/_runtime/common/composition/delete.js +4 -2
  98. package/libx/_runtime/common/composition/update.js +27 -35
  99. package/libx/_runtime/common/composition/utils.js +3 -7
  100. package/libx/_runtime/common/error/standardError.js +11 -0
  101. package/libx/_runtime/common/generic/auth.js +61 -30
  102. package/libx/_runtime/common/generic/crud.js +11 -23
  103. package/libx/_runtime/common/generic/input.js +20 -0
  104. package/libx/_runtime/common/generic/paging.js +2 -2
  105. package/libx/_runtime/common/generic/put.js +4 -10
  106. package/libx/_runtime/common/generic/sorting.js +12 -30
  107. package/libx/_runtime/common/perf/index.js +24 -0
  108. package/libx/_runtime/common/utils/cqn.js +58 -1
  109. package/libx/_runtime/common/utils/cqn2cqn4sql.js +289 -114
  110. package/libx/_runtime/common/utils/csn.js +38 -56
  111. package/libx/_runtime/common/utils/entityFromCqn.js +6 -6
  112. package/libx/_runtime/common/utils/resolveView.js +4 -5
  113. package/libx/_runtime/common/utils/rewriteAsterisks.js +46 -5
  114. package/libx/_runtime/common/utils/search2cqn4sql.js +21 -9
  115. package/libx/_runtime/common/utils/structured.js +35 -25
  116. package/libx/_runtime/db/Service.js +0 -6
  117. package/libx/_runtime/db/expand/expand-v2.js +130 -0
  118. package/libx/_runtime/db/expand/expandCQNToJoin.js +82 -61
  119. package/libx/_runtime/db/expand/index.js +3 -1
  120. package/libx/_runtime/db/generic/arrayed.js +14 -27
  121. package/libx/_runtime/db/generic/input.js +52 -10
  122. package/libx/_runtime/db/generic/integrity.js +367 -26
  123. package/libx/_runtime/db/generic/virtual.js +51 -13
  124. package/libx/_runtime/db/query/update.js +9 -3
  125. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +8 -9
  126. package/libx/_runtime/{common → db}/utils/propagateForeignKeys.js +11 -14
  127. package/libx/_runtime/fiori/generic/activate.js +1 -0
  128. package/libx/_runtime/fiori/generic/before.js +2 -1
  129. package/libx/_runtime/fiori/generic/edit.js +2 -1
  130. package/libx/_runtime/fiori/generic/patch.js +1 -1
  131. package/libx/_runtime/fiori/generic/read.js +151 -57
  132. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +0 -4
  133. package/libx/_runtime/fiori/uiflex/index.js +1 -1
  134. package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +6 -4
  135. package/libx/_runtime/fiori/utils/delete.js +7 -1
  136. package/libx/_runtime/hana/Service.js +1 -8
  137. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
  138. package/libx/_runtime/hana/execute.js +10 -4
  139. package/libx/_runtime/hana/pool.js +55 -45
  140. package/libx/_runtime/hana/search.js +7 -6
  141. package/libx/_runtime/hana/search2cqn4sql.js +8 -5
  142. package/libx/_runtime/hana/searchToContains.js +3 -1
  143. package/libx/_runtime/index.js +5 -5
  144. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
  145. package/libx/_runtime/messaging/Outbox.js +53 -0
  146. package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
  147. package/libx/_runtime/messaging/common-utils/connections.js +14 -9
  148. package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
  149. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
  150. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
  151. package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
  152. package/libx/_runtime/messaging/file-based.js +5 -5
  153. package/libx/_runtime/messaging/message-queuing.js +2 -3
  154. package/libx/_runtime/messaging/outbox/OutboxRunner.js +75 -0
  155. package/libx/_runtime/messaging/outbox/utils.js +192 -0
  156. package/libx/_runtime/messaging/service.js +16 -30
  157. package/libx/_runtime/remote/Service.js +21 -2
  158. package/libx/_runtime/remote/utils/client.js +15 -3
  159. package/libx/_runtime/remote/utils/{dataConversion.js → data.js} +12 -2
  160. package/libx/_runtime/sqlite/Service.js +7 -10
  161. package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +19 -0
  162. package/libx/_runtime/sqlite/execute.js +18 -12
  163. package/libx/_runtime/types/api.js +2 -1
  164. package/libx/odata/{odata2cqn/afterburner.js → afterburner.js} +28 -16
  165. package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
  166. package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +182 -118
  167. package/libx/odata/index.js +18 -15
  168. package/libx/odata/parser.js +1 -0
  169. package/libx/odata/utils.js +57 -0
  170. package/libx/rest/RestAdapter.js +2 -6
  171. package/libx/rest/utils/data.js +1 -6
  172. package/package.json +4 -3
  173. package/server.js +13 -10
  174. package/srv/audit-log.cds +87 -0
  175. package/{libx/_runtime/fiori/uiflex/extensibility/index.cds → srv/flex.cds} +0 -0
  176. package/srv/flex.js +1 -0
  177. package/srv/outbox.cds +11 -0
  178. package/srv/outbox.js +0 -0
  179. package/libx/_runtime/cds-services/adapter/perf/performance.js +0 -104
  180. package/libx/_runtime/cds-services/adapter/perf/performanceMeasurement.js +0 -33
  181. package/libx/odata/odata2cqn/index.js +0 -3
  182. package/libx/odata/odata2cqn/parser.js +0 -1
  183. package/libx/odata/readme.md +0 -1
  184. package/libx/odata/utils/index.js +0 -64
@@ -11,15 +11,27 @@ const { isAsteriskColumn } = require('../../common/utils/rewriteAsterisks')
11
11
  const GET_KEY_VALUE = Symbol.for('sap.cds.getKeyValue')
12
12
  const TO_MANY = Symbol.for('sap.cds.toMany')
13
13
  const TO_ACTIVE = Symbol.for('sap.cds.toActive')
14
-
15
14
  const SKIP_MAPPING = Symbol.for('sap.cds.skipMapping')
16
15
  const IDENTIFIER = Symbol.for('sap.cds.identifier')
17
16
  const IS_ACTIVE = Symbol.for('sap.cds.isActive')
18
17
  const IS_UNION_DRAFT = Symbol.for('sap.cds.isUnionDraft')
18
+
19
19
  const { DRAFT_COLUMNS } = require('../../common/constants/draft')
20
20
 
21
21
  const { getCQNUnionFrom } = require('../../common/utils/union')
22
22
 
23
+ function getCqnCopy(readToOneCQN) {
24
+ const readToOneCQNCopy = JSON.parse(JSON.stringify(readToOneCQN))
25
+ if (readToOneCQN[GET_KEY_VALUE] !== undefined) readToOneCQNCopy[GET_KEY_VALUE] = readToOneCQN[GET_KEY_VALUE]
26
+ if (readToOneCQN[TO_MANY] !== undefined) readToOneCQNCopy[TO_MANY] = readToOneCQN[TO_MANY]
27
+ if (readToOneCQN[TO_ACTIVE] !== undefined) readToOneCQNCopy[TO_ACTIVE] = readToOneCQN[TO_ACTIVE]
28
+ if (readToOneCQN[SKIP_MAPPING] !== undefined) readToOneCQNCopy[SKIP_MAPPING] = readToOneCQN[SKIP_MAPPING]
29
+ if (readToOneCQN[IDENTIFIER] !== undefined) readToOneCQNCopy[IDENTIFIER] = readToOneCQN[IDENTIFIER]
30
+ if (readToOneCQN[IS_ACTIVE] !== undefined) readToOneCQNCopy[IS_ACTIVE] = readToOneCQN[IS_ACTIVE]
31
+ if (readToOneCQN[IS_UNION_DRAFT] !== undefined) readToOneCQNCopy[IS_UNION_DRAFT] = readToOneCQN[IS_UNION_DRAFT]
32
+ return readToOneCQNCopy
33
+ }
34
+
23
35
  class JoinCQNFromExpanded {
24
36
  constructor(cqn, csn, locale) {
25
37
  this._SELECT = Object.assign({}, cqn.SELECT)
@@ -39,12 +51,8 @@ class JoinCQNFromExpanded {
39
51
  * @returns {this}
40
52
  */
41
53
  buildJoinQueries() {
42
- const unionTableRef = this._getUnionTable(this._SELECT)
43
54
  // side effect: this_aliases is set
44
- const aliases = this._getTableAlias(this._SELECT, [], unionTableRef && unionTableRef.table)
45
-
46
- // Add table aliases to all refs in where part obtained from annotations
47
- this._adaptAliasForWhere(this._SELECT.where)
55
+ const aliases = this._getTableAlias(this._SELECT, [])
48
56
 
49
57
  // Update elements at WHERE, so there are no issues with ambiguity
50
58
  this._adaptWhereOrderBy(this._SELECT, aliases)
@@ -91,16 +99,16 @@ class JoinCQNFromExpanded {
91
99
  * @private
92
100
  */
93
101
  _createJoinCQNFromExpanded(SELECT, toManyTree, defaultLanguage) {
94
- const unionArgs = SELECT.from.args
95
- const isJoinOfTwoUnions = unionArgs && unionArgs.every(a => a.SELECT)
102
+ const joinArgs = SELECT.from.args
103
+ const isJoinOfTwoSelects = joinArgs && joinArgs.every(a => a.SELECT)
96
104
 
97
105
  const unionTableRef = this._getUnionTable(SELECT)
98
106
  const unionTable = unionTableRef && unionTableRef.table
99
- const tableAlias = this._getTableAlias(SELECT, toManyTree, unionTable)
107
+ const tableAlias = this._getTableAlias(SELECT, toManyTree)
100
108
 
101
- const readToOneCQN = this._getReadToOneCQN(SELECT, isJoinOfTwoUnions ? 'filterExpand' : tableAlias)
109
+ const readToOneCQN = this._getReadToOneCQN(SELECT, isJoinOfTwoSelects ? 'filterExpand' : tableAlias)
102
110
 
103
- if (isJoinOfTwoUnions) {
111
+ if (isJoinOfTwoSelects) {
104
112
  // mappings
105
113
  const mappings = this._getMappingObject(toManyTree)
106
114
  const prefix = `${tableAlias}_`
@@ -110,7 +118,7 @@ class JoinCQNFromExpanded {
110
118
  mappings[c.as.replace(prefix, '')] = c.as
111
119
  })
112
120
  // expand to one
113
- const entity = this._csn.definitions[unionArgs[0].SELECT.from.SET.args[1].SELECT.from.ref[0]]
121
+ const entity = this._csn.definitions[joinArgs[0].SELECT.from.SET.args[1].SELECT.from.ref[0]]
114
122
  const givenColumns = readToOneCQN.columns
115
123
  readToOneCQN.columns = []
116
124
  this._expandedToFlat({ entity, givenColumns, readToOneCQN, tableAlias, toManyTree, defaultLanguage })
@@ -128,6 +136,9 @@ class JoinCQNFromExpanded {
128
136
  this._expandedToFlat({ entity, givenColumns, readToOneCQN, tableAlias, toManyTree, defaultLanguage })
129
137
  }
130
138
 
139
+ // brute force hack
140
+ readToOneCQN.columns = readToOneCQN.columns.filter(c => c.as !== 'filterExpand_IsActiveEntity')
141
+
131
142
  // Add at start, so that the deepest level is post processed first
132
143
  this.queries.push({
133
144
  SELECT: readToOneCQN,
@@ -146,11 +157,14 @@ class JoinCQNFromExpanded {
146
157
  * @returns {string}
147
158
  * @private
148
159
  */
149
- _getTableAlias(SELECT, toManyTree, unionTable) {
150
- return this._createAlias(toManyTree.length === 0 ? unionTable || this._getRef(SELECT).table : toManyTree.join(':'))
160
+ _getTableAlias(SELECT, toManyTree) {
161
+ return this._createAlias(toManyTree.length === 0 ? this._getRef(SELECT).table : toManyTree.join(':'))
151
162
  }
152
163
 
153
164
  _getRef(SELECT) {
165
+ const unionTableRef = this._getUnionTable(SELECT)
166
+ if (unionTableRef) return unionTableRef
167
+
154
168
  const table = Object.prototype.hasOwnProperty.call(SELECT.from, 'join')
155
169
  ? this._getRefFromJoin(SELECT.from.args)
156
170
  : SELECT.from
@@ -241,10 +255,18 @@ class JoinCQNFromExpanded {
241
255
  : column
242
256
  }
243
257
 
244
- _adaptJoin(tableAlias, cqn, from) {
258
+ _adaptJoin(tableAlias, cqn, from, mapping = {}) {
245
259
  from.args = from.args.slice(0)
246
260
  if (Object.prototype.hasOwnProperty.call(from.args[0], 'join')) {
247
- this._adaptJoin(tableAlias, cqn, from.args[0])
261
+ this._adaptJoin(tableAlias, cqn, from.args[0], mapping)
262
+ if (from.on) {
263
+ // we come here for SiblingEntity with expand
264
+ for (const originalIdentifier in mapping) {
265
+ from.on = from.on.map(column =>
266
+ this._adaptTableNameInColumn(column, originalIdentifier, mapping[originalIdentifier])
267
+ )
268
+ }
269
+ }
248
270
  } else {
249
271
  const index = from.args[0].ref ? 0 : from.args.length - 1
250
272
  const target = Object.assign({}, from.args[index], { as: tableAlias })
@@ -253,6 +275,7 @@ class JoinCQNFromExpanded {
253
275
  from.args[index] = target
254
276
  from.on = from.on.map(column => this._adaptTableNameInColumn(column, originalIdentifier, tableAlias))
255
277
  cqn.columns = cqn.columns.map(column => this._adaptTableNameInColumn(column, originalIdentifier, tableAlias))
278
+ mapping[originalIdentifier] = tableAlias
256
279
  }
257
280
  }
258
281
 
@@ -316,32 +339,6 @@ class JoinCQNFromExpanded {
316
339
  whereElement.ref && whereElement.ref.splice(0, 1, Object.values(this._aliases)[0])
317
340
  }
318
341
 
319
- _adaptAliasForFrom(from) {
320
- if (from.args) {
321
- from.args.forEach(arg => {
322
- this._adaptAliasForFrom(arg)
323
- })
324
- } else if (from.SELECT) {
325
- this._adaptAliasForFrom(from.SELECT.from)
326
- if (from.SELECT.where) {
327
- this._adaptAliasForWhere(from.SELECT.where)
328
- }
329
- }
330
- }
331
-
332
- _adaptAliasForWhere(where) {
333
- if (where) {
334
- for (const whereElement of where) {
335
- if (whereElement.SELECT) {
336
- if (whereElement.SELECT.where) {
337
- this._adaptAliasForWhere(whereElement.SELECT.where)
338
- }
339
- this._adaptAliasForFrom(whereElement.SELECT.from)
340
- }
341
- }
342
- }
343
- }
344
-
345
342
  _navigationNeedsAlias(element, { table } = {}) {
346
343
  const entity = this._csn.definitions[table]
347
344
  if (entity) {
@@ -362,9 +359,9 @@ class JoinCQNFromExpanded {
362
359
 
363
360
  if (element.ref.length === 1) {
364
361
  element.ref.unshift(tableAlias)
365
- } else if (this._elementAliasNeedsReplacement(element, this._getUnionTable(cqn) || this._getRef(cqn))) {
362
+ } else if (this._elementAliasNeedsReplacement(element, this._getRef(cqn))) {
366
363
  element.ref[0] = tableAlias
367
- } else if (this._navigationNeedsAlias(element, this._getUnionTable(cqn) || this._getRef(cqn))) {
364
+ } else if (this._navigationNeedsAlias(element, this._getRef(cqn))) {
368
365
  element.ref.unshift(tableAlias)
369
366
  }
370
367
 
@@ -377,7 +374,7 @@ class JoinCQNFromExpanded {
377
374
  } else if (element.SELECT && element.SELECT.where) {
378
375
  element = {
379
376
  SELECT: Object.assign({}, element.SELECT, {
380
- where: this._adaptWhereSELECT(this._getUnionTable(cqn) || this._getRef(cqn), element.SELECT.where, tableAlias)
377
+ where: this._adaptWhereSELECT(this._getRef(cqn), element.SELECT.where, tableAlias)
381
378
  })
382
379
  }
383
380
  }
@@ -480,6 +477,8 @@ class JoinCQNFromExpanded {
480
477
  const toManyColumns = []
481
478
  const mappings = this._getMappingObject(toManyTree)
482
479
 
480
+ const readToOneCQNCopy = getCqnCopy(readToOneCQN)
481
+
483
482
  for (const column of givenColumns) {
484
483
  let navigation
485
484
  if (column.expand) {
@@ -523,7 +522,16 @@ class JoinCQNFromExpanded {
523
522
  }
524
523
 
525
524
  // only as second step handle expand to many, or else keys might still be unknown
526
- this._toMany({ entity, readToOneCQN, tableAlias, toManyColumns, toManyTree, mappings, defaultLanguage })
525
+ this._toMany({
526
+ entity,
527
+ readToOneCQN,
528
+ tableAlias,
529
+ toManyColumns,
530
+ toManyTree,
531
+ mappings,
532
+ defaultLanguage,
533
+ readToOneCQNCopy
534
+ })
527
535
  }
528
536
 
529
537
  adjustOrderBy(orderBy, mappings, column, tableAlias) {
@@ -562,13 +570,15 @@ class JoinCQNFromExpanded {
562
570
  const on = []
563
571
  for (const key in entity._target.keys) {
564
572
  if (key !== 'IsActiveEntity') {
573
+ if (on.length) on.push('AND')
565
574
  on.push({ ref: [`${tableAlias}_drafts`, key] }, '=', { ref: [tableAlias, key] })
566
575
  }
567
576
  }
577
+
568
578
  return {
569
579
  args: [cqn, { ref: [draftTable], as: `${tableAlias}_drafts` }],
570
580
  join: 'left',
571
- on: on
581
+ on: ['(', ...on, ')']
572
582
  }
573
583
  }
574
584
 
@@ -587,8 +597,8 @@ class JoinCQNFromExpanded {
587
597
  )
588
598
  }
589
599
 
590
- _getTarget(entity, column) {
591
- const navigation = getNavigationIfStruct(entity, column.ref)
600
+ _getTarget(entity, column, parentAlias) {
601
+ const navigation = getNavigationIfStruct(entity, column.ref[0] === parentAlias ? column.ref.slice(1) : column.ref)
592
602
  return (navigation && navigation.target) || column.ref[0]
593
603
  }
594
604
 
@@ -606,17 +616,19 @@ class JoinCQNFromExpanded {
606
616
  */
607
617
  // eslint-disable-next-line complexity
608
618
  _addJoinAndElements({ column, entity, readToOneCQN, toManyTree, parentAlias, defaultLanguage }) {
609
- const extendedToManyTree = toManyTree.concat(column.ref)
619
+ const extendedToManyTree = toManyTree.concat(column.ref[0] === parentAlias ? column.ref.slice(1) : column.ref)
610
620
  const tableAlias = this._createAlias(extendedToManyTree.join(':'))
611
- const target = this._getTarget(entity, column)
621
+ const target = this._getTarget(entity, column, parentAlias)
622
+
623
+ const name = column.ref[column.ref.length - 1]
624
+ const element = name && entity.elements[name]
612
625
 
613
626
  // if union always only expand with active, otherwise evaluate flag
614
627
  // if flag shows false, we check entity for associations to non draft
615
628
  const activeTableRequired =
616
629
  readToOneCQN[IS_UNION_DRAFT] ||
617
630
  readToOneCQN[IS_ACTIVE] ||
618
- (entity.elements[column.ref[0]].type === 'cds.Association' &&
619
- !entity.elements[column.ref[0]]['@odata.draft.enclosed']) ||
631
+ (element && element.type === 'cds.Association' && !element['@odata.draft.enclosed']) ||
620
632
  !this._csn.definitions[target]._isDraftEnabled
621
633
 
622
634
  const colTarget = target && ensureUnlocalized(target)
@@ -626,9 +638,7 @@ class JoinCQNFromExpanded {
626
638
  (colTarget && this._csn.definitions[colTarget] && this._csn.definitions[colTarget]['@cds.localized'] === false)
627
639
 
628
640
  const join =
629
- column.ref[0] === 'DraftAdministrativeData' || !entity.elements[column.ref[0]].notNull || this._isDraft
630
- ? 'left'
631
- : 'inner'
641
+ column.ref[0] === 'DraftAdministrativeData' || !(element && element.notNull) || this._isDraft ? 'left' : 'inner'
632
642
 
633
643
  const args = [
634
644
  readToOneCQN.from.SET ? this._unionToSubQuery(readToOneCQN) : readToOneCQN.from,
@@ -669,11 +679,11 @@ class JoinCQNFromExpanded {
669
679
  }
670
680
 
671
681
  // special case of navigation to one requires additional LEFT JOIN and CASE for HasDraftEntity
672
- const compToOne = this._isNavigationToOne(readToOneCQN[IS_ACTIVE], entity.elements[column.ref[0]])
682
+ const compToOne = this._isNavigationToOne(readToOneCQN[IS_ACTIVE], element)
673
683
  const index = column.expand.findIndex(col => col.ref && col.ref[col.ref.length - 1] === 'HasDraftEntity')
674
684
 
675
685
  if (compToOne && index !== -1) {
676
- readToOneCQN.from = this._addJoinCompToOne(readToOneCQN.from, entity.elements[column.ref[0]], tableAlias)
686
+ readToOneCQN.from = this._addJoinCompToOne(readToOneCQN.from, element, tableAlias)
677
687
  if (activeTableRequired) {
678
688
  column.expand[index] = {
679
689
  xpr: [
@@ -823,7 +833,9 @@ class JoinCQNFromExpanded {
823
833
  const subSelectColumns = this._getSubSelectColumns(readToOneCQN)
824
834
 
825
835
  if (subSelectColumns.length === 0) {
826
- return entity._relations[tableAlias === columns[0] ? columns.slice(1) : columns].join(tableAlias, parentAlias)
836
+ return entity._relations[
837
+ tableAlias === columns[0] || parentAlias === columns[0] ? columns.slice(1) : columns
838
+ ].join(tableAlias, parentAlias)
827
839
  }
828
840
 
829
841
  const aliases = this._getAliases(subSelectColumns)
@@ -957,7 +969,16 @@ class JoinCQNFromExpanded {
957
969
  return DRAFT_COLUMNS.includes(ref[0])
958
970
  }
959
971
 
960
- _toMany({ entity, readToOneCQN, tableAlias, toManyColumns, toManyTree, mappings, defaultLanguage }) {
972
+ _toMany({
973
+ entity,
974
+ readToOneCQN,
975
+ tableAlias,
976
+ toManyColumns,
977
+ toManyTree,
978
+ mappings,
979
+ defaultLanguage,
980
+ readToOneCQNCopy
981
+ }) {
961
982
  if (toManyColumns.length === 0) {
962
983
  return
963
984
  }
@@ -968,7 +989,7 @@ class JoinCQNFromExpanded {
968
989
  const select = this._buildExpandedCQN({
969
990
  column,
970
991
  entity,
971
- readToOneCQN,
992
+ readToOneCQN: readToOneCQNCopy,
972
993
  toManyTree,
973
994
  mappings,
974
995
  parentAlias,
@@ -1,8 +1,10 @@
1
1
  const { hasExpand, createJoinCQNFromExpanded } = require('./expandCQNToJoin')
2
2
  const rawToExpanded = require('./rawToExpanded')
3
+ const expandV2 = require('./expand-v2')
3
4
 
4
5
  module.exports = {
5
6
  hasExpand,
6
7
  createJoinCQNFromExpanded,
7
- rawToExpanded
8
+ rawToExpanded,
9
+ expandV2
8
10
  }
@@ -1,27 +1,14 @@
1
- // REVISIT: use templating mechanism (resp. results.metadata, once available) to make more efficient
2
-
3
1
  const { getEntityFromCQN } = require('../../common/utils/entityFromCqn')
2
+ const getTemplate = require('../../common/utils/template')
3
+ const templateProcessor = require('../../common/utils/templateProcessor')
4
4
 
5
- const _toArray = (result, elements) => {
6
- // REVISIT: This is a very expensive loop in loop...
7
- // and 100% overhead the results don't contain arrayed elements,
8
- // and 100% of all currently existing stakeholder projects don't.
9
- // but that's not that easy to fix -> see comment about results.metadata below
10
- for (const row of result) {
11
- for (const column in row) {
12
- if (elements[column] === undefined || row[column] === undefined) continue
5
+ const _pick = element => {
6
+ if (element.kind === 'element' && element.items) return 'arrayed'
7
+ }
13
8
 
14
- // .items marks arrayed element
15
- if (elements[column].items) {
16
- row[column] = JSON.parse(row[column])
17
- } else if (elements[column].is2many) {
18
- _toArray(row[column], elements[column]._target.elements)
19
- } else if (elements[column].is2one) {
20
- _toArray([row[column]], elements[column]._target.elements)
21
- } else if (elements[column].elements) {
22
- _toArray([row[column]], elements[column].elements)
23
- }
24
- }
9
+ const _processFn = ({ row, key, plain }) => {
10
+ if (plain === 'arrayed' && row && row[key] && (typeof row[key] === 'string' || Buffer.isBuffer(row[key]))) {
11
+ row[key] = JSON.parse(row[key])
25
12
  }
26
13
  }
27
14
 
@@ -35,12 +22,12 @@ const _toArray = (result, elements) => {
35
22
  module.exports = function (result, req) {
36
23
  if (!this.model) return
37
24
 
38
- if (!Array.isArray(result)) result = [result]
39
-
40
- // REVISIT: We need results.metadata to make that more efficient
41
- // results.metadata ~= cds.infer(req.query).metadata
42
- // REVISIT: No entity for sets/unions outside of common draft scenarios
43
25
  const entity = getEntityFromCQN(req, this)
44
26
  if (!entity) return
45
- if (entity) _toArray(result, entity.elements)
27
+
28
+ const template = getTemplate('db-arrayed', this, entity, { pick: _pick })
29
+ if (template.elements.size === 0) return
30
+
31
+ for (const row of Array.isArray(result) ? result : [result])
32
+ templateProcessor({ processFn: _processFn, row, template })
46
33
  }
@@ -14,7 +14,7 @@ const cds = require('../../cds')
14
14
  const normalizeTimeData = require('../utils/normalizeTimeData')
15
15
 
16
16
  const { enrichDataWithKeysFromWhere } = require('../../common/utils/keys')
17
- const { propagateForeignKeys } = require('../../common/utils/propagateForeignKeys')
17
+ const propagateForeignKeys = require('../utils/propagateForeignKeys')
18
18
  const getTemplate = require('../../common/utils/template')
19
19
  const templateProcessor = require('../../common/utils/templateProcessor')
20
20
 
@@ -25,12 +25,14 @@ const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
25
25
  const _isManaged = (category, event) =>
26
26
  (category === '@cds.on.insert' && event === 'CREATE') || (category === '@cds.on.update' && event === 'UPDATE')
27
27
 
28
+ const _shouldGenerateUUID = element => element.key && !DRAFT_COLUMNS_MAP[element.name] && element.type === 'cds.UUID'
29
+
28
30
  const _processComplexCategory = ({ row, key, val, category, req, element }) => {
29
31
  const categoryArgs = category.args
30
32
  category = category.category
31
33
 
32
34
  // propagate keys
33
- if (category === 'propagateForeignKeys') {
35
+ if (category === 'propagateForeignKeys' && row[key]) {
34
36
  propagateForeignKeys(key, row, element._foreignKeys, element._isCompositionEffective)
35
37
  return
36
38
  }
@@ -73,6 +75,20 @@ const _processCategory = ({ category, row, key, element, val, req }) => {
73
75
  return
74
76
  }
75
77
 
78
+ if (category === 'comp2one') {
79
+ if (!val) return
80
+
81
+ for (const keyName in element._target.keys) {
82
+ const k = element._target.keys[keyName]
83
+ if (k.type === 'cds.Association' || k.name === 'IsActiveEntity') continue
84
+
85
+ if (k.type !== 'cds.UUID' && !(k.name in val)) {
86
+ req.error(400, 'ASSERT_NOT_NULL', key + '[' + k + ']', [key + '[' + k + ']'])
87
+ return
88
+ }
89
+ }
90
+ }
91
+
76
92
  // not null without default (for better error message)
77
93
  if (category === '!default' && val == null && req.event === 'CREATE') {
78
94
  req.error(400, 'ASSERT_NOT_NULL', key, [key])
@@ -101,12 +117,24 @@ const processorFn =
101
117
  }
102
118
  }
103
119
 
120
+ const _isVirtualOrCalculated = element => {
121
+ if (element.virtual) return true
122
+ if (
123
+ element.parent &&
124
+ element.parent.projection &&
125
+ element.parent.projection.columns &&
126
+ element.parent.projection.columns.find(c => c.as === element.name && (c.xpr || c.val || c.func))
127
+ ) {
128
+ return true
129
+ }
130
+ }
131
+
104
132
  // params: element, target, parent, templateElements
105
- const _pick = element => {
133
+ const _pickCRUD = element => {
106
134
  // collect actions to apply
107
135
  const categories = []
108
136
 
109
- if (element.virtual) {
137
+ if (_isVirtualOrCalculated(element)) {
110
138
  categories.push('virtual')
111
139
  return { categories } // > no need to continue
112
140
  }
@@ -136,22 +164,37 @@ const _pick = element => {
136
164
  categories.push('associationEffective')
137
165
  }
138
166
 
167
+ // REVISIT: element._foreignKeys.length seems to be a very broad check
139
168
  if (element.isAssociation && element._foreignKeys.length) {
140
169
  categories.push({ category: 'propagateForeignKeys' })
141
170
  }
142
171
 
143
- // generate uuid
144
- if (element.key && !DRAFT_COLUMNS_MAP[element.name] && element.type === 'cds.UUID') {
172
+ if (_shouldGenerateUUID(element)) {
145
173
  categories.push('uuid')
146
174
  }
147
175
 
176
+ if (element.isComposition && element.is2one) {
177
+ categories.push('comp2one')
178
+ }
179
+
148
180
  if (categories.length) return { categories }
149
181
  }
150
182
 
151
- const _pickVirtual = element => {
183
+ const _pickDraft = element => {
152
184
  // collect actions to apply
153
185
  const categories = []
186
+
154
187
  if (element.virtual) categories.push('virtual')
188
+
189
+ // REVISIT: element._foreignKeys.length seems to be a very broad check
190
+ if (element.isAssociation && element._foreignKeys.length) {
191
+ categories.push({ category: 'propagateForeignKeys' })
192
+ }
193
+
194
+ if (_shouldGenerateUUID(element)) {
195
+ categories.push('uuid')
196
+ }
197
+
155
198
  if (categories.length) return { categories }
156
199
  }
157
200
 
@@ -174,10 +217,9 @@ function _handler(req) {
174
217
 
175
218
  let template
176
219
  if (draft) {
177
- // draft -> filter virtual only
178
- template = getTemplate('db-virtual', this, target, { pick: _pickVirtual })
220
+ template = getTemplate('db-input-draft', this, target, { pick: _pickDraft })
179
221
  } else {
180
- template = getTemplate('db-input', this, target, { pick: _pick })
222
+ template = getTemplate('db-input-crud', this, target, { pick: _pickCRUD })
181
223
  }
182
224
 
183
225
  if (template.elements.size === 0) return