@sap/cds 5.9.2 → 5.9.5

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 (34) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/lib/compile/for/drafts.js +1 -1
  3. package/lib/index.js +1 -1
  4. package/lib/serve/Service-methods.js +47 -1
  5. package/libx/_runtime/auth/index.js +16 -1
  6. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/BatchRequestListBuilder.js +3 -1
  7. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -3
  8. package/libx/_runtime/common/aspects/utils.js +8 -2
  9. package/libx/_runtime/common/composition/data.js +22 -13
  10. package/libx/_runtime/common/composition/delete.js +14 -12
  11. package/libx/_runtime/common/generic/auth/expand.js +1 -0
  12. package/libx/_runtime/common/generic/input.js +1 -0
  13. package/libx/_runtime/common/generic/put.js +1 -0
  14. package/libx/_runtime/common/utils/cqn.js +5 -10
  15. package/libx/_runtime/common/utils/cqn2cqn4sql.js +39 -75
  16. package/libx/_runtime/common/utils/foreignKeyPropagations.js +28 -8
  17. package/libx/_runtime/common/utils/path.js +3 -3
  18. package/libx/_runtime/common/utils/require.js +2 -1
  19. package/libx/_runtime/common/utils/resolveView.js +3 -0
  20. package/libx/_runtime/common/utils/structured.js +6 -1
  21. package/libx/_runtime/db/Service.js +10 -0
  22. package/libx/_runtime/db/expand/expand-v2.js +13 -5
  23. package/libx/_runtime/db/expand/expandCQNToJoin.js +56 -26
  24. package/libx/_runtime/db/utils/generateAliases.js +9 -0
  25. package/libx/_runtime/extensibility/uiflex/handler/transformWRITE.js +1 -0
  26. package/libx/_runtime/fiori/generic/read.js +83 -31
  27. package/libx/_runtime/fiori/generic/readOverDraft.js +22 -13
  28. package/libx/_runtime/fiori/utils/handler.js +3 -0
  29. package/libx/_runtime/fiori/utils/where.js +38 -25
  30. package/libx/_runtime/hana/driver.js +1 -1
  31. package/libx/_runtime/hana/search2cqn4sql.js +4 -1
  32. package/libx/_runtime/remote/Service.js +3 -3
  33. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +19 -12
  34. package/package.json +1 -1
@@ -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
 
@@ -19,9 +19,18 @@ const { deleteCondition, readAndDeleteKeywords, removeIsActiveEntityRecursively
19
19
  const { getColumns } = require('../../cds-services/services/utils/columns')
20
20
  const { adaptStreamCQN } = require('../../cds-services/adapter/odata-v4/utils/stream')
21
21
 
22
+ const _findSubselect = where => {
23
+ return where.find((e, i) => {
24
+ if (e.xpr) {
25
+ return _findSubselect(e.xpr)
26
+ }
27
+ return e.SELECT && where[i - 1] === 'exists'
28
+ })
29
+ }
30
+
22
31
  const _findRootSubSelectFor = query => {
23
32
  if (query.SELECT.where) {
24
- const subSelect = query.SELECT.where.find((e, i) => e.SELECT && query.SELECT.where[i - 1] === 'exists')
33
+ const subSelect = _findSubselect(query.SELECT.where)
25
34
  return subSelect ? _findRootSubSelectFor(subSelect) : query
26
35
  }
27
36
  return query
@@ -422,8 +431,8 @@ const _allActive = (req, columns, model) => {
422
431
  cqn.leftJoin(ensureDraftsSuffix(table.ref[0]) + ' as drafts').on(`${table.as}.${ids[0]} = drafts.${ids[0]}`)
423
432
 
424
433
  for (let i = 1; i < ids.length; i++) {
425
- // REVISIT: this is extremely expensive as it repeatedly invokes the compiler's cds.parse.expr -> better extend plain CQN yourself here
426
- cqn.and(`${table.as}.${ids[i]} = drafts.${ids[i]}`)
434
+ // this 'and' belongs to the join condition and is not a where and
435
+ cqn.and({ ref: [table.as, ids[i]] }, '=', { ref: ['drafts', ids[i]] })
427
436
  }
428
437
  }
429
438
 
@@ -557,27 +566,38 @@ const _alignAliasForUnion = (table, as, select) => {
557
566
  return select
558
567
  }
559
568
 
560
- const _findJoinInQuery = (query, parentAlias) => {
561
- const targetAlias = query.SELECT.from.as
562
- const isTargetRef = el => targetAlias && el.ref && el.ref.length > 1 && el.ref[0] === targetAlias
563
- if (query.SELECT && query.SELECT.where)
564
- return query.SELECT.where.reduce((links, el, idx, where) => {
565
- if (el.ref && el.ref[0] === parentAlias && el.ref[el.ref.length - 1] !== 'IsActiveEntity') {
566
- if (where[idx - 1] && where[idx - 1] === '=' && isTargetRef(where[idx - 2])) {
567
- if (links.length) links.push('and')
568
- links.push(el, '=', where[idx - 2])
569
- }
570
- if (where[idx + 1] && where[idx + 1] === '=' && isTargetRef(where[idx + 2])) {
571
- if (links.length) links.push('and')
572
- links.push(el, '=', where[idx + 2])
573
- }
569
+ const isTargetRef = (el, targetAlias) => targetAlias && el.ref && el.ref.length > 1 && el.ref[0] === targetAlias
570
+
571
+ const _joinFromWhere = (where, parentAlias, targetAlias) => {
572
+ return where.reduce((links, el, idx, where) => {
573
+ if (el.xpr) {
574
+ const result = _joinFromWhere(el.xpr, parentAlias, targetAlias)
575
+ if (result.length) {
576
+ if (links.length) links.push('and')
577
+ links.push(...result)
574
578
  }
575
579
  return links
576
- }, [])
577
- return []
580
+ }
581
+
582
+ if (el.ref && el.ref[0] === parentAlias && el.ref[el.ref.length - 1] !== 'IsActiveEntity') {
583
+ if (where[idx - 1] && where[idx - 1] === '=' && isTargetRef(where[idx - 2], targetAlias)) {
584
+ if (links.length) links.push('and')
585
+ links.push(el, '=', where[idx - 2])
586
+ }
587
+ if (where[idx + 1] && where[idx + 1] === '=' && isTargetRef(where[idx + 2], targetAlias)) {
588
+ if (links.length) links.push('and')
589
+ links.push(el, '=', where[idx + 2])
590
+ }
591
+ }
592
+ return links
593
+ }, [])
578
594
  }
579
595
 
580
- const _isFiltered = where => where.some(element => !(element in ['(', ')']))
596
+ const _findJoinInQuery = (query, parentAlias) => {
597
+ const targetAlias = query.SELECT.from.as
598
+ if (query.SELECT && query.SELECT.where) return _joinFromWhere(query.SELECT.where, parentAlias, targetAlias)
599
+ return []
600
+ }
581
601
 
582
602
  const _isDraftField = element => element.ref && element.ref.length > 1 && element.ref[0] === 'DraftAdministrativeData'
583
603
 
@@ -597,6 +617,10 @@ const _isLogicalFunction = (where, index) => {
597
617
  const _getWhereForActive = where => {
598
618
  const activeWhere = []
599
619
  for (let i = 0; i < where.length; i++) {
620
+ if (where[i].xpr) {
621
+ activeWhere.push({ xpr: _getWhereForActive(where[i].xpr) })
622
+ continue
623
+ }
600
624
  if (_isDraftField(where[i])) {
601
625
  activeWhere.push({ val: null })
602
626
  } else if (_functionContainsDraftField(where[i])) {
@@ -721,10 +745,20 @@ const _getSiblingScenario = (req, columns, model, siblingIndex, nav) => {
721
745
  const draftAdminAlias = _isDraftAdminScenario(req) && req.query.SELECT.from.as
722
746
  const params = [...req.params].reverse()
723
747
  const _getSiblingQueryFromWhere = (query, queryIndex, parentQuery) => {
724
- if (query.SELECT && query.SELECT.where) {
725
- const indexExists = query.SELECT.where.indexOf('exists')
726
- if (indexExists > -1 && queryIndex > 0) {
727
- return _getSiblingQueryFromWhere(query.SELECT.where[indexExists + 1], queryIndex - 1, query)
748
+ if (query.SELECT && query.SELECT.where && queryIndex > 0) {
749
+ for (let i = 0; i < query.SELECT.where.length; i++) {
750
+ if (query.SELECT.where[i].xpr && queryIndex > 0) {
751
+ const sibilingQueryFromWhere = _getSiblingQueryFromWhere(
752
+ { SELECT: { where: query.SELECT.where[i].xpr } },
753
+ queryIndex - 1,
754
+ query
755
+ )
756
+ if (sibilingQueryFromWhere) return sibilingQueryFromWhere
757
+ }
758
+
759
+ if (query.SELECT.where[i] === 'exists' && queryIndex > 0) {
760
+ return _getSiblingQueryFromWhere(query.SELECT.where[i + 1], queryIndex - 1, query)
761
+ }
728
762
  }
729
763
  }
730
764
  const target = { name: query.SELECT.from.ref[0].id || query.SELECT.from.ref[0], as: query.SELECT.from.as }
@@ -741,9 +775,16 @@ const _getSiblingScenario = (req, columns, model, siblingIndex, nav) => {
741
775
  return _getSiblingQueryFromWhere(req.query, siblingIndex)
742
776
  }
743
777
 
744
- const _mergeSiblingIntoCQN = (cqn, { cqn: siblingCQN }, siblingIndex) => {
745
- const _replaceWhereExists = (query, _siblingIndex) => {
746
- if (query.SELECT && query.SELECT.where) {
778
+ const _replaceWhereExists = (query, _siblingIndex, siblingCQN) => {
779
+ if (query.SELECT && query.SELECT.where) {
780
+ for (let i = 0; i < query.SELECT.where.length; i++) {
781
+ const whereElement = query.SELECT.where[i]
782
+ if (whereElement.xpr) {
783
+ const res = _replaceWhereExists({ SELECT: { where: whereElement.xpr } }, _siblingIndex, siblingCQN)
784
+ if (res) return res
785
+ continue
786
+ }
787
+
747
788
  const indexExists = query.SELECT.where.indexOf('exists')
748
789
  if (indexExists > -1) {
749
790
  if (_siblingIndex > 0) return _replaceWhereExists(query.SELECT.where[indexExists + 1], _siblingIndex - 1)
@@ -751,7 +792,10 @@ const _mergeSiblingIntoCQN = (cqn, { cqn: siblingCQN }, siblingIndex) => {
751
792
  }
752
793
  }
753
794
  }
754
- return _replaceWhereExists(cqn, siblingIndex)
795
+ }
796
+
797
+ const _mergeSiblingIntoCQN = (cqn, { cqn: siblingCQN }, siblingIndex) => {
798
+ return _replaceWhereExists(cqn, siblingIndex, siblingCQN)
755
799
  }
756
800
 
757
801
  const _getDraftDoc = (req, draftName, draftWhere) => {
@@ -796,6 +840,11 @@ const _getOrderByEnrichedColumns = (orderBy, columns, entity) => {
796
840
 
797
841
  const _replaceDraftAlias = where => {
798
842
  where.forEach(element => {
843
+ if (element.xpr) {
844
+ _replaceDraftAlias(element.xpr)
845
+ return
846
+ }
847
+
799
848
  if (_isDraftField(element)) {
800
849
  element.ref[0] = 'filterAdmin'
801
850
  }
@@ -981,7 +1030,10 @@ const _validatedDraftOfWhichIAmOwner = (req, draftWhere, draftParameters, column
981
1030
  _isValidDraftOfWhichIAmOwner(draftParameters.isActiveEntity) && _draftOfWhichIAmOwner(req, draftWhere, columns)
982
1031
 
983
1032
  const _draftInSubSelect = (where, req) => {
984
- return where.some(({ SELECT }) => {
1033
+ return where.some(({ SELECT, xpr }) => {
1034
+ if (xpr) {
1035
+ return _draftInSubSelect(xpr, req)
1036
+ }
985
1037
  if (SELECT && SELECT.where) {
986
1038
  const isActiveEntity = readAndDeleteKeywords(['IsActiveEntity'], SELECT.where, false)
987
1039
  if (isActiveEntity) {
@@ -1022,7 +1074,7 @@ const _generateCQN = (originalFrom, req, columns, model) => {
1022
1074
  return _draftAdminTable(req)
1023
1075
  }
1024
1076
 
1025
- if (!req.query.SELECT.where || !_isFiltered(req.query.SELECT.where)) {
1077
+ if (!req.query.SELECT.where) {
1026
1078
  return _allActive(req, columns, model)
1027
1079
  }
1028
1080
 
@@ -1032,7 +1084,7 @@ const _generateCQN = (originalFrom, req, columns, model) => {
1032
1084
  if (
1033
1085
  draftParameters.isActiveEntity &&
1034
1086
  _isTrue(draftParameters.isActiveEntity.value.val) &&
1035
- !draftParameters.siblingIsActive &&
1087
+ !(draftParameters.siblingIsActive && draftParameters.siblingIsActive.value.val === null) &&
1036
1088
  !draftParameters.hasDraftEntity
1037
1089
  ) {
1038
1090
  return _allActive(req, columns, model)
@@ -5,18 +5,14 @@ const { readAndDeleteKeywords } = require('../utils/where')
5
5
  const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
6
6
  const { isActiveEntityRequested } = require('../../../_runtime/fiori/utils/where')
7
7
 
8
- const _modifyCQN = (cqnDraft, where, context) => {
9
- const whereDraft = [...where]
10
- const result = readAndDeleteKeywords(['IsActiveEntity'], whereDraft)
11
- cqnDraft.where(whereDraft)
8
+ const _modifyWhere = (where, context) => {
9
+ for (let i = 0; i < where.length; i++) {
10
+ const element = where[i]
12
11
 
13
- if (result && result.value.val === false) {
14
- const fromRef = cqnDraft.SELECT.from.ref
15
- cqnDraft.SELECT.from.ref[fromRef.length - 1] = ensureDraftsSuffix(fromRef[fromRef.length - 1])
16
- }
17
-
18
- for (let i = 0; i < cqnDraft.SELECT.where.length; i++) {
19
- const element = cqnDraft.SELECT.where[i]
12
+ if (element.xpr) {
13
+ _modifyWhere(element.xpr, context)
14
+ continue
15
+ }
20
16
 
21
17
  if (element.SELECT) {
22
18
  const subCqnDraft = SELECT.from(
@@ -27,12 +23,25 @@ const _modifyCQN = (cqnDraft, where, context) => {
27
23
  [1]
28
24
  )
29
25
 
30
- cqnDraft.SELECT.where[i] = subCqnDraft
26
+ where[i] = subCqnDraft
31
27
  _modifyCQN(subCqnDraft, element.SELECT.where, context)
32
28
  }
33
29
  }
34
30
  }
35
31
 
32
+ const _modifyCQN = (cqnDraft, where, context) => {
33
+ const whereDraft = [...where]
34
+ const result = readAndDeleteKeywords(['IsActiveEntity'], whereDraft)
35
+ cqnDraft.where(whereDraft)
36
+
37
+ if (result && result.value.val === false) {
38
+ const fromRef = cqnDraft.SELECT.from.ref
39
+ cqnDraft.SELECT.from.ref[fromRef.length - 1] = ensureDraftsSuffix(fromRef[fromRef.length - 1])
40
+ }
41
+
42
+ _modifyWhere(cqnDraft.SELECT.where, context)
43
+ }
44
+
36
45
  const _hasNavToNonDraftEnclosedAssoc = (pathSegments, definitions, excludeAssoc) => {
37
46
  if (pathSegments.length < 2) return false
38
47
  const entity = definitions[pathSegments[0]]
@@ -112,7 +121,7 @@ const _readOverDraftHandler = async function (req, next) {
112
121
 
113
122
  const hasDraftEntity = hasDraft(definitions, sqlQuery)
114
123
 
115
- if (hasDraftEntity && sqlQuery.SELECT.where && sqlQuery.SELECT.where.length > 0) {
124
+ if (hasDraftEntity && sqlQuery.SELECT.where && sqlQuery.SELECT.where.length) {
116
125
  let cqnDraft = SELECT.from({
117
126
  ref: [...sqlQuery.SELECT.from.ref],
118
127
  as: sqlQuery.SELECT.from.as
@@ -93,6 +93,9 @@ const getUpdateDraftAdminCQN = ({ user }, draftUUID) => {
93
93
  const _addAlias = (where, tableName) => {
94
94
  // copy where
95
95
  return where.map(element => {
96
+ if (element.xpr) {
97
+ return { xpr: _addAlias(element.xpr, tableName) }
98
+ }
96
99
  if (element.ref && element.ref.length === 1) {
97
100
  // and copy ref
98
101
  return { ref: [tableName, element.ref[0]] }
@@ -57,7 +57,9 @@ const _removeIsActiveEntityCondition = where => {
57
57
  } else if (where[i] === 'and' && where[i + 1] === '(' && _isActiveEntity(where[i + 2])) {
58
58
  i = i + 6
59
59
  } else if (where[i].xpr) {
60
- newWhere.push({ xpr: _removeIsActiveEntityCondition(where[i].xpr) })
60
+ if (where[i].xpr.length) {
61
+ newWhere.push({ xpr: _removeIsActiveEntityCondition(where[i].xpr) })
62
+ }
61
63
  i++
62
64
  } else {
63
65
  newWhere.push(where[i])
@@ -65,7 +67,7 @@ const _removeIsActiveEntityCondition = where => {
65
67
  }
66
68
  }
67
69
 
68
- if (newWhere[0] === 'and') {
70
+ if (newWhere[0] === 'and' || newWhere[0] === 'or') {
69
71
  newWhere.splice(0, 1)
70
72
  } else if (newWhere[0] === '(' && newWhere[1] === 'and') {
71
73
  newWhere.splice(0, 2)
@@ -90,32 +92,35 @@ const deleteCondition = (index, whereCondition, isXpr = false) => {
90
92
  }
91
93
 
92
94
  const readAndDeleteKeywords = (keywords, whereCondition, toDelete = true) => {
93
- let index = whereCondition.findIndex(({ xpr }) => xpr)
94
- if (index !== -1) {
95
- const result = readAndDeleteKeywords(keywords, whereCondition[index].xpr, toDelete)
96
- if (result) {
97
- if (whereCondition[index].xpr.length === 0) {
98
- deleteCondition(index, whereCondition, true)
95
+ let index = -1
96
+ for (let i = 0; i < whereCondition.length; i++) {
97
+ const entry = whereCondition[i]
98
+ if (entry.xpr) {
99
+ const result = readAndDeleteKeywords(keywords, entry.xpr, toDelete)
100
+ if (result) {
101
+ if (entry.xpr.length === 0) {
102
+ deleteCondition(i, whereCondition, true)
103
+ }
104
+ return result
105
+ }
106
+ } else if (entry.ref) {
107
+ const refLastIndex = entry.ref.length - 1
108
+
109
+ if (keywords.length === 1) {
110
+ if (entry.ref[refLastIndex] === keywords[0]) {
111
+ index = i
112
+ break
113
+ }
99
114
  }
100
- return result
101
- }
102
- }
103
-
104
- index = whereCondition.findIndex(({ ref }) => {
105
- if (!ref) {
106
- return false
107
- }
108
-
109
- const refLastIndex = ref.length - 1
110
-
111
- if (keywords.length === 1) {
112
- return ref[refLastIndex] === keywords[0]
113
- }
114
115
 
115
- if (keywords.length === 2 && ref.length >= 2) {
116
- return ref[refLastIndex - 1] === keywords[0] && ref[refLastIndex] === keywords[1]
116
+ if (keywords.length === 2 && entry.ref.length >= 2) {
117
+ if (entry.ref[refLastIndex - 1] === keywords[0] && entry.ref[refLastIndex] === keywords[1]) {
118
+ index = i
119
+ break
120
+ }
121
+ }
117
122
  }
118
- })
123
+ }
119
124
 
120
125
  if (index === -1) {
121
126
  return
@@ -135,6 +140,10 @@ const readAndDeleteKeywords = (keywords, whereCondition, toDelete = true) => {
135
140
 
136
141
  const removeIsActiveEntityRecursively = where => {
137
142
  for (const entry of where) {
143
+ if (entry.xpr) {
144
+ entry.xpr = removeIsActiveEntityRecursively(entry.xpr)
145
+ continue
146
+ }
138
147
  if (entry.SELECT && entry.SELECT.where && entry.SELECT.from.ref && !entry.SELECT.from.ref[0].endsWith('_drafts')) {
139
148
  entry.SELECT.where = removeIsActiveEntityRecursively(entry.SELECT.where)
140
149
 
@@ -153,6 +162,10 @@ const isActiveEntityRequested = where => {
153
162
  let i = 0
154
163
 
155
164
  while (where[i]) {
165
+ if (where[i].xpr) {
166
+ const isRequested = isActiveEntityRequested(where.xpr)
167
+ if (isRequested) return true
168
+ }
156
169
  if (
157
170
  where[i].ref &&
158
171
  where[i].ref[where[i].ref.length - 1] === 'IsActiveEntity' &&
@@ -37,7 +37,7 @@ function _connectHdb(creds, tenant) {
37
37
  // tls keep alive
38
38
  if (process.env.HDB_TCP_KEEP_ALIVE_IDLE) {
39
39
  const num = Number(process.env.HDB_TCP_KEEP_ALIVE_IDLE)
40
- creds.tcpKeepAliveIdle = Number.NaN(num) ? false : num
40
+ creds.tcpKeepAliveIdle = Number.isNaN(num) ? false : num
41
41
  }
42
42
 
43
43
  const hdbClient = this.createClient(creds)
@@ -37,8 +37,11 @@ const search2cqn4sql = (query, entity, options) => {
37
37
  if (resolveLocalizedDataAtRuntime) {
38
38
  const onCondition = entity._relations[localizedAssociation.name].join(localizedAssociation.target, entity.name)
39
39
 
40
+ // REVISIT this is dirty but works for now
40
41
  // replace $user_locale placeholder with the user locale or the HANA session context
41
- onCondition[onCondition.length - 2] = { val: locale || "SESSION_CONTEXT('LOCALE')" }
42
+ if (onCondition[0].xpr) {
43
+ onCondition[0].xpr[onCondition[0].xpr.length - 1] = { val: locale || "SESSION_CONTEXT('LOCALE')" }
44
+ } else onCondition[onCondition.length - 2] = { val: locale || "SESSION_CONTEXT('LOCALE')" }
42
45
 
43
46
  // inner join the target table with the _texts table (the _texts table contains
44
47
  // the translated texts)