@sap/cds 5.9.1 → 5.9.4

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 (45) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/lib/compile/etc/_localized.js +3 -2
  3. package/lib/compile/for/drafts.js +1 -1
  4. package/lib/connect/bindings.js +1 -1
  5. package/lib/connect/index.js +2 -3
  6. package/lib/env/requires.js +1 -1
  7. package/lib/index.js +2 -1
  8. package/lib/serve/Service-methods.js +28 -1
  9. package/lib/serve/adapters.js +6 -6
  10. package/lib/serve/factory.js +14 -9
  11. package/lib/serve/index.js +4 -3
  12. package/libx/_runtime/auth/index.js +16 -1
  13. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -1
  14. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/BatchRequestListBuilder.js +3 -1
  15. package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +11 -6
  16. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -3
  17. package/libx/_runtime/cds-services/services/Service.js +1 -1
  18. package/libx/_runtime/common/aspects/utils.js +8 -2
  19. package/libx/_runtime/common/composition/data.js +22 -13
  20. package/libx/_runtime/common/composition/delete.js +14 -12
  21. package/libx/_runtime/common/generic/auth/expand.js +1 -0
  22. package/libx/_runtime/common/generic/auth/restrict.js +3 -1
  23. package/libx/_runtime/{cds-services/services/utils → common/generic/auth}/restrictions.js +8 -1
  24. package/libx/_runtime/common/generic/input.js +1 -0
  25. package/libx/_runtime/common/generic/put.js +1 -0
  26. package/libx/_runtime/common/utils/cqn.js +5 -10
  27. package/libx/_runtime/common/utils/cqn2cqn4sql.js +37 -63
  28. package/libx/_runtime/common/utils/foreignKeyPropagations.js +28 -8
  29. package/libx/_runtime/common/utils/path.js +3 -3
  30. package/libx/_runtime/common/utils/require.js +2 -1
  31. package/libx/_runtime/common/utils/resolveView.js +3 -0
  32. package/libx/_runtime/common/utils/structured.js +6 -1
  33. package/libx/_runtime/db/Service.js +10 -0
  34. package/libx/_runtime/db/expand/expand-v2.js +13 -5
  35. package/libx/_runtime/db/expand/expandCQNToJoin.js +56 -26
  36. package/libx/_runtime/db/utils/generateAliases.js +9 -0
  37. package/libx/_runtime/extensibility/uiflex/handler/transformWRITE.js +1 -0
  38. package/libx/_runtime/fiori/generic/read.js +83 -31
  39. package/libx/_runtime/fiori/generic/readOverDraft.js +31 -19
  40. package/libx/_runtime/fiori/utils/handler.js +3 -0
  41. package/libx/_runtime/fiori/utils/where.js +38 -25
  42. package/libx/_runtime/hana/execute.js +18 -1
  43. package/libx/_runtime/hana/search2cqn4sql.js +4 -1
  44. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +19 -12
  45. package/package.json +1 -1
@@ -12,7 +12,6 @@ const { getEntityNameFromCQN } = require('./entityFromCqn')
12
12
  const getError = require('../../common/error')
13
13
  const { rewriteAsterisks } = require('./rewriteAsterisks')
14
14
  const { getPathFromRef, getEntityFromPath } = require('../../common/utils/path')
15
- const { addToWhere } = require('../../common/utils/cqn')
16
15
  const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
17
16
  const { addRefToWhereIfNecessary } = require('../../../odata/afterburner')
18
17
  const { addAliasToExpression, PARENT_ALIAS, FOREIGN_ALIAS } = require('../../db/utils/generateAliases')
@@ -240,11 +239,14 @@ const _convertCountNavigation = (SELECT, target) => {
240
239
  }
241
240
 
242
241
  const _addTableName = (where, tableName) => {
243
- return where.map(ref => {
244
- if (ref.ref) {
245
- ref.ref.unshift(tableName)
242
+ return where.map(whereEl => {
243
+ if (whereEl.xpr) {
244
+ return { xpr: _addTableName(whereEl.xpr, tableName) }
246
245
  }
247
- return ref
246
+ if (whereEl.ref) {
247
+ whereEl.ref.unshift(tableName)
248
+ }
249
+ return whereEl
248
250
  })
249
251
  }
250
252
 
@@ -444,10 +446,7 @@ const convertWhereExists = (query, model, options, currentTarget) => {
444
446
  outerAlias = as || PARENT_ALIAS + lambdaIteration
445
447
  innerAlias = FOREIGN_ALIAS + lambdaIteration
446
448
  } else {
447
- const element = currentTarget.elements[ref]
448
- if (element && element.isAssociation) {
449
- queryTarget = element._target
450
- }
449
+ queryTarget = getEntityFromPath({ ref }, currentTarget)
451
450
  }
452
451
 
453
452
  if (where) {
@@ -501,8 +500,9 @@ const _convertNotEqual = (container, partName = 'where') => {
501
500
  }
502
501
  }
503
502
 
504
- if (el && el.SELECT) {
505
- _convertNotEqual(el.SELECT, partName)
503
+ if (el) {
504
+ if (el.SELECT) _convertNotEqual(el.SELECT, partName)
505
+ if (el.xpr) _convertNotEqual(el, 'xpr')
506
506
  }
507
507
  })
508
508
 
@@ -561,6 +561,10 @@ const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, target, model, alias, proc
561
561
  const queryTarget = model.definitions[ensureNoDraftsSuffix(target)]
562
562
 
563
563
  orderByOrWhereCQN.forEach((el, index) => {
564
+ if (el.xpr) {
565
+ _convertOrderByOrWhereCQN(el.xpr, target, model, alias, processFn)
566
+ return
567
+ }
564
568
  if (el.ref && _skip(queryTarget, el.ref, alias, model)) processFn(orderByOrWhereCQN, index)
565
569
  })
566
570
  }
@@ -602,86 +606,56 @@ const _convertRefWhereInExpand = columns => {
602
606
  }
603
607
  }
604
608
 
605
- const _flattenCQN = cqn => {
606
- if (Array.isArray(cqn)) cqn.forEach(_flattenCQN)
607
- else if (cqn) {
608
- if (cqn.SELECT) _flattenCQN(cqn.SELECT)
609
- if (cqn.from) _flattenCQN(cqn.from)
610
- if (cqn.ref) _flattenCQN(cqn.ref)
611
- if (cqn.SET) _flattenCQN(cqn.SET)
612
- if (cqn.args) _flattenCQN(cqn.args)
613
- if (cqn.columns) _flattenCQN(cqn.columns)
614
- if (cqn.expand) _flattenCQN(cqn.expand)
615
- if (cqn.where) _flattenXpr(cqn.where)
616
- if (cqn.having) _flattenXpr(cqn.having)
617
- }
618
- }
619
-
620
- const _flattenXpr = cqn => {
621
- if (!Array.isArray(cqn)) {
622
- if (cqn.xpr) cqn = cqn.xpr
623
- return
624
- }
625
-
626
- let idx = cqn.findIndex(el => el.xpr)
627
- while (idx > -1) {
628
- cqn.splice(idx, 1, '(', ...cqn[idx].xpr, ')')
629
- idx = cqn.findIndex(el => el.xpr)
630
- }
631
-
632
- cqn.forEach(_flattenCQN)
633
- }
634
-
635
- const _convertPathExpression = (SELECT, model, options = {}) => {
636
- const prevAlias = SELECT.from.as
637
- for (const whereEl of SELECT.where || []) {
638
- if (typeof whereEl === 'object' && whereEl.SELECT) _convertPathExpression(whereEl.SELECT, model)
609
+ const _convertPathExpression = (query, model, options = {}) => {
610
+ const prevAlias = query.SELECT.from.as
611
+ for (const whereEl of query.SELECT.where || []) {
612
+ if (typeof whereEl === 'object' && whereEl.SELECT) _convertPathExpression(whereEl, model)
639
613
  }
640
- const conversion = convertPathExpressionToWhere(SELECT.from, model, options)
614
+ const conversion = convertPathExpressionToWhere(query.SELECT.from, model, options)
641
615
  if (!conversion) return
642
616
  const { target, alias, where, cardinality, columns, args } = conversion
643
617
 
644
618
  // REVISIT: It's not possible to just change the reference (i.e. SELECT.from.ref = [target])
645
619
  // as many parts of our code base still refer to SELECT.from (e.g. authorization)
646
620
  if (args) {
647
- SELECT.from = { ref: [{ id: target, args }] }
621
+ query.SELECT.from = { ref: [{ id: target, args }] }
648
622
  } else {
649
- SELECT.from = { ref: [target] }
623
+ query.SELECT.from = { ref: [target] }
650
624
  }
651
625
  if (alias) {
652
- SELECT.from.as = alias
653
- if (SELECT.where && alias !== prevAlias) {
654
- SELECT.where = addAliasToExpression(SELECT.where, alias)
626
+ query.SELECT.from.as = alias
627
+ if (query.SELECT.where && alias !== prevAlias) {
628
+ query.SELECT.where = addAliasToExpression(query.SELECT.where, alias)
655
629
  }
656
630
  }
657
631
  if (columns) {
658
632
  // TODO: use streaming as outer property
659
- if (options.isStreaming) SELECT.columns = columns
633
+ if (options.isStreaming) query.SELECT.columns = columns
660
634
  else {
661
- if (!SELECT.columns) {
635
+ if (!query.SELECT.columns) {
662
636
  // Okra always wants to have the key values, remove once we relax this requirement
663
637
  if (model.definitions[target] && model.definitions[target].keys) {
664
- SELECT.columns = Object.keys(model.definitions[target].keys)
638
+ query.SELECT.columns = Object.keys(model.definitions[target].keys)
665
639
  .filter(
666
640
  k =>
667
641
  !model.definitions[target].keys[k].isAssociation &&
668
642
  !columns.find(element => element.ref && element.ref[element.ref.length - 1] === k)
669
643
  )
670
644
  .map(k => ({ ref: [k] }))
671
- } else SELECT.columns = []
645
+ } else query.SELECT.columns = []
672
646
  }
673
- SELECT.columns.push(...columns)
647
+ query.SELECT.columns.push(...columns)
674
648
  }
675
649
  }
676
650
  if (cardinality && cardinality.max === 1) {
677
- SELECT.one = true
651
+ query.SELECT.one = true
678
652
  }
679
653
  // TODO: REVISIT: We need to add alias to subselect in .where, .columns, .from, ... etc
680
654
  if (where) {
681
655
  if (options._4fiori) {
682
- addToWhere({ SELECT }, where)
656
+ query.where(where)
683
657
  } else {
684
- addToWhere({ SELECT }, removeIsActiveEntityRecursively(where))
658
+ query.where(removeIsActiveEntityRecursively(where))
685
659
  }
686
660
  }
687
661
  }
@@ -692,6 +666,9 @@ const _convertToOneEqNullInFilter = (query, target) => {
692
666
 
693
667
  for (let i = 0; i < query.where.length; i++) {
694
668
  const w = query.where[i]
669
+ if (w.xpr) {
670
+ _convertToOneEqNullInFilter({ where: w.xpr }, target)
671
+ }
695
672
  const w2 = query.where[i + 2]
696
673
  if (!w2 || !w.ref || w2.val !== null) {
697
674
  continue
@@ -730,9 +707,6 @@ const _convertSelect = (query, model, _options) => {
730
707
  query.SELECT.from = _convertSelect(query.SELECT.from, model, options)
731
708
  }
732
709
 
733
- // REVISIT: a temporary workaround for xpr from new parser
734
- if (cds.env.features.odata_new_parser) _flattenCQN(query)
735
-
736
710
  // lambda functions
737
711
  convertWhereExists(query.SELECT, model, options)
738
712
 
@@ -742,7 +716,7 @@ const _convertSelect = (query, model, _options) => {
742
716
  _convertNotEqual(query.SELECT, 'having')
743
717
  }
744
718
 
745
- _convertPathExpression(query.SELECT, model, options)
719
+ _convertPathExpression(query, model, options)
746
720
  rewriteAsterisks(query, model, options)
747
721
  if (query.SELECT.where) {
748
722
  const entityName =
@@ -1,24 +1,44 @@
1
- const _getSubOns = element => {
2
- // this only works for on conds with `and`, once we support `or` this needs to be adjusted
3
- const newOn = element.on ? element.on.filter(e => e !== '(' && e !== ')') : []
4
- const subOns = []
1
+ const _normalizedRef = o => (o && o.ref && o.ref.length > 1 && o.ref[0] === '$self' ? { ref: o.ref.slice(1) } : o)
2
+
3
+ const _sub = (newOn, subOns = []) => {
5
4
  let currArr = []
6
5
 
7
- for (const onEl of newOn) {
8
- if (currArr.length === 0) subOns.push(currArr)
6
+ for (let i = 0; i < newOn.length; i++) {
7
+ const onEl = newOn[i]
8
+
9
+ if (onEl === 'or') {
10
+ // abort condition for or
11
+ subOns.push([])
12
+ return subOns
13
+ }
14
+ if (onEl.xpr) {
15
+ _sub(onEl.xpr, subOns)
16
+ // after xpr there usually should be and/or
17
+ i++
18
+ continue
19
+ }
20
+ if (currArr.length === 0) {
21
+ subOns.push(currArr)
22
+ }
9
23
  if (onEl !== 'and') currArr.push(onEl)
10
24
  else {
11
25
  currArr = []
12
26
  }
13
27
  }
14
28
 
29
+ return subOns
30
+ }
31
+
32
+ const _getSubOns = element => {
33
+ const newOn = element.on || []
34
+ const subOns = _sub(newOn)
35
+
15
36
  for (const subOn of subOns) {
16
37
  // We don't support anything else than
17
38
  // A = B AND C = D AND ...
18
39
  if (subOn.length !== 3) return []
19
40
  }
20
-
21
- return subOns
41
+ return subOns.map(subOn => subOn.map(ref => _normalizedRef(ref)))
22
42
  }
23
43
 
24
44
  const _parentFieldsFromSimpleOnCond = (element, subOn) => {
@@ -17,14 +17,14 @@ const getPathFromRef = ref => {
17
17
  /*
18
18
  * returns the target entity for the given path
19
19
  */
20
- const getEntityFromPath = (path, model) => {
21
- let current = { elements: model.definitions }
20
+ const getEntityFromPath = (path, def) => {
21
+ let current = def.definitions ? { elements: def.definitions } : def
22
22
  path = typeof path === 'string' ? cds.parse.path(path) : path
23
23
  const segments = [...path.ref]
24
24
  while (segments.length) {
25
25
  const segment = segments.shift()
26
26
  current = current.elements[segment.id || segment]
27
- if (current && current.target) current = model.definitions[current.target]
27
+ if (current && current.target) current = current._target
28
28
  }
29
29
  return current
30
30
  }
@@ -3,6 +3,7 @@ module.exports = name => {
3
3
  try {
4
4
  return require(name)
5
5
  } catch (e) {
6
- throw new Error(`Unable to require required package/file "${name}"`)
6
+ e.message = `Cannot find module '${name}'. Make sure to install it with 'npm i ${name}'\n` + e.message
7
+ throw e
7
8
  }
8
9
  }
@@ -243,6 +243,9 @@ const _newEntries = (entries = [], transition, service) =>
243
243
 
244
244
  const _newWhere = (where = [], transition, tableName, alias, isSubselect = false) => {
245
245
  const newWhere = where.map(whereElement => {
246
+ if (whereElement.xpr) {
247
+ return { xpr: _newWhere(whereElement.xpr, transition, tableName, alias, isSubselect) }
248
+ }
246
249
  const newWhereElement = { ...whereElement }
247
250
  if (!whereElement.ref && !whereElement.SELECT && !whereElement.func) return whereElement
248
251
  if (whereElement.SELECT && whereElement.SELECT.where) {
@@ -47,7 +47,8 @@ const _flattenStructuredInExpand = (column, { _target: expandedEntity }) => {
47
47
  if (orderBy) {
48
48
  column.orderBy = orderBy
49
49
  }
50
- column.where = flattenStructuredWhereHaving(column.where, expandedEntity)
50
+ const columnWhere = flattenStructuredWhereHaving(column.where, expandedEntity)
51
+ if (columnWhere) column.where = columnWhere
51
52
  column.expand = column.expand.filter(e => !e.ref || !toBeDeleted.includes(e.ref[e.ref.length - 1]))
52
53
  column.expand.push(...flattenedElements)
53
54
  }
@@ -205,6 +206,10 @@ const flattenStructuredWhereHaving = (filterArray, csnEntity, model) => {
205
206
 
206
207
  const newFilterArray = []
207
208
  for (let i = 0; i < filterArray.length; i++) {
209
+ if (filterArray[i].xpr) {
210
+ newFilterArray.push({ xpr: flattenStructuredWhereHaving(filterArray[i].xpr, csnEntity, model) })
211
+ continue
212
+ }
208
213
  if (filterArray[i + 1] in OPERATIONS_MAP) {
209
214
  const refElement = filterArray[i].ref ? filterArray[i] : filterArray[i + 2]
210
215
 
@@ -38,6 +38,16 @@ class DatabaseService extends cds.Service {
38
38
  // REVISIT: how to generic handler registration?
39
39
  }
40
40
 
41
+ /** Database services don't support custom-defined operations */
42
+ operations() {
43
+ return []
44
+ }
45
+
46
+ /** Database services don't support custom-defined events */
47
+ events() {
48
+ return []
49
+ }
50
+
41
51
  /*
42
52
  * tx
43
53
  */
@@ -79,16 +79,24 @@ const _autoExpandNavsAndAttachToResult = async (entity, previousResult, depth, o
79
79
  return previousResult
80
80
  }
81
81
 
82
+ const _fkForOnCOnd = (onCond, requiredFks) => {
83
+ for (const ele of onCond) {
84
+ if (ele.xpr) {
85
+ _fkForOnCOnd(ele.xpr, requiredFks)
86
+ }
87
+
88
+ if (ele.ref && ele.ref[0] === 'parent') {
89
+ requiredFks.add(ele.ref.slice(1).join('_'))
90
+ }
91
+ }
92
+ }
93
+
82
94
  const _foreignKeysOfTopLevelNavs = (entity, options) => {
83
95
  const requiredFks = new Set()
84
96
  for (const nav in entity._associations) {
85
97
  if (options.onlyCompositions && entity._associations[nav]._isAssociationEffective) continue
86
98
  const onCond = entity._relations[nav].join('child', 'parent')
87
- for (const ele of onCond) {
88
- if (ele.ref && ele.ref[0] === 'parent') {
89
- requiredFks.add(ele.ref.slice(1).join('_'))
90
- }
91
- }
99
+ _fkForOnCOnd(onCond, requiredFks)
92
100
  }
93
101
  return [...requiredFks]
94
102
  }
@@ -395,6 +395,9 @@ class JoinCQNFromExpanded {
395
395
  */
396
396
  _adaptWhereSELECT(aliasedTable, where, tableAlias) {
397
397
  return where.map(element => {
398
+ if (element.xpr) {
399
+ return { xpr: this._adaptWhereSELECT(aliasedTable, element.xpr, tableAlias) }
400
+ }
398
401
  return this._elementAliasNeedsReplacement(element, aliasedTable)
399
402
  ? Object.assign({}, element, { ref: [tableAlias, element.ref[1]] })
400
403
  : element
@@ -870,13 +873,21 @@ class JoinCQNFromExpanded {
870
873
  const aliases = this._getAliases(subSelectColumns)
871
874
  const on = entity._relations[tableAlias === columns[0] ? columns.slice(1) : columns].join(tableAlias, parentAlias)
872
875
 
876
+ this._adjustAliases(on, aliases)
877
+
878
+ return on
879
+ }
880
+
881
+ _adjustAliases(on, aliases) {
873
882
  for (const element of on) {
883
+ if (element.xpr) {
884
+ this._adjustAliases(element.xpr, aliases)
885
+ continue
886
+ }
874
887
  if (element.ref && aliases[element.ref[0]] && aliases[element.ref[0]][element.ref[1]]) {
875
888
  element.ref[1] = aliases[element.ref[0]][element.ref[1]]
876
889
  }
877
890
  }
878
-
879
- return on
880
891
  }
881
892
 
882
893
  _addJoinKeyColumnsToUnion(args, on, parentAlias) {
@@ -899,23 +910,27 @@ class JoinCQNFromExpanded {
899
910
  }
900
911
 
901
912
  _addColumns(columns, on, parentAlias, withAlias = false) {
902
- const keyColumns = on
903
- .filter(entry => {
904
- return (
905
- entry.ref &&
906
- entry.ref[0] === parentAlias &&
907
- !columns.some(column => column.ref && column.ref[column.ref.length - 1] === entry.ref[1])
913
+ for (const entry of on) {
914
+ if (entry.xpr) {
915
+ this._addColumns(columns, entry.xpr, parentAlias, withAlias)
916
+ continue
917
+ }
918
+ if (
919
+ entry.ref &&
920
+ entry.ref[0] === parentAlias &&
921
+ !columns.some(column => column.ref && column.ref[column.ref.length - 1] === entry.ref[1])
922
+ ) {
923
+ columns.push(
924
+ withAlias
925
+ ? { ref: [parentAlias, entry.ref[1]], as: `${parentAlias}_${entry.ref[1]}` }
926
+ : { ref: [entry.ref[1]] }
908
927
  )
909
- })
910
- .map(entry =>
911
- withAlias ? { ref: [parentAlias, entry.ref[1]], as: `${parentAlias}_${entry.ref[1]}` } : { ref: [entry.ref[1]] }
912
- )
913
- if (keyColumns.length === 0) return
914
- columns.push(...keyColumns)
928
+ }
929
+ }
915
930
  }
916
931
 
917
932
  /**
918
- * Add an unique alias to each column, to avoid ambiguity.
933
+ * Add a unique alias to each column, to avoid ambiguity.
919
934
  * Add this information to the post process config.
920
935
  *
921
936
  * @param {object} column
@@ -1318,6 +1333,26 @@ class JoinCQNFromExpanded {
1318
1333
  return newObj
1319
1334
  }
1320
1335
 
1336
+ _getFilterColumns(readToOneCQN, on, parentAlias) {
1337
+ const columns = []
1338
+ const outerColumns = []
1339
+
1340
+ for (const entry of on) {
1341
+ if (entry.xpr) {
1342
+ const { columns: cols, outerColumns: outerCols } = this._getFilterColumns(readToOneCQN, entry.xpr, parentAlias)
1343
+ columns.push(...cols)
1344
+ outerColumns.push(...outerCols)
1345
+ continue
1346
+ }
1347
+ if (typeof entry === 'object' && entry.ref && entry.ref[0] === 'filterExpand') {
1348
+ columns.push(this._getColumnObjectForFilterExpand(readToOneCQN, parentAlias, entry.ref[1]))
1349
+ outerColumns.push({ ref: [entry.ref[1]] })
1350
+ }
1351
+ }
1352
+
1353
+ return { columns, outerColumns }
1354
+ }
1355
+
1321
1356
  /**
1322
1357
  * Reduce column list to column(s) needed to merge the result into one.
1323
1358
  *
@@ -1329,22 +1364,13 @@ class JoinCQNFromExpanded {
1329
1364
  * @private
1330
1365
  */
1331
1366
  _getFilterExpandCQN(readToOneCQN, on, parentAlias, keyObject) {
1332
- const columns = []
1333
-
1334
- const outerColumns = []
1335
-
1336
- for (const entry of on) {
1337
- if (typeof entry === 'object' && entry.ref && entry.ref[0] === 'filterExpand') {
1338
- columns.push(this._getColumnObjectForFilterExpand(readToOneCQN, parentAlias, entry.ref[1]))
1339
- outerColumns.push({ ref: [entry.ref[1]] })
1340
- }
1341
- }
1342
-
1343
1367
  const keys = Object.keys(keyObject).filter(
1344
1368
  key =>
1345
1369
  key !== 'IsActiveEntity' && !keyObject[key].is2one && !keyObject[key].is2many && !keyObject[key]._isStructured
1346
1370
  )
1347
1371
 
1372
+ const { columns, outerColumns } = this._getFilterColumns(readToOneCQN, on, parentAlias)
1373
+
1348
1374
  for (const key of keys) {
1349
1375
  if (!columns.map(entry => entry.as).includes(key)) {
1350
1376
  columns.push(this._getColumnObjectForFilterExpand(readToOneCQN, parentAlias, key))
@@ -1455,6 +1481,10 @@ class JoinCQNFromExpanded {
1455
1481
  this._addColumNames(entity, parentAlias, columnNames)
1456
1482
 
1457
1483
  for (const entry of on) {
1484
+ if (entry.xpr) {
1485
+ columns.push(...this._getJoinColumnsFromOnAddToMapping(mapping, parentAlias, entry.xpr, entity))
1486
+ continue
1487
+ }
1458
1488
  if (typeof entry === 'object' && entry.ref && entry.ref[0] !== 'filterExpand') {
1459
1489
  const as = entry.ref.join('_')
1460
1490
  columns.push({
@@ -20,6 +20,11 @@ const _redirectXpr = (xpr, aliasMap) => {
20
20
  return
21
21
  }
22
22
 
23
+ if (element.xpr) {
24
+ _redirectXpr(element.xpr, aliasMap)
25
+ return
26
+ }
27
+
23
28
  if (element.func) {
24
29
  _redirectXpr(element.args, aliasMap)
25
30
  return
@@ -98,6 +103,10 @@ const generateAliases = query => {
98
103
 
99
104
  const _addParentAlias = (where, alias) => {
100
105
  where.forEach(e => {
106
+ if (e.xpr) {
107
+ _addParentAlias(e.xpr, alias)
108
+ return
109
+ }
101
110
  if (e.ref && e.ref[0].match(PARENT_ALIAS_REGEX)) {
102
111
  e.ref[0] = alias
103
112
  }
@@ -21,6 +21,7 @@ const _processorFn = ({ row, key }) => {
21
21
  }
22
22
 
23
23
  function transformExtendedFieldsCREATE(req) {
24
+ if (!req.query) return // FIXME: the code below expects req.query to be defined
24
25
  if (!req.target) return
25
26
  if (!req.query.INSERT.entries) return // REVISIT: breaks at cds.deploy -> should anyways not kick in during cds.deploy
26
27