@sap/cds 5.6.3 → 5.7.3

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 (187) hide show
  1. package/CHANGELOG.md +135 -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 +3 -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 +0 -34
  31. package/lib/deploy.js +25 -17
  32. package/lib/env/defaults.js +3 -1
  33. package/lib/env/index.js +8 -3
  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 -69
  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 +29 -1
  73. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +7 -1
  74. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +9 -5
  75. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +2 -2
  76. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +13 -10
  77. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -7
  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/stream.js +54 -76
  80. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
  81. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
  82. package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
  83. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
  84. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
  85. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
  86. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -2
  87. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
  88. package/libx/_runtime/cds-services/services/Service.js +0 -6
  89. package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
  90. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -7
  91. package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
  92. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
  93. package/libx/_runtime/cds-services/util/assert.js +1 -262
  94. package/libx/_runtime/cds.js +6 -9
  95. package/libx/_runtime/common/aspects/entity.js +1 -1
  96. package/libx/_runtime/common/composition/delete.js +4 -2
  97. package/libx/_runtime/common/composition/update.js +22 -38
  98. package/libx/_runtime/common/composition/utils.js +3 -7
  99. package/libx/_runtime/common/error/standardError.js +11 -0
  100. package/libx/_runtime/common/generic/auth.js +63 -33
  101. package/libx/_runtime/common/generic/crud.js +11 -23
  102. package/libx/_runtime/common/generic/input.js +20 -0
  103. package/libx/_runtime/common/generic/paging.js +2 -2
  104. package/libx/_runtime/common/generic/put.js +4 -10
  105. package/libx/_runtime/common/generic/sorting.js +12 -30
  106. package/libx/_runtime/common/i18n/messages.properties +2 -0
  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 +298 -121
  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 +38 -52
  119. package/libx/_runtime/db/expand/index.js +3 -1
  120. package/libx/_runtime/db/generic/arrayed.js +3 -1
  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/read.js +12 -8
  125. package/libx/_runtime/db/query/update.js +9 -3
  126. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +8 -9
  127. package/libx/_runtime/{common → db}/utils/propagateForeignKeys.js +11 -14
  128. package/libx/_runtime/fiori/generic/activate.js +1 -0
  129. package/libx/_runtime/fiori/generic/before.js +2 -1
  130. package/libx/_runtime/fiori/generic/edit.js +1 -0
  131. package/libx/_runtime/fiori/generic/patch.js +1 -1
  132. package/libx/_runtime/fiori/generic/read.js +128 -57
  133. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +0 -4
  134. package/libx/_runtime/fiori/uiflex/index.js +1 -1
  135. package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +6 -4
  136. package/libx/_runtime/fiori/utils/delete.js +7 -1
  137. package/libx/_runtime/hana/Service.js +1 -8
  138. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
  139. package/libx/_runtime/hana/execute.js +10 -4
  140. package/libx/_runtime/hana/pool.js +55 -45
  141. package/libx/_runtime/hana/search.js +7 -6
  142. package/libx/_runtime/hana/search2cqn4sql.js +8 -5
  143. package/libx/_runtime/hana/searchToContains.js +3 -1
  144. package/libx/_runtime/index.js +5 -5
  145. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
  146. package/libx/_runtime/messaging/Outbox.js +53 -0
  147. package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
  148. package/libx/_runtime/messaging/common-utils/connections.js +14 -9
  149. package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
  150. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
  151. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
  152. package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
  153. package/libx/_runtime/messaging/file-based.js +5 -5
  154. package/libx/_runtime/messaging/message-queuing-utils/options-messaging.js +1 -0
  155. package/libx/_runtime/messaging/message-queuing.js +2 -3
  156. package/libx/_runtime/messaging/outbox/OutboxRunner.js +75 -0
  157. package/libx/_runtime/messaging/outbox/utils.js +192 -0
  158. package/libx/_runtime/messaging/service.js +16 -30
  159. package/libx/_runtime/remote/Service.js +15 -0
  160. package/libx/_runtime/remote/utils/client.js +15 -3
  161. package/libx/_runtime/remote/utils/{dataConversion.js → data.js} +12 -2
  162. package/libx/_runtime/sqlite/Service.js +7 -10
  163. package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +19 -0
  164. package/libx/_runtime/sqlite/execute.js +18 -12
  165. package/libx/_runtime/types/api.js +2 -1
  166. package/libx/gql/resolvers/parse/ast2cqn/columns.js +1 -1
  167. package/libx/odata/{odata2cqn/afterburner.js → afterburner.js} +28 -16
  168. package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
  169. package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +222 -130
  170. package/libx/odata/index.js +23 -14
  171. package/libx/odata/parser.js +1 -0
  172. package/libx/odata/utils.js +57 -0
  173. package/libx/rest/RestAdapter.js +2 -6
  174. package/libx/rest/utils/data.js +1 -6
  175. package/package.json +4 -3
  176. package/server.js +4 -5
  177. package/srv/audit-log.cds +87 -0
  178. package/{libx/_runtime/fiori/uiflex/extensibility/index.cds → srv/flex.cds} +0 -0
  179. package/srv/flex.js +1 -0
  180. package/srv/outbox.cds +11 -0
  181. package/srv/outbox.js +0 -0
  182. package/libx/_runtime/cds-services/adapter/perf/performance.js +0 -104
  183. package/libx/_runtime/cds-services/adapter/perf/performanceMeasurement.js +0 -33
  184. package/libx/odata/odata2cqn/index.js +0 -3
  185. package/libx/odata/odata2cqn/parser.js +0 -1
  186. package/libx/odata/readme.md +0 -1
  187. package/libx/odata/utils/index.js +0 -64
@@ -1,5 +1,7 @@
1
1
  const cds = require('../../cds')
2
2
 
3
+ const _autoGenerate = e => e && e.type === 'cds.UUID' && e.key
4
+
3
5
  const _generateParentField = ({ parentElement }, row) => {
4
6
  if (_autoGenerate(parentElement) && !row[parentElement.name]) {
5
7
  row[parentElement.name] = cds.utils.uuid()
@@ -14,8 +16,6 @@ const _generateChildField = ({ deep, childElement }, childRow) => {
14
16
  }
15
17
  }
16
18
 
17
- const _autoGenerate = e => e && e.type === 'cds.UUID' && e.key
18
-
19
19
  const _getNestedVal = (row, prefix) => {
20
20
  let val = row
21
21
  const splitted = prefix.split('_')
@@ -52,12 +52,12 @@ const _propagateToParent = ({ parentElement, childElement, deep }, childRow, row
52
52
  if (deep) {
53
53
  _propagateToParent(deep.propagation, childRow[deep.targetName], childRow)
54
54
  }
55
- if (parentElement && childElement && childRow && Object.prototype.hasOwnProperty.call(childRow, childElement.name)) {
55
+ if (parentElement && childElement && childRow && childElement.name in childRow) {
56
56
  row[parentElement.name] = childRow[childElement.name]
57
57
  }
58
58
  }
59
59
 
60
- const propagateForeignKeys = (tKey, row, foreignKeyPropagations, isCompositionEffective) => {
60
+ module.exports = (tKey, row, foreignKeyPropagations, isCompositionEffective) => {
61
61
  const childRows = Array.isArray(row[tKey]) ? row[tKey] : [row[tKey]]
62
62
 
63
63
  for (const childRow of childRows) {
@@ -65,12 +65,13 @@ const propagateForeignKeys = (tKey, row, foreignKeyPropagations, isCompositionEf
65
65
 
66
66
  for (const foreignKeyPropagation of foreignKeyPropagations) {
67
67
  if (foreignKeyPropagation.fillChild) {
68
- _generateParentField(foreignKeyPropagation, row)
69
- if (!isCompositionEffective) {
70
- delete row[tKey]
71
- } else {
72
- _propagateToChid(foreignKeyPropagation, row, childRow)
73
- }
68
+ // propagate or generate in parent
69
+ const pk = foreignKeyPropagation.parentElement && foreignKeyPropagation.parentElement.name
70
+ if (pk && !(pk in row)) _propagateToParent(foreignKeyPropagation, childRow, row)
71
+ if (!(pk in row)) _generateParentField(foreignKeyPropagation, row)
72
+
73
+ if (!isCompositionEffective) delete row[tKey]
74
+ else _propagateToChid(foreignKeyPropagation, row, childRow)
74
75
  } else {
75
76
  _generateChildField(foreignKeyPropagation, childRow)
76
77
  _propagateToParent(foreignKeyPropagation, childRow, row)
@@ -78,7 +79,3 @@ const propagateForeignKeys = (tKey, row, foreignKeyPropagations, isCompositionEf
78
79
  }
79
80
  }
80
81
  }
81
-
82
- module.exports = {
83
- propagateForeignKeys
84
- }
@@ -147,6 +147,7 @@ const _handler = async function (req) {
147
147
  r.getUriInfo = () => req.getUriInfo()
148
148
  r.getUrlObject = () => req.getUrlObject()
149
149
  r._.params = req.params
150
+ r._.query = req.query
150
151
 
151
152
  // use finally to preserve r.messages in success or error case
152
153
  let result
@@ -190,8 +190,9 @@ module.exports = cds.service.impl(function () {
190
190
  _new._initial = true
191
191
  _patchUpdate._initial = true
192
192
  _deleteCancel._initial = true
193
+ const entities = Object.values(this.entities).filter(e => e._isDraftEnabled)
193
194
 
194
- for (const entity of Object.values(this.entities).filter(e => e._isDraftEnabled)) {
195
+ for (const entity of entities) {
195
196
  this.before('NEW', entity, _new)
196
197
  this.before(['PATCH', 'UPDATE'], entity, _patchUpdate)
197
198
  this.before(['DELETE', 'CANCEL'], entity, _deleteCancel)
@@ -130,6 +130,7 @@ const _handler = async function (req) {
130
130
  const lockAndSelectCQNs = [lockRecordCQN, draftExistsCQN, ...selectCQNs]
131
131
 
132
132
  const dbtx = cds.tx(req)
133
+ // REVISIT: Use service.read with expand **
133
134
  const [, draftExists, ...results] = await _select(lockAndSelectCQNs, req, dbtx)
134
135
 
135
136
  if (!results[0].length) {
@@ -62,7 +62,7 @@ const _getUpdateDraftCQN = ({ query, target: { name } }, keysCondition) => {
62
62
  * @param req
63
63
  */
64
64
  const _handler = async function (req) {
65
- if (req.data.IsActiveEntity === 'true') req.reject(400)
65
+ if (req.data.IsActiveEntity === 'true') req.reject(400, 'Patch can only be applied to a draft entity')
66
66
 
67
67
  const keysCondition = getKeysCondition(req.target, req.data)
68
68
 
@@ -1,7 +1,7 @@
1
1
  const cds = require('../../cds')
2
2
  const { SELECT } = cds.ql
3
3
 
4
- const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
4
+ const { cqn2cqn4sql, convertWhereExists } = require('../../common/utils/cqn2cqn4sql')
5
5
  const { getElementDeep } = require('../../common/utils/csn')
6
6
 
7
7
  const { DRAFT_COLUMNS, DRAFT_COLUMNS_MAP, SCENARIO } = require('../../common/constants/draft')
@@ -21,26 +21,51 @@ const { deleteCondition, readAndDeleteKeywords, removeIsActiveEntityRecursively
21
21
 
22
22
  const { getColumns } = require('../../cds-services/services/utils/columns')
23
23
 
24
+ const _findRootSubSelectFor = query => {
25
+ if (query.SELECT.where) {
26
+ const subSelect = query.SELECT.where.find((e, i) => e.SELECT && query.SELECT.where[i - 1] === 'exists')
27
+ return subSelect ? _findRootSubSelectFor(subSelect) : query
28
+ }
29
+ return query
30
+ }
31
+
24
32
  // append where with clauses from @restrict
25
33
  const _getWhereWithAppendedDraftRestrictions = (where = [], req, scenarioAlias, model) => {
26
34
  if (req.query._draftRestrictions) {
27
35
  for (const each of req.query._draftRestrictions) {
28
- if (where.length) where.push('and')
29
-
30
- // REVISIT: remove with cds^6
31
- // adjust alias of @restrict where "exists (select ...)"
32
- if (scenarioAlias && model)
33
- each
34
- .filter(e => e.SELECT && e.SELECT.from && e.SELECT.where)
35
- .forEach(e => {
36
- const entity = model.definitions[e.SELECT.from.ref[0]]
37
- e.SELECT.where = e.SELECT.where.map(w => {
38
- if (w.ref && w.ref.length === 1 && !entity.elements[w.ref[0]]) w.ref.unshift(scenarioAlias)
39
- return w
36
+ const xpr = each._xpr
37
+ if (each.target.name === ensureUnlocalized(req.target.name)) {
38
+ // > restriction directly on child
39
+ // REVISIT: remove support for selects in restriction with cds^6
40
+ // adjust alias of @restrict where "exists (select ...)"
41
+ if (scenarioAlias && model)
42
+ xpr
43
+ .filter(e => e.SELECT && e.SELECT.from && e.SELECT.where)
44
+ .forEach(e => {
45
+ const entity = model.definitions[e.SELECT.from.ref[0]]
46
+ e.SELECT.where = e.SELECT.where.map(w => {
47
+ if (w.ref && w.ref.length === 1 && !entity.elements[w.ref[0]]) w.ref.unshift(scenarioAlias)
48
+ return w
49
+ })
40
50
  })
41
- })
42
51
 
43
- where.push(...each)
52
+ if (where.length) where.push('and')
53
+ where.push(...xpr)
54
+ } else {
55
+ // > restriction inherited from parent via autoexposure
56
+ // find inner most sub select if available and append restriction to where clause
57
+ const rootSubSelect = _findRootSubSelectFor({ SELECT: { where } })
58
+ if (rootSubSelect && rootSubSelect.SELECT.from) {
59
+ if (rootSubSelect.SELECT.where && rootSubSelect.SELECT.where.length) rootSubSelect.SELECT.where.push('and')
60
+ const tableAlias = rootSubSelect.SELECT.from.as
61
+ rootSubSelect.SELECT.where.push(
62
+ ...xpr.map(e => {
63
+ if (e.ref) return tableAlias ? { ref: [tableAlias, ...e.ref] } : { ref: [...e.ref] }
64
+ return e
65
+ })
66
+ )
67
+ }
68
+ }
44
69
  }
45
70
  }
46
71
  return where
@@ -299,21 +324,24 @@ const _allInactive = (req, columns) => {
299
324
  _getDefaultDraftProperties({ hasDraft: false, isActive: false, withDraftUUID: false })
300
325
  )
301
326
 
302
- const ids = filterKeys(req.target.keys)
303
327
  const isCount = columns.some(element => element.func === 'count')
304
328
 
329
+ // ensure only own drafts are read
305
330
  const cqn = SELECT.from(table)
331
+ .join('DRAFT.DraftAdministrativeData', 'filterAdmin')
332
+ .on([
333
+ { ref: [table.as, 'DraftAdministrativeData_DraftUUID'] },
334
+ '=',
335
+ {
336
+ ref: ['filterAdmin', 'DraftUUID']
337
+ }
338
+ ])
339
+ .where(_inProcessByUserWhere(req.user.id))
306
340
 
307
341
  if (isCount) {
308
342
  cqn.columns(...outerMostColumns)
309
343
  } else {
310
344
  cqn.columns(...outerMostColumns.filter(o => o.as !== 'HasActiveEntity'), { ref: ['HasActiveEntity'] })
311
- cqn.leftJoin(ensureNoDraftsSuffix(table.ref[0]) + ' as active').on(`${table.as}.${ids[0]} = active.${ids[0]}`)
312
-
313
- for (let i = 1; i < ids.length; i++) {
314
- // REVISIT: this is extremely expensive as it repeatedly invokes the compiler's cds.parse.expr -> better extend plain CQN yourself here
315
- cqn.and(`${table.as}.${ids[i]} = active.${ids[i]}`)
316
- }
317
345
  }
318
346
 
319
347
  cqn.where(req.query.SELECT.where)
@@ -507,11 +535,11 @@ const _activeWithDraftInProcess = (req, draftWhere, columns, isLocked) => {
507
535
  subSelect
508
536
  )
509
537
 
510
- subSelect.SELECT.where = _getWhereWithAppendedDraftRestrictions(subSelect.SELECT.where, req)
511
-
512
538
  const outerMostColumns = _getOuterMostColumns(columns, draftColumns)
513
539
 
514
- const cqn = SELECT.from(active.table).columns(outerMostColumns).where(['exists', subSelect])
540
+ const cqn = SELECT.from(active.table).columns(outerMostColumns)
541
+ cqn.where(_getWhereWithAppendedDraftRestrictions([], req))
542
+ cqn.where(['exists', subSelect])
515
543
 
516
544
  return { cqn: getEnrichedCQN(cqn, req.query.SELECT, draftWhere), scenario: SCENARIO.DRAFT_IN_PROCESS }
517
545
  }
@@ -598,13 +626,21 @@ const _getWhereForActive = where => {
598
626
  return activeWhere
599
627
  }
600
628
 
601
- const _siblingEntity = ({ query, target, nav, params }, columns, model, draftAdminAlias, parentQuery, siblingIndex) => {
629
+ const _siblingEntity = (
630
+ { query, target, nav, params },
631
+ columns,
632
+ model,
633
+ draftAdminAlias,
634
+ parentQuery,
635
+ siblingIndex,
636
+ req
637
+ ) => {
602
638
  const parentLinks = parentQuery ? _findJoinInQuery(query, parentQuery.SELECT.from.as) : []
603
639
  const keys = (nav[siblingIndex + 1].where && (params[siblingIndex] || params[0])) || {}
604
640
  const siblingQuery = query.SELECT.where[query.SELECT.where.indexOf('exists') + 1]
605
641
  const onCond = _findJoinInQuery(siblingQuery, target.as)
606
642
  const siblingAlias = siblingQuery.SELECT.from.as
607
- const subScenario = _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, model, onCond)
643
+ const subScenario = _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, model, onCond, req)
608
644
  const isSiblingDraft = subScenario
609
645
  ? subScenario.isSiblingActive || subScenario.scenario === 'ACTIVE' || subScenario.scenario === 'ALL_ACTIVE'
610
646
  : keys.IsActiveEntity && keys.IsActiveEntity !== 'false'
@@ -652,12 +688,17 @@ const _siblingEntity = ({ query, target, nav, params }, columns, model, draftAdm
652
688
  return { cqn, scenario: SCENARIO.SIBLING_ENTITY, isSiblingActive: !isSiblingDraft }
653
689
  }
654
690
 
655
- function _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, model, onCond) {
691
+ function _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, model, onCond, req) {
656
692
  if (nav[siblingIndex + 1].where) return
657
693
  let subScenario
658
694
  const subNav = nav.slice(siblingIndex + 1)
659
695
  const subSiblingIndex = subNav.indexOf('SiblingEntity')
660
- const subReq = { query: siblingQuery, target: model.definitions[target.name], params: [...params].reverse() }
696
+ const subReq = {
697
+ query: siblingQuery,
698
+ target: model.definitions[target.name],
699
+ params: [...params].reverse(),
700
+ user: req.user
701
+ }
661
702
  if (subSiblingIndex > -1) {
662
703
  subScenario = _getSiblingScenario(subReq, [{ val: 1 }], model, subSiblingIndex, subNav, params)
663
704
  if (subSiblingIndex > 0) {
@@ -670,8 +711,8 @@ function _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, mo
670
711
  subReq.query = SELECT.from(siblingQuery.SELECT.from).columns([{ val: 1 }])
671
712
  const existsIdx = siblingQuery.SELECT.where.indexOf('exists')
672
713
  if (existsIdx > -1) subReq.query.where(siblingQuery.SELECT.where.slice(existsIdx, existsIdx + 2))
673
- const subReqOrig = { query: { SELECT: { from: { ref: [...subNav].reverse() } } } }
674
- subScenario = _generateCQN(subReqOrig, subReq, [{ val: 1 }], model)
714
+ const subOrigFrom = { ref: [...subNav].reverse() }
715
+ subScenario = _generateCQN(subOrigFrom, subReq, [{ val: 1 }], model)
675
716
  subScenario.cqn.where(onCond)
676
717
  }
677
718
  return subScenario
@@ -688,7 +729,15 @@ const _getSiblingScenario = (req, columns, model, siblingIndex, nav) => {
688
729
  }
689
730
  }
690
731
  const target = { name: query.SELECT.from.ref[0].id || query.SELECT.from.ref[0], as: query.SELECT.from.as }
691
- return _siblingEntity({ query, target, params, nav }, columns, model, draftAdminAlias, parentQuery, siblingIndex)
732
+ return _siblingEntity(
733
+ { query, target, params, nav },
734
+ columns,
735
+ model,
736
+ draftAdminAlias,
737
+ parentQuery,
738
+ siblingIndex,
739
+ req
740
+ )
692
741
  }
693
742
  return _getSiblingQueryFromWhere(req.query, siblingIndex)
694
743
  }
@@ -731,7 +780,7 @@ const _getDraftDoc = (req, draftName, draftWhere) => {
731
780
 
732
781
  const _getOrderByEnrichedColumns = (orderBy, columns) => {
733
782
  const enrichedCol = []
734
- if (orderBy.length > 1) {
783
+ if (orderBy && orderBy.length > 1) {
735
784
  const colNames = columns.map(el => el.ref[el.ref.length - 1])
736
785
  // REVISIT: GET Books?$select=title&$expand=NotBooks($select=pages)&$orderby=NotBooks/title - what's then?
737
786
  for (const el of orderBy) {
@@ -757,15 +806,19 @@ const _replaceDraftAlias = where => {
757
806
 
758
807
  const _poorMansAlias4 = xpr => '_' + xpr.ref.join('_') + '_'
759
808
 
760
- const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere) => {
809
+ const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere, model) => {
761
810
  const draftActiveWhere = _getWhereForActive(draftWhere)
762
811
  const activeDocs = getEnrichedCQN(SELECT.from(req.target), req.query.SELECT, draftActiveWhere, undefined, false)
812
+ activeDocs.where(_getWhereWithAppendedDraftRestrictions([], req))
813
+ convertWhereExists(activeDocs, model, {})
763
814
 
815
+ // @restrict.where not applicable for drafts (I can ALWAYS read mine)
764
816
  _replaceDraftAlias(draftWhere)
765
817
  const draftDocs = _getDraftDoc(req, draftName, draftWhere)
766
818
 
767
819
  const union = SELECT.from({ SET: { op: 'union', all: true, args: [draftDocs, activeDocs] } })
768
820
  if (req.query.SELECT.count) union.SELECT.count = true
821
+ if (req.query.SELECT.__countAggregated) union.SELECT.__countAggregated = true
769
822
 
770
823
  if (req.query.SELECT.from.as) {
771
824
  draftDocs.SELECT.from.as = req.query.SELECT.from.as
@@ -846,7 +899,7 @@ const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere) => {
846
899
  .columns(..._filterDraftColumnsBySelected(DRAFT_COLUMNS_CASTED, req.query.SELECT.columns))
847
900
  }
848
901
 
849
- const _excludeActiveDraftExists = (req, draftWhere, columns) => {
902
+ const _excludeActiveDraftExists = (req, draftWhere, columns, model) => {
850
903
  const { table, name } = _getTableName(req, true)
851
904
  const draftName = table.ref[0]
852
905
 
@@ -865,10 +918,8 @@ const _excludeActiveDraftExists = (req, draftWhere, columns) => {
865
918
  subSelect.where([{ ref: [ensureNoDraftsSuffix(req.target.name), key] }, '=', { ref: [draftName, key] }])
866
919
  }
867
920
 
868
- draftWhere = _getWhereWithAppendedDraftRestrictions(draftWhere, req)
869
-
870
921
  draftWhere = removeIsActiveEntityRecursively(draftWhere)
871
- const cqn = _getUnionCQN(req, draftName, columns, subSelect, draftWhere)
922
+ const cqn = _getUnionCQN(req, draftName, columns, subSelect, draftWhere, model)
872
923
  cqn.SELECT.from.as = name
873
924
 
874
925
  if (cqn.SELECT.orderBy) {
@@ -900,13 +951,13 @@ const _validatedActiveWithoutDraft = (req, draftWhere, draftParameters, columns)
900
951
  _isValidActiveWithoutDraft(draftParameters.isActiveEntity, draftParameters.hasDraftEntity) &&
901
952
  _activeWithoutDraft(req, draftWhere, columns)
902
953
 
903
- const _validatedWithSiblingInProcess = (req, draftWhere, draftParameters, columns) => {
954
+ const _validatedWithSiblingInProcess = (req, draftWhere, draftParameters, columns, model) => {
904
955
  const { isActiveEntity, siblingIsActive, draftInProcessByUser } = draftParameters
905
956
  if (
906
957
  !draftInProcessByUser &&
907
958
  _isValidExcludeActiveDraftExists(draftParameters.isActiveEntity, draftParameters.siblingIsActive)
908
959
  )
909
- return _excludeActiveDraftExists(req, draftWhere, columns)
960
+ return _excludeActiveDraftExists(req, draftWhere, columns, model)
910
961
  if (
911
962
  draftInProcessByUser.op === '!=' &&
912
963
  _isValidWithDraftLocked(isActiveEntity, siblingIsActive, draftInProcessByUser)
@@ -925,9 +976,7 @@ const _draftInSubSelect = (where, req) => {
925
976
  if (SELECT && SELECT.where) {
926
977
  const isActiveEntity = readAndDeleteKeywords(['IsActiveEntity'], SELECT.where, false)
927
978
  if (isActiveEntity) {
928
- const isFalse = _isFalse(isActiveEntity.value.val)
929
- if (isFalse) SELECT.where = _getWhereWithAppendedDraftRestrictions(SELECT.where, req)
930
- return isFalse
979
+ return _isFalse(isActiveEntity.value.val)
931
980
  }
932
981
 
933
982
  return _draftInSubSelect(SELECT.where, req)
@@ -940,10 +989,16 @@ const _draftInSubSelect = (where, req) => {
940
989
  const _isDraftAdminScenario = req =>
941
990
  req.target.query && req.target.query._target && req.target.query._target.name === 'DRAFT.DraftAdministrativeData'
942
991
 
943
- // eslint-disable-next-line complexity
944
- const _generateCQN = (reqOriginal, req, columns, model) => {
945
- const nav = [...reqOriginal.query.SELECT.from.ref].reverse() || []
946
- const siblingIndex = nav.indexOf('SiblingEntity')
992
+ const _generateCQN = (originalFrom, req, columns, model) => {
993
+ const nav = [...originalFrom.ref].reverse() || []
994
+ let siblingIndex = nav.indexOf('SiblingEntity')
995
+
996
+ // it can also be a property access (new parser), then we must shift it
997
+ if (siblingIndex === 1 && req.target.elements[nav[0]]) {
998
+ nav.shift()
999
+ siblingIndex = siblingIndex - 1
1000
+ }
1001
+
947
1002
  let siblingScenario
948
1003
  if (siblingIndex > -1) {
949
1004
  siblingScenario = _getSiblingScenario(req, columns, model, siblingIndex, nav)
@@ -975,8 +1030,6 @@ const _generateCQN = (reqOriginal, req, columns, model) => {
975
1030
  }
976
1031
 
977
1032
  if (!draftParameters.isActiveEntity) {
978
- // _draftInSubSelect adds draft restrictions in case check is truthy
979
- // -> not nice but works for now and we don't need to go in recursively again
980
1033
  if (_draftInSubSelect(req.query.SELECT.where, req) || (siblingScenario && !siblingScenario.isSiblingActive)) {
981
1034
  // this is only the case when navigating into tree
982
1035
  return _allInactive(req, columns)
@@ -989,23 +1042,20 @@ const _generateCQN = (reqOriginal, req, columns, model) => {
989
1042
  }
990
1043
 
991
1044
  if (draftParameters.siblingIsActive) {
992
- return _validatedWithSiblingInProcess(req, req.query.SELECT.where, draftParameters, columns)
1045
+ return _validatedWithSiblingInProcess(req, req.query.SELECT.where, draftParameters, columns, model)
993
1046
  }
994
1047
 
995
1048
  return _validatedDraftOfWhichIAmOwner(req, req.query.SELECT.where, draftParameters, columns)
996
1049
  }
997
1050
 
998
- const _getColumns = ({ query: { SELECT } }, model) => {
1051
+ const _getColumns = ({ query: { SELECT }, target }, model) => {
999
1052
  return SELECT.columns
1000
1053
  ? SELECT.columns.filter(
1001
1054
  col =>
1002
1055
  (col.ref && !DRAFT_COLUMNS.includes(col.ref[col.ref.length - 1])) ||
1003
1056
  (!col.ref && !DRAFT_COLUMNS.includes(col))
1004
1057
  )
1005
- : getColumns(model.definitions[ensureNoDraftsSuffix(SELECT.from.ref[0])], {
1006
- onlyNames: true,
1007
- removeIgnore: true
1008
- })
1058
+ : getColumns(target, { onlyNames: true, removeIgnore: true })
1009
1059
  }
1010
1060
 
1011
1061
  const _isIsActiveEntity = element => element.ref && element.ref[element.ref.length - 1] === 'IsActiveEntity'
@@ -1035,6 +1085,10 @@ const _adaptSubSelects = ({ SELECT: { from, where } }, scenario) => {
1035
1085
  }
1036
1086
  } else if (element.SELECT) {
1037
1087
  _adaptSubSelects(element, scenario)
1088
+ } else if (element.xpr) {
1089
+ for (const ele of element.xpr.filter(e => e.SELECT)) {
1090
+ _adaptSubSelects(ele, scenario)
1091
+ }
1038
1092
  }
1039
1093
  }
1040
1094
  }
@@ -1136,6 +1190,13 @@ const _getLocalizedEntity = (model, target, user) => {
1136
1190
  return localizedEntity || model.definitions[`${prefix}.${target.name}`]
1137
1191
  }
1138
1192
 
1193
+ const _getLastSubQuery = query => (query.SELECT.from.SELECT ? _getLastSubQuery(query.SELECT.from) : query)
1194
+ const _setLastSubQuery = (query, last, prev = query) => {
1195
+ if (query.SELECT.from.SELECT) return _setLastSubQuery(query.SELECT.from, last, query)
1196
+ else prev.SELECT.from = _copyCQNPartial(last)
1197
+ return prev
1198
+ }
1199
+
1139
1200
  const _adaptDraftAdminExpand = cqn => {
1140
1201
  const draftAdminExpand =
1141
1202
  cqn.SELECT.columns && cqn.SELECT.columns.find(c => c.expand && c.ref[0] === 'DraftAdministrativeData')
@@ -1149,7 +1210,6 @@ const _adaptDraftAdminExpand = cqn => {
1149
1210
  *
1150
1211
  * @param req
1151
1212
  */
1152
- // eslint-disable-next-line complexity
1153
1213
  const _handler = async function (req) {
1154
1214
  // handle localized here as it was previously handled for req.target
1155
1215
  req.target = _getLocalizedEntity(this.model, req.target, req.user) || req.target
@@ -1157,6 +1217,8 @@ const _handler = async function (req) {
1157
1217
  // REVISIT
1158
1218
  delete req.query._validationQuery
1159
1219
 
1220
+ const originalFrom = _copyCQNPartial(req.query.SELECT.from)
1221
+
1160
1222
  // REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called here
1161
1223
  const sqlQuery = cqn2cqn4sql(req.query, this.model, { draft: true })
1162
1224
 
@@ -1171,8 +1233,17 @@ const _handler = async function (req) {
1171
1233
  reqClone.query._streaming = true
1172
1234
  return cds.tx(req).run(reqClone.query)
1173
1235
  }
1236
+ let cqnScenario
1174
1237
 
1175
- const cqnScenario = _generateCQN(req, reqClone, _getColumns(reqClone, this.model), this.model)
1238
+ // to replace scenario CQNs for queries with $apply SELECT chain (new odata2cqn parser)
1239
+ // just to make existing tests working with new parser. not really tested, not needed to be supported
1240
+ if (reqClone.query.SELECT.from.SELECT) {
1241
+ const subQueryReq = { __proto__: req, query: _copyCQNPartial(_getLastSubQuery(reqClone.query)) }
1242
+ cqnScenario = _generateCQN(originalFrom.SELECT.from, subQueryReq, _getColumns(subQueryReq, this.model), this.model)
1243
+ cqnScenario.cqn = _setLastSubQuery(reqClone.query, cqnScenario.cqn)
1244
+ } else {
1245
+ cqnScenario = _generateCQN(originalFrom, reqClone, _getColumns(reqClone, this.model), this.model)
1246
+ }
1176
1247
 
1177
1248
  if (!cqnScenario) {
1178
1249
  req.reject(400)
@@ -16,10 +16,6 @@ const _processorFn = ({ row, key }) => {
16
16
 
17
17
  delete row[EXT_BACK_PACK]
18
18
  }
19
-
20
- if (row[key] === undefined) {
21
- row[key] = null
22
- }
23
19
  }
24
20
 
25
21
  function transformExtendedFieldsRESULT(result, req) {
@@ -29,7 +29,7 @@ module.exports = async () => {
29
29
  .before('READ', transformExtendedFieldsREAD)
30
30
  .after('READ', transformExtendedFieldsRESULT)
31
31
  if ('cds_r.ExtensibilityService' in cds.services) return
32
- const model = require('path').join(__dirname, 'extensibility')
32
+ const model = require('path').join(__dirname, '../../../..', 'srv/flex.cds')
33
33
  return cds.serve(model, { silent: true }).to('odata').in(cds.app)
34
34
  })
35
35
  }
@@ -1,7 +1,7 @@
1
- const cds = require('../../../cds')
2
- const { ensureDraftsSuffix } = require('../../../common/utils/draft')
1
+ const cds = require('../../cds')
2
+ const { ensureDraftsSuffix } = require('../../common/utils/draft')
3
3
 
4
- const { EXT_BACK_PACK } = require('../utils')
4
+ const { EXT_BACK_PACK } = require('./utils')
5
5
 
6
6
  const _getDraftTable = (view, cds) => {
7
7
  return cds.model.definitions[view]._isDraftEnabled ? ensureDraftsSuffix(view) : undefined
@@ -55,13 +55,15 @@ const _addViews = csn => {
55
55
  })
56
56
  }
57
57
 
58
+ const _needsQuotations = t => t instanceof cds.builtin.classes.string || t instanceof cds.builtin.classes.date
59
+
58
60
  const _handleDefaults = async (extension, dbEntity, req, cds, draftEntity) => {
59
61
  const ext = Object.keys(extension.elements)
60
62
  .filter(key => extension.elements[key].default)
61
63
  .map(key => {
62
64
  const element = extension.elements[key]
63
65
  const t = cds.model.definitions[element.type] || cds.builtin.types[element.type]
64
- const value = t && t instanceof cds.builtin.classes.string ? `"${element.default.val}"` : element.default.val
66
+ const value = t && _needsQuotations(t) ? `"${element.default.val}"` : element.default.val
65
67
  return `"${key}":${value}`
66
68
  })
67
69
 
@@ -75,7 +75,13 @@ const deleteDraft = async (req, srv, includingActive = false) => {
75
75
  const delCQNs = []
76
76
 
77
77
  if (includingActive) {
78
- delCQNs.push(DELETE.from(ensureNoDraftsSuffix(req.target.name)).where(keys.keyList))
78
+ const r = new cds.Request({ query: DELETE.from(ensureNoDraftsSuffix(req.target.name)).where(keys.keyList) })
79
+
80
+ // REVISIT: should not be necessary
81
+ r._ = Object.assign(r._, req._)
82
+ r._.query = req.query
83
+
84
+ await dbtx.dispatch(r)
79
85
  }
80
86
 
81
87
  if (draftResult.length !== 0) {
@@ -45,13 +45,6 @@ class HanaDatabase extends DatabaseService {
45
45
  this._run = this._queries.run(this._insert, this._read, this._update, this._delete, execute.cqn, execute.sql)
46
46
  }
47
47
 
48
- set model(csn) {
49
- const m = csn && 'definitions' in csn ? cds.linked(cds.compile.for.odata(csn)) : csn
50
- // with compiler v2 we always need to localized the csn
51
- cds.alpha_localized(m)
52
- super.model = m
53
- }
54
-
55
48
  init() {
56
49
  this._registerBeforeHandlers()
57
50
  this._registerOnHandlers()
@@ -134,7 +127,7 @@ class HanaDatabase extends DatabaseService {
134
127
  async acquire(arg) {
135
128
  // REVISIT: remove fallback arg.user.tenant with cds^6
136
129
  const tenant = (typeof arg === 'string' ? arg : arg.tenant || (arg.user && arg.user.tenant)) || 'anonymous'
137
- const dbc = await pool.acquire(tenant, this.options.credentials)
130
+ const dbc = await pool.acquire(tenant, this)
138
131
 
139
132
  if (typeof arg !== 'string') {
140
133
  _setSessionContext(dbc, 'APPLICATIONUSER', arg.user.id || 'ANONYMOUS')
@@ -34,18 +34,9 @@ class CustomSelectBuilder extends SelectBuilder {
34
34
  }
35
35
 
36
36
  getParameters() {
37
- // REVISIT: remove skipWithParameters after grace period
38
- if (
39
- (cds.env.features && cds.env.features.with_parameters === false) ||
40
- (cds.env.runtime && cds.env.runtime.skipWithParameters)
41
- ) {
42
- return ''
43
- }
44
-
45
- // REVISIT: remove feature flag skip_with_parameters after grace period of at least two months (> June release)
46
- if (cds.env.features && cds.env.features.skip_with_parameters === false) {
47
- return this._options.locale ? `with parameters ('LOCALE' = '${this._options.locale}')` : ''
48
- }
37
+ const { with_parameters: withParameters } = cds.env.features
38
+ if (withParameters === false || !this._options.locale) return ''
39
+ if (withParameters === true) return `with parameters ('LOCALE' = '${this._options.locale}')`
49
40
 
50
41
  // skip with parameters if all orderby columns are not strings
51
42
  let skip
@@ -78,9 +69,9 @@ class CustomSelectBuilder extends SelectBuilder {
78
69
  }
79
70
  }
80
71
  }
81
- if (skip) return ''
82
72
 
83
- return this._options.locale ? `with parameters ('LOCALE' = '${this._options.locale}')` : ''
73
+ if (skip) return ''
74
+ return `with parameters ('LOCALE' = '${this._options.locale}')`
84
75
  }
85
76
  }
86
77
 
@@ -7,7 +7,7 @@ const {
7
7
  getStructMapper,
8
8
  postProcess
9
9
  } = require('../db/data-conversion/post-processing')
10
- const { createJoinCQNFromExpanded, hasExpand, rawToExpanded } = require('../db/expand')
10
+ const { createJoinCQNFromExpanded, hasExpand, rawToExpanded, expandV2 } = require('../db/expand')
11
11
  const {
12
12
  hasStreamInsert,
13
13
  hasStreamUpdate,
@@ -33,6 +33,8 @@ function _cqnToSQL(model, query, user, locale, txTimestamp) {
33
33
  const cds = require('../cds')
34
34
  const LOG = cds.log('hana|db|sql')
35
35
 
36
+ const SANITIZE_VALUES = process.env.NODE_ENV === 'production' && cds.env.log.sanitize_values !== false
37
+
36
38
  function _getOutputParameters(stmt) {
37
39
  const result = {}
38
40
  const info = stmt.getParameterInfo()
@@ -50,7 +52,7 @@ function _executeAsPreparedStatement(dbc, sql, values, reject, resolve) {
50
52
  dbc.prepare(sql, function (err, stmt) {
51
53
  if (err) {
52
54
  err.query = sql
53
- if (values) err.values = values
55
+ if (values) err.values = SANITIZE_VALUES ? ['***'] : values
54
56
  return reject(err)
55
57
  }
56
58
 
@@ -76,7 +78,7 @@ function _executeAsPreparedStatement(dbc, sql, values, reject, resolve) {
76
78
  if (err) {
77
79
  stmt.drop(() => {})
78
80
  err.query = sql
79
- if (values) err.values = values
81
+ if (values) err.values = SANITIZE_VALUES ? ['***'] : values
80
82
  return reject(err)
81
83
  }
82
84
 
@@ -99,7 +101,7 @@ function _executeSimpleSQL(dbc, sql, values) {
99
101
  const res = sql.match(regex)
100
102
  if (res) sql = sql.replace(regex, '') + ' ' + res[0]
101
103
 
102
- LOG._debug && LOG.debug(`${sql} ${values && values.length ? JSON.stringify(values) : ''}`)
104
+ LOG._debug && LOG.debug(sql, SANITIZE_VALUES ? ['***'] : values)
103
105
  return new Promise((resolve, reject) => {
104
106
  // hana-client only accepts arrays
105
107
  if (dbc.name !== 'hdb' && typeof values === 'object') {
@@ -149,6 +151,10 @@ function _processExpand(model, dbc, cqn, user, locale, txTimestamp) {
149
151
 
150
152
  function executeSelectCQN(model, dbc, query, user, locale, txTimestamp) {
151
153
  if (hasExpand(query)) {
154
+ // expand: '**' or '*3' is handled by new impl
155
+ if (query.SELECT.columns.some(c => c.expand && typeof c.expand === 'string' && /^\*{1}[\d|*]+/.test(c.expand))) {
156
+ return expandV2(model, dbc, query, user, locale, txTimestamp, executeSelectCQN)
157
+ }
152
158
  return _processExpand(model, dbc, query, user, locale, txTimestamp)
153
159
  }
154
160