@sap/cds 5.6.4 → 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 (176) hide show
  1. package/CHANGELOG.md +102 -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 +3 -1
  38. package/lib/log/index.js +2 -2
  39. package/lib/req/cds-context.js +79 -0
  40. package/lib/req/context.js +5 -77
  41. package/lib/req/request.js +1 -1
  42. package/lib/serve/Service-api.js +8 -4
  43. package/lib/serve/Service-dispatch.js +0 -7
  44. package/lib/serve/Service-methods.js +6 -8
  45. package/lib/serve/Transaction.js +35 -30
  46. package/lib/serve/adapters.js +1 -4
  47. package/lib/utils/axios.js +1 -1
  48. package/libx/_runtime/audit/Service.js +44 -20
  49. package/libx/_runtime/audit/generic/personal/access.js +16 -11
  50. package/libx/_runtime/audit/generic/personal/modification.js +5 -5
  51. package/libx/_runtime/audit/generic/personal/utils.js +46 -37
  52. package/libx/_runtime/{common/auth → auth}/index.js +21 -7
  53. package/libx/_runtime/{common/auth → auth}/strategies/JWT.js +2 -2
  54. package/libx/_runtime/{common/auth → auth}/strategies/basic.js +2 -2
  55. package/libx/_runtime/{common/auth → auth}/strategies/dummy.js +1 -1
  56. package/libx/_runtime/{common/auth → auth}/strategies/mock.js +2 -2
  57. package/libx/_runtime/{common/auth → auth}/strategies/utils/uaa.js +1 -1
  58. package/libx/_runtime/{common/auth → auth}/strategies/utils/xssec.js +0 -0
  59. package/libx/_runtime/{common/auth → auth}/strategies/xsuaa.js +2 -2
  60. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +7 -2
  61. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +0 -7
  62. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +0 -8
  63. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +3 -4
  64. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +6 -7
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +3 -4
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -11
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +16 -6
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -65
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +0 -7
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +3 -66
  71. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +24 -0
  72. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +2 -2
  73. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +13 -10
  74. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -7
  75. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -8
  76. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +54 -76
  77. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
  78. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
  79. package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
  80. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
  81. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
  82. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
  83. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
  84. package/libx/_runtime/cds-services/services/Service.js +0 -6
  85. package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
  86. package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -6
  87. package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
  88. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
  89. package/libx/_runtime/cds-services/util/assert.js +1 -262
  90. package/libx/_runtime/cds.js +6 -9
  91. package/libx/_runtime/common/aspects/entity.js +1 -1
  92. package/libx/_runtime/common/composition/delete.js +4 -2
  93. package/libx/_runtime/common/composition/update.js +22 -38
  94. package/libx/_runtime/common/composition/utils.js +3 -7
  95. package/libx/_runtime/common/error/standardError.js +11 -0
  96. package/libx/_runtime/common/generic/auth.js +61 -30
  97. package/libx/_runtime/common/generic/crud.js +11 -23
  98. package/libx/_runtime/common/generic/input.js +20 -0
  99. package/libx/_runtime/common/generic/put.js +4 -10
  100. package/libx/_runtime/common/generic/sorting.js +12 -30
  101. package/libx/_runtime/common/perf/index.js +24 -0
  102. package/libx/_runtime/common/utils/cqn.js +58 -1
  103. package/libx/_runtime/common/utils/cqn2cqn4sql.js +289 -114
  104. package/libx/_runtime/common/utils/csn.js +38 -56
  105. package/libx/_runtime/common/utils/entityFromCqn.js +6 -6
  106. package/libx/_runtime/common/utils/resolveView.js +4 -5
  107. package/libx/_runtime/common/utils/rewriteAsterisks.js +46 -5
  108. package/libx/_runtime/common/utils/search2cqn4sql.js +21 -9
  109. package/libx/_runtime/common/utils/structured.js +35 -25
  110. package/libx/_runtime/db/Service.js +0 -6
  111. package/libx/_runtime/db/expand/expand-v2.js +130 -0
  112. package/libx/_runtime/db/expand/expandCQNToJoin.js +38 -52
  113. package/libx/_runtime/db/expand/index.js +3 -1
  114. package/libx/_runtime/db/generic/input.js +52 -10
  115. package/libx/_runtime/db/generic/integrity.js +367 -26
  116. package/libx/_runtime/db/generic/virtual.js +51 -13
  117. package/libx/_runtime/db/query/update.js +9 -3
  118. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +8 -9
  119. package/libx/_runtime/{common → db}/utils/propagateForeignKeys.js +11 -14
  120. package/libx/_runtime/fiori/generic/activate.js +1 -0
  121. package/libx/_runtime/fiori/generic/before.js +2 -1
  122. package/libx/_runtime/fiori/generic/edit.js +1 -0
  123. package/libx/_runtime/fiori/generic/patch.js +1 -1
  124. package/libx/_runtime/fiori/generic/read.js +123 -57
  125. package/libx/_runtime/fiori/uiflex/index.js +1 -1
  126. package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +3 -3
  127. package/libx/_runtime/fiori/utils/delete.js +7 -1
  128. package/libx/_runtime/hana/Service.js +1 -8
  129. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
  130. package/libx/_runtime/hana/execute.js +10 -4
  131. package/libx/_runtime/hana/pool.js +55 -45
  132. package/libx/_runtime/hana/search.js +7 -6
  133. package/libx/_runtime/hana/search2cqn4sql.js +8 -5
  134. package/libx/_runtime/hana/searchToContains.js +3 -1
  135. package/libx/_runtime/index.js +5 -5
  136. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
  137. package/libx/_runtime/messaging/Outbox.js +53 -0
  138. package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
  139. package/libx/_runtime/messaging/common-utils/connections.js +14 -9
  140. package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
  141. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
  142. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
  143. package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
  144. package/libx/_runtime/messaging/file-based.js +5 -5
  145. package/libx/_runtime/messaging/message-queuing.js +2 -3
  146. package/libx/_runtime/messaging/outbox/OutboxRunner.js +75 -0
  147. package/libx/_runtime/messaging/outbox/utils.js +192 -0
  148. package/libx/_runtime/messaging/service.js +16 -30
  149. package/libx/_runtime/remote/Service.js +15 -0
  150. package/libx/_runtime/remote/utils/client.js +15 -3
  151. package/libx/_runtime/remote/utils/{dataConversion.js → data.js} +12 -2
  152. package/libx/_runtime/sqlite/Service.js +7 -10
  153. package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +19 -0
  154. package/libx/_runtime/sqlite/execute.js +18 -12
  155. package/libx/_runtime/types/api.js +2 -1
  156. package/libx/odata/{odata2cqn/afterburner.js → afterburner.js} +19 -15
  157. package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
  158. package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +171 -130
  159. package/libx/odata/index.js +16 -14
  160. package/libx/odata/parser.js +1 -0
  161. package/libx/odata/utils.js +57 -0
  162. package/libx/rest/RestAdapter.js +2 -6
  163. package/libx/rest/utils/data.js +1 -6
  164. package/package.json +4 -3
  165. package/server.js +4 -5
  166. package/srv/audit-log.cds +87 -0
  167. package/{libx/_runtime/fiori/uiflex/extensibility/index.cds → srv/flex.cds} +0 -0
  168. package/srv/flex.js +1 -0
  169. package/srv/outbox.cds +11 -0
  170. package/srv/outbox.js +0 -0
  171. package/libx/_runtime/cds-services/adapter/perf/performance.js +0 -104
  172. package/libx/_runtime/cds-services/adapter/perf/performanceMeasurement.js +0 -33
  173. package/libx/odata/odata2cqn/index.js +0 -3
  174. package/libx/odata/odata2cqn/parser.js +0 -1
  175. package/libx/odata/readme.md +0 -1
  176. 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
@@ -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,10 +806,13 @@ 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
 
@@ -846,7 +898,7 @@ const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere) => {
846
898
  .columns(..._filterDraftColumnsBySelected(DRAFT_COLUMNS_CASTED, req.query.SELECT.columns))
847
899
  }
848
900
 
849
- const _excludeActiveDraftExists = (req, draftWhere, columns) => {
901
+ const _excludeActiveDraftExists = (req, draftWhere, columns, model) => {
850
902
  const { table, name } = _getTableName(req, true)
851
903
  const draftName = table.ref[0]
852
904
 
@@ -865,10 +917,8 @@ const _excludeActiveDraftExists = (req, draftWhere, columns) => {
865
917
  subSelect.where([{ ref: [ensureNoDraftsSuffix(req.target.name), key] }, '=', { ref: [draftName, key] }])
866
918
  }
867
919
 
868
- draftWhere = _getWhereWithAppendedDraftRestrictions(draftWhere, req)
869
-
870
920
  draftWhere = removeIsActiveEntityRecursively(draftWhere)
871
- const cqn = _getUnionCQN(req, draftName, columns, subSelect, draftWhere)
921
+ const cqn = _getUnionCQN(req, draftName, columns, subSelect, draftWhere, model)
872
922
  cqn.SELECT.from.as = name
873
923
 
874
924
  if (cqn.SELECT.orderBy) {
@@ -900,13 +950,13 @@ const _validatedActiveWithoutDraft = (req, draftWhere, draftParameters, columns)
900
950
  _isValidActiveWithoutDraft(draftParameters.isActiveEntity, draftParameters.hasDraftEntity) &&
901
951
  _activeWithoutDraft(req, draftWhere, columns)
902
952
 
903
- const _validatedWithSiblingInProcess = (req, draftWhere, draftParameters, columns) => {
953
+ const _validatedWithSiblingInProcess = (req, draftWhere, draftParameters, columns, model) => {
904
954
  const { isActiveEntity, siblingIsActive, draftInProcessByUser } = draftParameters
905
955
  if (
906
956
  !draftInProcessByUser &&
907
957
  _isValidExcludeActiveDraftExists(draftParameters.isActiveEntity, draftParameters.siblingIsActive)
908
958
  )
909
- return _excludeActiveDraftExists(req, draftWhere, columns)
959
+ return _excludeActiveDraftExists(req, draftWhere, columns, model)
910
960
  if (
911
961
  draftInProcessByUser.op === '!=' &&
912
962
  _isValidWithDraftLocked(isActiveEntity, siblingIsActive, draftInProcessByUser)
@@ -925,9 +975,7 @@ const _draftInSubSelect = (where, req) => {
925
975
  if (SELECT && SELECT.where) {
926
976
  const isActiveEntity = readAndDeleteKeywords(['IsActiveEntity'], SELECT.where, false)
927
977
  if (isActiveEntity) {
928
- const isFalse = _isFalse(isActiveEntity.value.val)
929
- if (isFalse) SELECT.where = _getWhereWithAppendedDraftRestrictions(SELECT.where, req)
930
- return isFalse
978
+ return _isFalse(isActiveEntity.value.val)
931
979
  }
932
980
 
933
981
  return _draftInSubSelect(SELECT.where, req)
@@ -940,10 +988,16 @@ const _draftInSubSelect = (where, req) => {
940
988
  const _isDraftAdminScenario = req =>
941
989
  req.target.query && req.target.query._target && req.target.query._target.name === 'DRAFT.DraftAdministrativeData'
942
990
 
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')
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
+
947
1001
  let siblingScenario
948
1002
  if (siblingIndex > -1) {
949
1003
  siblingScenario = _getSiblingScenario(req, columns, model, siblingIndex, nav)
@@ -975,8 +1029,6 @@ const _generateCQN = (reqOriginal, req, columns, model) => {
975
1029
  }
976
1030
 
977
1031
  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
1032
  if (_draftInSubSelect(req.query.SELECT.where, req) || (siblingScenario && !siblingScenario.isSiblingActive)) {
981
1033
  // this is only the case when navigating into tree
982
1034
  return _allInactive(req, columns)
@@ -989,23 +1041,20 @@ const _generateCQN = (reqOriginal, req, columns, model) => {
989
1041
  }
990
1042
 
991
1043
  if (draftParameters.siblingIsActive) {
992
- return _validatedWithSiblingInProcess(req, req.query.SELECT.where, draftParameters, columns)
1044
+ return _validatedWithSiblingInProcess(req, req.query.SELECT.where, draftParameters, columns, model)
993
1045
  }
994
1046
 
995
1047
  return _validatedDraftOfWhichIAmOwner(req, req.query.SELECT.where, draftParameters, columns)
996
1048
  }
997
1049
 
998
- const _getColumns = ({ query: { SELECT } }, model) => {
1050
+ const _getColumns = ({ query: { SELECT }, target }, model) => {
999
1051
  return SELECT.columns
1000
1052
  ? SELECT.columns.filter(
1001
1053
  col =>
1002
1054
  (col.ref && !DRAFT_COLUMNS.includes(col.ref[col.ref.length - 1])) ||
1003
1055
  (!col.ref && !DRAFT_COLUMNS.includes(col))
1004
1056
  )
1005
- : getColumns(model.definitions[ensureNoDraftsSuffix(SELECT.from.ref[0])], {
1006
- onlyNames: true,
1007
- removeIgnore: true
1008
- })
1057
+ : getColumns(target, { onlyNames: true, removeIgnore: true })
1009
1058
  }
1010
1059
 
1011
1060
  const _isIsActiveEntity = element => element.ref && element.ref[element.ref.length - 1] === 'IsActiveEntity'
@@ -1136,6 +1185,13 @@ const _getLocalizedEntity = (model, target, user) => {
1136
1185
  return localizedEntity || model.definitions[`${prefix}.${target.name}`]
1137
1186
  }
1138
1187
 
1188
+ const _getLastSubQuery = query => (query.SELECT.from.SELECT ? _getLastSubQuery(query.SELECT.from) : query)
1189
+ const _setLastSubQuery = (query, last, prev = query) => {
1190
+ if (query.SELECT.from.SELECT) return _setLastSubQuery(query.SELECT.from, last, query)
1191
+ else prev.SELECT.from = _copyCQNPartial(last)
1192
+ return prev
1193
+ }
1194
+
1139
1195
  const _adaptDraftAdminExpand = cqn => {
1140
1196
  const draftAdminExpand =
1141
1197
  cqn.SELECT.columns && cqn.SELECT.columns.find(c => c.expand && c.ref[0] === 'DraftAdministrativeData')
@@ -1149,7 +1205,6 @@ const _adaptDraftAdminExpand = cqn => {
1149
1205
  *
1150
1206
  * @param req
1151
1207
  */
1152
- // eslint-disable-next-line complexity
1153
1208
  const _handler = async function (req) {
1154
1209
  // handle localized here as it was previously handled for req.target
1155
1210
  req.target = _getLocalizedEntity(this.model, req.target, req.user) || req.target
@@ -1157,6 +1212,8 @@ const _handler = async function (req) {
1157
1212
  // REVISIT
1158
1213
  delete req.query._validationQuery
1159
1214
 
1215
+ const originalFrom = _copyCQNPartial(req.query.SELECT.from)
1216
+
1160
1217
  // REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called here
1161
1218
  const sqlQuery = cqn2cqn4sql(req.query, this.model, { draft: true })
1162
1219
 
@@ -1171,8 +1228,17 @@ const _handler = async function (req) {
1171
1228
  reqClone.query._streaming = true
1172
1229
  return cds.tx(req).run(reqClone.query)
1173
1230
  }
1231
+ let cqnScenario
1174
1232
 
1175
- const cqnScenario = _generateCQN(req, reqClone, _getColumns(reqClone, this.model), this.model)
1233
+ // to replace scenario CQNs for queries with $apply SELECT chain (new odata2cqn parser)
1234
+ // just to make existing tests working with new parser. not really tested, not needed to be supported
1235
+ if (reqClone.query.SELECT.from.SELECT) {
1236
+ const subQueryReq = { __proto__: req, query: _copyCQNPartial(_getLastSubQuery(reqClone.query)) }
1237
+ cqnScenario = _generateCQN(originalFrom.SELECT.from, subQueryReq, _getColumns(subQueryReq, this.model), this.model)
1238
+ cqnScenario.cqn = _setLastSubQuery(reqClone.query, cqnScenario.cqn)
1239
+ } else {
1240
+ cqnScenario = _generateCQN(originalFrom, reqClone, _getColumns(reqClone, this.model), this.model)
1241
+ }
1176
1242
 
1177
1243
  if (!cqnScenario) {
1178
1244
  req.reject(400)
@@ -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
@@ -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