@sap/cds 5.6.2 → 5.7.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 (183) hide show
  1. package/CHANGELOG.md +133 -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 +0 -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/validator/ConditionalRequestValidator.js +0 -8
  78. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +54 -76
  79. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
  80. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
  81. package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
  82. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
  83. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
  84. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
  85. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -2
  86. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
  87. package/libx/_runtime/cds-services/services/Service.js +0 -6
  88. package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
  89. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -7
  90. package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
  91. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
  92. package/libx/_runtime/cds-services/util/assert.js +1 -262
  93. package/libx/_runtime/cds.js +6 -9
  94. package/libx/_runtime/common/aspects/entity.js +1 -1
  95. package/libx/_runtime/common/composition/delete.js +4 -2
  96. package/libx/_runtime/common/composition/update.js +22 -35
  97. package/libx/_runtime/common/composition/utils.js +3 -7
  98. package/libx/_runtime/common/error/standardError.js +11 -0
  99. package/libx/_runtime/common/generic/auth.js +63 -33
  100. package/libx/_runtime/common/generic/crud.js +11 -23
  101. package/libx/_runtime/common/generic/input.js +20 -0
  102. package/libx/_runtime/common/generic/paging.js +2 -2
  103. package/libx/_runtime/common/generic/put.js +4 -10
  104. package/libx/_runtime/common/generic/sorting.js +12 -30
  105. package/libx/_runtime/common/perf/index.js +24 -0
  106. package/libx/_runtime/common/utils/cqn.js +58 -1
  107. package/libx/_runtime/common/utils/cqn2cqn4sql.js +297 -121
  108. package/libx/_runtime/common/utils/csn.js +38 -56
  109. package/libx/_runtime/common/utils/entityFromCqn.js +6 -6
  110. package/libx/_runtime/common/utils/resolveView.js +4 -5
  111. package/libx/_runtime/common/utils/rewriteAsterisks.js +46 -5
  112. package/libx/_runtime/common/utils/search2cqn4sql.js +21 -9
  113. package/libx/_runtime/common/utils/structured.js +35 -25
  114. package/libx/_runtime/db/Service.js +0 -6
  115. package/libx/_runtime/db/expand/expand-v2.js +130 -0
  116. package/libx/_runtime/db/expand/expandCQNToJoin.js +38 -52
  117. package/libx/_runtime/db/expand/index.js +3 -1
  118. package/libx/_runtime/db/generic/arrayed.js +3 -1
  119. package/libx/_runtime/db/generic/input.js +52 -10
  120. package/libx/_runtime/db/generic/integrity.js +367 -26
  121. package/libx/_runtime/db/generic/virtual.js +51 -13
  122. package/libx/_runtime/db/query/update.js +9 -3
  123. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +8 -9
  124. package/libx/_runtime/{common → db}/utils/propagateForeignKeys.js +11 -14
  125. package/libx/_runtime/fiori/generic/activate.js +1 -0
  126. package/libx/_runtime/fiori/generic/before.js +2 -1
  127. package/libx/_runtime/fiori/generic/edit.js +1 -0
  128. package/libx/_runtime/fiori/generic/patch.js +1 -1
  129. package/libx/_runtime/fiori/generic/read.js +155 -57
  130. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +0 -4
  131. package/libx/_runtime/fiori/uiflex/index.js +1 -1
  132. package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +6 -4
  133. package/libx/_runtime/fiori/utils/delete.js +7 -1
  134. package/libx/_runtime/hana/Service.js +1 -8
  135. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
  136. package/libx/_runtime/hana/execute.js +10 -4
  137. package/libx/_runtime/hana/pool.js +55 -45
  138. package/libx/_runtime/hana/search.js +7 -6
  139. package/libx/_runtime/hana/search2cqn4sql.js +8 -5
  140. package/libx/_runtime/hana/searchToContains.js +3 -1
  141. package/libx/_runtime/index.js +5 -5
  142. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
  143. package/libx/_runtime/messaging/Outbox.js +53 -0
  144. package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
  145. package/libx/_runtime/messaging/common-utils/connections.js +14 -9
  146. package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
  147. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
  148. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
  149. package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
  150. package/libx/_runtime/messaging/file-based.js +5 -5
  151. package/libx/_runtime/messaging/message-queuing.js +2 -3
  152. package/libx/_runtime/messaging/outbox/OutboxRunner.js +75 -0
  153. package/libx/_runtime/messaging/outbox/utils.js +192 -0
  154. package/libx/_runtime/messaging/service.js +16 -30
  155. package/libx/_runtime/remote/Service.js +15 -0
  156. package/libx/_runtime/remote/utils/client.js +15 -3
  157. package/libx/_runtime/remote/utils/{dataConversion.js → data.js} +12 -2
  158. package/libx/_runtime/sqlite/Service.js +7 -10
  159. package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +19 -0
  160. package/libx/_runtime/sqlite/execute.js +18 -12
  161. package/libx/_runtime/types/api.js +2 -1
  162. package/libx/gql/resolvers/parse/ast2cqn/columns.js +1 -1
  163. package/libx/odata/{odata2cqn/afterburner.js → afterburner.js} +28 -16
  164. package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
  165. package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +182 -118
  166. package/libx/odata/index.js +18 -15
  167. package/libx/odata/parser.js +1 -0
  168. package/libx/odata/utils.js +57 -0
  169. package/libx/rest/RestAdapter.js +2 -6
  170. package/libx/rest/utils/data.js +1 -6
  171. package/package.json +4 -3
  172. package/server.js +4 -5
  173. package/srv/audit-log.cds +87 -0
  174. package/{libx/_runtime/fiori/uiflex/extensibility/index.cds → srv/flex.cds} +0 -0
  175. package/srv/flex.js +1 -0
  176. package/srv/outbox.cds +11 -0
  177. package/srv/outbox.js +0 -0
  178. package/libx/_runtime/cds-services/adapter/perf/performance.js +0 -104
  179. package/libx/_runtime/cds-services/adapter/perf/performanceMeasurement.js +0 -33
  180. package/libx/odata/odata2cqn/index.js +0 -3
  181. package/libx/odata/odata2cqn/parser.js +0 -1
  182. package/libx/odata/readme.md +0 -1
  183. package/libx/odata/utils/index.js +0 -64
@@ -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
@@ -257,12 +282,29 @@ const _getOuterMostColumns = (columnsFromRequest, additionalDraftColumns) => {
257
282
  return columns
258
283
  }
259
284
 
285
+ // adds base columns 'InProcessByUser' and 'CreatedByUser' to columns param if needed
286
+ // those are required for calculating 'DraftIsProcessedByMe' and 'DraftIsCreatedByMe'
287
+ const _ensureDraftAdminColumnsForCalculation = columns => {
288
+ columns.forEach((c, i) => {
289
+ if (c.ref && c.ref[0] === 'DraftIsCreatedByMe' && !columns.find(e => e.ref && e.ref[0] === 'CreatedByUser')) {
290
+ columns.push({ ref: ['CreatedByUser'] })
291
+ } else if (
292
+ c.ref &&
293
+ c.ref[0] === 'DraftIsProcessedByMe' &&
294
+ !columns.find(e => e.ref && e.ref[0] === 'InProcessByUser')
295
+ ) {
296
+ columns.push({ ref: ['InProcessByUser'] })
297
+ }
298
+ })
299
+ }
300
+
260
301
  const _draftAdminTable = req => {
261
302
  const { table } = _getTableName(req)
262
303
 
263
304
  let cqn = SELECT.from(table)
264
305
  if (req.query.SELECT.columns) {
265
306
  cqn = cqn.columns(...req.query.SELECT.columns)
307
+ _ensureDraftAdminColumnsForCalculation(cqn.SELECT.columns)
266
308
  }
267
309
 
268
310
  return {
@@ -282,21 +324,24 @@ const _allInactive = (req, columns) => {
282
324
  _getDefaultDraftProperties({ hasDraft: false, isActive: false, withDraftUUID: false })
283
325
  )
284
326
 
285
- const ids = filterKeys(req.target.keys)
286
327
  const isCount = columns.some(element => element.func === 'count')
287
328
 
329
+ // ensure only own drafts are read
288
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))
289
340
 
290
341
  if (isCount) {
291
342
  cqn.columns(...outerMostColumns)
292
343
  } else {
293
344
  cqn.columns(...outerMostColumns.filter(o => o.as !== 'HasActiveEntity'), { ref: ['HasActiveEntity'] })
294
- cqn.leftJoin(ensureNoDraftsSuffix(table.ref[0]) + ' as active').on(`${table.as}.${ids[0]} = active.${ids[0]}`)
295
-
296
- for (let i = 1; i < ids.length; i++) {
297
- // REVISIT: this is extremely expensive as it repeatedly invokes the compiler's cds.parse.expr -> better extend plain CQN yourself here
298
- cqn.and(`${table.as}.${ids[i]} = active.${ids[i]}`)
299
- }
300
345
  }
301
346
 
302
347
  cqn.where(req.query.SELECT.where)
@@ -490,11 +535,11 @@ const _activeWithDraftInProcess = (req, draftWhere, columns, isLocked) => {
490
535
  subSelect
491
536
  )
492
537
 
493
- subSelect.SELECT.where = _getWhereWithAppendedDraftRestrictions(subSelect.SELECT.where, req)
494
-
495
538
  const outerMostColumns = _getOuterMostColumns(columns, draftColumns)
496
539
 
497
- 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])
498
543
 
499
544
  return { cqn: getEnrichedCQN(cqn, req.query.SELECT, draftWhere), scenario: SCENARIO.DRAFT_IN_PROCESS }
500
545
  }
@@ -581,13 +626,21 @@ const _getWhereForActive = where => {
581
626
  return activeWhere
582
627
  }
583
628
 
584
- 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
+ ) => {
585
638
  const parentLinks = parentQuery ? _findJoinInQuery(query, parentQuery.SELECT.from.as) : []
586
639
  const keys = (nav[siblingIndex + 1].where && (params[siblingIndex] || params[0])) || {}
587
640
  const siblingQuery = query.SELECT.where[query.SELECT.where.indexOf('exists') + 1]
588
641
  const onCond = _findJoinInQuery(siblingQuery, target.as)
589
642
  const siblingAlias = siblingQuery.SELECT.from.as
590
- const subScenario = _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, model, onCond)
643
+ const subScenario = _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, model, onCond, req)
591
644
  const isSiblingDraft = subScenario
592
645
  ? subScenario.isSiblingActive || subScenario.scenario === 'ACTIVE' || subScenario.scenario === 'ALL_ACTIVE'
593
646
  : keys.IsActiveEntity && keys.IsActiveEntity !== 'false'
@@ -635,12 +688,17 @@ const _siblingEntity = ({ query, target, nav, params }, columns, model, draftAdm
635
688
  return { cqn, scenario: SCENARIO.SIBLING_ENTITY, isSiblingActive: !isSiblingDraft }
636
689
  }
637
690
 
638
- function _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, model, onCond) {
691
+ function _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, model, onCond, req) {
639
692
  if (nav[siblingIndex + 1].where) return
640
693
  let subScenario
641
694
  const subNav = nav.slice(siblingIndex + 1)
642
695
  const subSiblingIndex = subNav.indexOf('SiblingEntity')
643
- 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
+ }
644
702
  if (subSiblingIndex > -1) {
645
703
  subScenario = _getSiblingScenario(subReq, [{ val: 1 }], model, subSiblingIndex, subNav, params)
646
704
  if (subSiblingIndex > 0) {
@@ -653,8 +711,8 @@ function _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, mo
653
711
  subReq.query = SELECT.from(siblingQuery.SELECT.from).columns([{ val: 1 }])
654
712
  const existsIdx = siblingQuery.SELECT.where.indexOf('exists')
655
713
  if (existsIdx > -1) subReq.query.where(siblingQuery.SELECT.where.slice(existsIdx, existsIdx + 2))
656
- const subReqOrig = { query: { SELECT: { from: { ref: [...subNav].reverse() } } } }
657
- subScenario = _generateCQN(subReqOrig, subReq, [{ val: 1 }], model)
714
+ const subOrigFrom = { ref: [...subNav].reverse() }
715
+ subScenario = _generateCQN(subOrigFrom, subReq, [{ val: 1 }], model)
658
716
  subScenario.cqn.where(onCond)
659
717
  }
660
718
  return subScenario
@@ -671,7 +729,15 @@ const _getSiblingScenario = (req, columns, model, siblingIndex, nav) => {
671
729
  }
672
730
  }
673
731
  const target = { name: query.SELECT.from.ref[0].id || query.SELECT.from.ref[0], as: query.SELECT.from.as }
674
- 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
+ )
675
741
  }
676
742
  return _getSiblingQueryFromWhere(req.query, siblingIndex)
677
743
  }
@@ -714,7 +780,7 @@ const _getDraftDoc = (req, draftName, draftWhere) => {
714
780
 
715
781
  const _getOrderByEnrichedColumns = (orderBy, columns) => {
716
782
  const enrichedCol = []
717
- if (orderBy.length > 1) {
783
+ if (orderBy && orderBy.length > 1) {
718
784
  const colNames = columns.map(el => el.ref[el.ref.length - 1])
719
785
  // REVISIT: GET Books?$select=title&$expand=NotBooks($select=pages)&$orderby=NotBooks/title - what's then?
720
786
  for (const el of orderBy) {
@@ -740,10 +806,13 @@ const _replaceDraftAlias = where => {
740
806
 
741
807
  const _poorMansAlias4 = xpr => '_' + xpr.ref.join('_') + '_'
742
808
 
743
- const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere) => {
809
+ const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere, model) => {
744
810
  const draftActiveWhere = _getWhereForActive(draftWhere)
745
811
  const activeDocs = getEnrichedCQN(SELECT.from(req.target), req.query.SELECT, draftActiveWhere, undefined, false)
812
+ activeDocs.where(_getWhereWithAppendedDraftRestrictions([], req))
813
+ convertWhereExists(activeDocs, model, {})
746
814
 
815
+ // @restrict.where not applicable for drafts (I can ALWAYS read mine)
747
816
  _replaceDraftAlias(draftWhere)
748
817
  const draftDocs = _getDraftDoc(req, draftName, draftWhere)
749
818
 
@@ -829,7 +898,7 @@ const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere) => {
829
898
  .columns(..._filterDraftColumnsBySelected(DRAFT_COLUMNS_CASTED, req.query.SELECT.columns))
830
899
  }
831
900
 
832
- const _excludeActiveDraftExists = (req, draftWhere, columns) => {
901
+ const _excludeActiveDraftExists = (req, draftWhere, columns, model) => {
833
902
  const { table, name } = _getTableName(req, true)
834
903
  const draftName = table.ref[0]
835
904
 
@@ -848,10 +917,8 @@ const _excludeActiveDraftExists = (req, draftWhere, columns) => {
848
917
  subSelect.where([{ ref: [ensureNoDraftsSuffix(req.target.name), key] }, '=', { ref: [draftName, key] }])
849
918
  }
850
919
 
851
- draftWhere = _getWhereWithAppendedDraftRestrictions(draftWhere, req)
852
-
853
920
  draftWhere = removeIsActiveEntityRecursively(draftWhere)
854
- const cqn = _getUnionCQN(req, draftName, columns, subSelect, draftWhere)
921
+ const cqn = _getUnionCQN(req, draftName, columns, subSelect, draftWhere, model)
855
922
  cqn.SELECT.from.as = name
856
923
 
857
924
  if (cqn.SELECT.orderBy) {
@@ -883,13 +950,13 @@ const _validatedActiveWithoutDraft = (req, draftWhere, draftParameters, columns)
883
950
  _isValidActiveWithoutDraft(draftParameters.isActiveEntity, draftParameters.hasDraftEntity) &&
884
951
  _activeWithoutDraft(req, draftWhere, columns)
885
952
 
886
- const _validatedWithSiblingInProcess = (req, draftWhere, draftParameters, columns) => {
953
+ const _validatedWithSiblingInProcess = (req, draftWhere, draftParameters, columns, model) => {
887
954
  const { isActiveEntity, siblingIsActive, draftInProcessByUser } = draftParameters
888
955
  if (
889
956
  !draftInProcessByUser &&
890
957
  _isValidExcludeActiveDraftExists(draftParameters.isActiveEntity, draftParameters.siblingIsActive)
891
958
  )
892
- return _excludeActiveDraftExists(req, draftWhere, columns)
959
+ return _excludeActiveDraftExists(req, draftWhere, columns, model)
893
960
  if (
894
961
  draftInProcessByUser.op === '!=' &&
895
962
  _isValidWithDraftLocked(isActiveEntity, siblingIsActive, draftInProcessByUser)
@@ -908,9 +975,7 @@ const _draftInSubSelect = (where, req) => {
908
975
  if (SELECT && SELECT.where) {
909
976
  const isActiveEntity = readAndDeleteKeywords(['IsActiveEntity'], SELECT.where, false)
910
977
  if (isActiveEntity) {
911
- const isFalse = _isFalse(isActiveEntity.value.val)
912
- if (isFalse) SELECT.where = _getWhereWithAppendedDraftRestrictions(SELECT.where, req)
913
- return isFalse
978
+ return _isFalse(isActiveEntity.value.val)
914
979
  }
915
980
 
916
981
  return _draftInSubSelect(SELECT.where, req)
@@ -923,10 +988,16 @@ const _draftInSubSelect = (where, req) => {
923
988
  const _isDraftAdminScenario = req =>
924
989
  req.target.query && req.target.query._target && req.target.query._target.name === 'DRAFT.DraftAdministrativeData'
925
990
 
926
- // eslint-disable-next-line complexity
927
- const _generateCQN = (reqOriginal, req, columns, model) => {
928
- const nav = [...reqOriginal.query.SELECT.from.ref].reverse() || []
929
- const siblingIndex = nav.indexOf('SiblingEntity')
991
+ const _generateCQN = (originalFrom, req, columns, model) => {
992
+ const nav = [...originalFrom.ref].reverse() || []
993
+ let siblingIndex = nav.indexOf('SiblingEntity')
994
+
995
+ // it can also be a property access (new parser), then we must shift it
996
+ if (siblingIndex === 1 && req.target.elements[nav[0]]) {
997
+ nav.shift()
998
+ siblingIndex = siblingIndex - 1
999
+ }
1000
+
930
1001
  let siblingScenario
931
1002
  if (siblingIndex > -1) {
932
1003
  siblingScenario = _getSiblingScenario(req, columns, model, siblingIndex, nav)
@@ -958,8 +1029,6 @@ const _generateCQN = (reqOriginal, req, columns, model) => {
958
1029
  }
959
1030
 
960
1031
  if (!draftParameters.isActiveEntity) {
961
- // _draftInSubSelect adds draft restrictions in case check is truthy
962
- // -> not nice but works for now and we don't need to go in recursively again
963
1032
  if (_draftInSubSelect(req.query.SELECT.where, req) || (siblingScenario && !siblingScenario.isSiblingActive)) {
964
1033
  // this is only the case when navigating into tree
965
1034
  return _allInactive(req, columns)
@@ -972,23 +1041,20 @@ const _generateCQN = (reqOriginal, req, columns, model) => {
972
1041
  }
973
1042
 
974
1043
  if (draftParameters.siblingIsActive) {
975
- return _validatedWithSiblingInProcess(req, req.query.SELECT.where, draftParameters, columns)
1044
+ return _validatedWithSiblingInProcess(req, req.query.SELECT.where, draftParameters, columns, model)
976
1045
  }
977
1046
 
978
1047
  return _validatedDraftOfWhichIAmOwner(req, req.query.SELECT.where, draftParameters, columns)
979
1048
  }
980
1049
 
981
- const _getColumns = ({ query: { SELECT } }, model) => {
1050
+ const _getColumns = ({ query: { SELECT }, target }, model) => {
982
1051
  return SELECT.columns
983
1052
  ? SELECT.columns.filter(
984
1053
  col =>
985
1054
  (col.ref && !DRAFT_COLUMNS.includes(col.ref[col.ref.length - 1])) ||
986
1055
  (!col.ref && !DRAFT_COLUMNS.includes(col))
987
1056
  )
988
- : getColumns(model.definitions[ensureNoDraftsSuffix(SELECT.from.ref[0])], {
989
- onlyNames: true,
990
- removeIgnore: true
991
- })
1057
+ : getColumns(target, { onlyNames: true, removeIgnore: true })
992
1058
  }
993
1059
 
994
1060
  const _isIsActiveEntity = element => element.ref && element.ref[element.ref.length - 1] === 'IsActiveEntity'
@@ -1018,6 +1084,10 @@ const _adaptSubSelects = ({ SELECT: { from, where } }, scenario) => {
1018
1084
  }
1019
1085
  } else if (element.SELECT) {
1020
1086
  _adaptSubSelects(element, scenario)
1087
+ } else if (element.xpr) {
1088
+ for (const ele of element.xpr.filter(e => e.SELECT)) {
1089
+ _adaptSubSelects(ele, scenario)
1090
+ }
1021
1091
  }
1022
1092
  }
1023
1093
  }
@@ -1119,12 +1189,26 @@ const _getLocalizedEntity = (model, target, user) => {
1119
1189
  return localizedEntity || model.definitions[`${prefix}.${target.name}`]
1120
1190
  }
1121
1191
 
1192
+ const _getLastSubQuery = query => (query.SELECT.from.SELECT ? _getLastSubQuery(query.SELECT.from) : query)
1193
+ const _setLastSubQuery = (query, last, prev = query) => {
1194
+ if (query.SELECT.from.SELECT) return _setLastSubQuery(query.SELECT.from, last, query)
1195
+ else prev.SELECT.from = _copyCQNPartial(last)
1196
+ return prev
1197
+ }
1198
+
1199
+ const _adaptDraftAdminExpand = cqn => {
1200
+ const draftAdminExpand =
1201
+ cqn.SELECT.columns && cqn.SELECT.columns.find(c => c.expand && c.ref[0] === 'DraftAdministrativeData')
1202
+ if (draftAdminExpand) {
1203
+ _ensureDraftAdminColumnsForCalculation(draftAdminExpand.expand)
1204
+ }
1205
+ }
1206
+
1122
1207
  /**
1123
1208
  * Generic Handler for READ requests in the context of draft.
1124
1209
  *
1125
1210
  * @param req
1126
1211
  */
1127
- // eslint-disable-next-line complexity
1128
1212
  const _handler = async function (req) {
1129
1213
  // handle localized here as it was previously handled for req.target
1130
1214
  req.target = _getLocalizedEntity(this.model, req.target, req.user) || req.target
@@ -1132,6 +1216,8 @@ const _handler = async function (req) {
1132
1216
  // REVISIT
1133
1217
  delete req.query._validationQuery
1134
1218
 
1219
+ const originalFrom = _copyCQNPartial(req.query.SELECT.from)
1220
+
1135
1221
  // REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called here
1136
1222
  const sqlQuery = cqn2cqn4sql(req.query, this.model, { draft: true })
1137
1223
 
@@ -1146,14 +1232,26 @@ const _handler = async function (req) {
1146
1232
  reqClone.query._streaming = true
1147
1233
  return cds.tx(req).run(reqClone.query)
1148
1234
  }
1235
+ let cqnScenario
1149
1236
 
1150
- const cqnScenario = _generateCQN(req, reqClone, _getColumns(reqClone, this.model), this.model)
1237
+ // to replace scenario CQNs for queries with $apply SELECT chain (new odata2cqn parser)
1238
+ // just to make existing tests working with new parser. not really tested, not needed to be supported
1239
+ if (reqClone.query.SELECT.from.SELECT) {
1240
+ const subQueryReq = { __proto__: req, query: _copyCQNPartial(_getLastSubQuery(reqClone.query)) }
1241
+ cqnScenario = _generateCQN(originalFrom.SELECT.from, subQueryReq, _getColumns(subQueryReq, this.model), this.model)
1242
+ cqnScenario.cqn = _setLastSubQuery(reqClone.query, cqnScenario.cqn)
1243
+ } else {
1244
+ cqnScenario = _generateCQN(originalFrom, reqClone, _getColumns(reqClone, this.model), this.model)
1245
+ }
1151
1246
 
1152
1247
  if (!cqnScenario) {
1153
1248
  req.reject(400)
1154
1249
  return
1155
1250
  }
1156
1251
 
1252
+ // ensure base columns for calculation are selected in draft admin expand
1253
+ _adaptDraftAdminExpand(cqnScenario.cqn)
1254
+
1157
1255
  if (cqnScenario.scenario === SCENARIO.ALL_ACTIVE && cqnScenario.cqn.SELECT.where) {
1158
1256
  cqnScenario.cqn.SELECT.where = removeIsActiveEntityRecursively(cqnScenario.cqn.SELECT.where)
1159
1257
  }
@@ -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