@sap/cds 5.7.5 → 5.8.0

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 (141) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/app/fiori/routes.js +1 -1
  3. package/bin/deploy/to-hana/cfUtil.js +251 -138
  4. package/bin/deploy/to-hana/gitUtil.js +55 -0
  5. package/bin/deploy/to-hana/hana.js +92 -93
  6. package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
  7. package/bin/deploy/to-hana/index.js +14 -13
  8. package/bin/mtx/in-cds.js +1 -0
  9. package/bin/serve.js +1 -1
  10. package/bin/version.js +1 -0
  11. package/lib/compile/cdsc.js +0 -6
  12. package/lib/compile/resolve.js +1 -1
  13. package/lib/compile/to/srvinfo.js +1 -1
  14. package/lib/core/classes.js +21 -1
  15. package/lib/env/index.js +3 -2
  16. package/lib/env/requires.js +4 -0
  17. package/lib/i18n/localize.js +5 -8
  18. package/lib/index.js +1 -0
  19. package/lib/log/errors.js +1 -1
  20. package/lib/ql/SELECT.js +2 -2
  21. package/lib/req/cds-context.js +1 -1
  22. package/lib/req/context.js +1 -1
  23. package/lib/serve/Transaction.js +9 -5
  24. package/lib/serve/index.js +13 -21
  25. package/lib/utils/tests.js +90 -66
  26. package/libx/_runtime/audit/generic/personal/modification.js +0 -8
  27. package/libx/_runtime/auth/index.js +7 -6
  28. package/libx/_runtime/auth/strategies/dwc.js +43 -0
  29. package/libx/_runtime/auth/utils.js +24 -0
  30. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -32
  31. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
  32. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +2 -5
  46. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
  47. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
  48. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
  49. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
  50. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
  51. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
  52. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
  53. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
  54. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  55. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -4
  56. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
  57. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
  58. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +60 -18
  59. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +7 -5
  60. package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
  61. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
  62. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
  63. package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
  64. package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
  65. package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
  66. package/libx/_runtime/common/aspects/Association.js +16 -0
  67. package/libx/_runtime/common/composition/data.js +28 -37
  68. package/libx/_runtime/common/composition/delete.js +107 -58
  69. package/libx/_runtime/common/composition/index.js +2 -1
  70. package/libx/_runtime/common/composition/insert.js +13 -13
  71. package/libx/_runtime/common/composition/update.js +39 -34
  72. package/libx/_runtime/common/error/frontend.js +17 -2
  73. package/libx/_runtime/common/generic/auth.js +20 -85
  74. package/libx/_runtime/common/generic/crud.js +22 -1
  75. package/libx/_runtime/common/i18n/messages.properties +2 -1
  76. package/libx/_runtime/common/utils/cqn.js +2 -6
  77. package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
  78. package/libx/_runtime/common/utils/csn.js +14 -3
  79. package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -1
  80. package/libx/_runtime/common/utils/keys.js +2 -1
  81. package/libx/_runtime/common/utils/path.js +1 -1
  82. package/libx/_runtime/common/utils/resolveView.js +12 -4
  83. package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
  84. package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
  85. package/libx/_runtime/common/utils/vcap.js +27 -10
  86. package/libx/_runtime/db/data-conversion/post-processing.js +20 -13
  87. package/libx/_runtime/db/expand/expand-v2.js +21 -12
  88. package/libx/_runtime/db/expand/expandCQNToJoin.js +8 -6
  89. package/libx/_runtime/db/expand/index.js +3 -0
  90. package/libx/_runtime/db/generic/create.js +0 -10
  91. package/libx/_runtime/db/generic/index.js +3 -0
  92. package/libx/_runtime/db/generic/read.js +2 -24
  93. package/libx/_runtime/db/generic/rewrite.js +1 -3
  94. package/libx/_runtime/db/generic/update.js +1 -1
  95. package/libx/_runtime/db/query/delete.js +10 -4
  96. package/libx/_runtime/db/query/insert.js +3 -3
  97. package/libx/_runtime/db/query/read.js +4 -1
  98. package/libx/_runtime/db/query/update.js +5 -5
  99. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
  100. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
  101. package/libx/_runtime/db/sql-builder/index.js +3 -0
  102. package/libx/_runtime/db/utils/columns.js +5 -2
  103. package/libx/_runtime/db/utils/deep.js +6 -8
  104. package/libx/_runtime/db/utils/generateAliases.js +56 -6
  105. package/libx/_runtime/fiori/generic/before.js +73 -49
  106. package/libx/_runtime/fiori/generic/edit.js +14 -18
  107. package/libx/_runtime/fiori/generic/patch.js +8 -11
  108. package/libx/_runtime/fiori/generic/read.js +19 -16
  109. package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
  110. package/libx/_runtime/hana/Service.js +1 -1
  111. package/libx/_runtime/hana/conversion.js +10 -0
  112. package/libx/_runtime/hana/execute.js +33 -16
  113. package/libx/_runtime/hana/search.js +3 -3
  114. package/libx/_runtime/hana/search2cqn4sql.js +22 -21
  115. package/libx/_runtime/hana/searchToContains.js +1 -1
  116. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  117. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
  118. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  119. package/libx/_runtime/messaging/file-based.js +3 -1
  120. package/libx/_runtime/messaging/service.js +4 -1
  121. package/libx/_runtime/remote/utils/client.js +33 -20
  122. package/libx/_runtime/remote/utils/data.js +52 -11
  123. package/libx/_runtime/sqlite/Service.js +1 -1
  124. package/libx/_runtime/sqlite/conversion.js +10 -0
  125. package/libx/_runtime/types/api.js +2 -2
  126. package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
  127. package/libx/odata/afterburner.js +29 -6
  128. package/libx/odata/cqn2odata.js +9 -0
  129. package/libx/odata/grammar.pegjs +49 -21
  130. package/libx/odata/index.js +2 -2
  131. package/libx/odata/parser.js +1 -1
  132. package/libx/odata/utils.js +2 -2
  133. package/libx/rest/RestAdapter.js +29 -1
  134. package/libx/rest/middleware/auth.js +1 -3
  135. package/libx/rest/middleware/parse.js +1 -0
  136. package/package.json +1 -1
  137. package/server.js +1 -1
  138. package/bin/deploy/to-hana/logger.js +0 -27
  139. package/bin/deploy/to-hana/runCommand.js +0 -113
  140. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
  141. package/libx/_runtime/common/utils/auth.js +0 -16
@@ -13,54 +13,10 @@ const { getPathFromRef, getEntityFromPath } = require('../../common/utils/path')
13
13
  const { addToWhere } = require('../../common/utils/cqn')
14
14
  const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
15
15
  const { addRefToWhereIfNecessary } = require('../../../odata/afterburner')
16
+ const { addAliasToExpression, PARENT_ALIAS, FOREIGN_ALIAS } = require('../../db/utils/generateAliases')
16
17
 
17
- const PARENT_ALIAS = '_parent_'
18
- const PARENT_ALIAS_REGEX = new RegExp('^' + PARENT_ALIAS + '\\d*$')
19
- const FOREIGN_ALIAS = '_foreign_'
20
18
  const OPERATIONS = ['=', '>', '<', '!=', '<>', '>=', '<=', 'like', 'between', 'in', 'not in']
21
19
 
22
- // special case in lambda functions
23
- const _addParentAlias = (where, alias) => {
24
- where.forEach(e => {
25
- if (e.ref && e.ref[0].match(PARENT_ALIAS_REGEX)) {
26
- e.ref[0] = alias
27
- }
28
- })
29
- }
30
-
31
- const _addAliasToElement = (e, alias) => {
32
- if (e.ref) {
33
- return { ref: [alias, ...e.ref] }
34
- }
35
-
36
- if (e.list) {
37
- return { list: e.list.map(arg => _addAliasToElement(arg, alias)) }
38
- }
39
-
40
- if (e.func) {
41
- const args = e.args.map(arg => _addAliasToElement(arg, alias))
42
- return { ...e, args }
43
- }
44
-
45
- if (e.SELECT && e.SELECT.where) {
46
- _addParentAlias(e.SELECT.where, alias)
47
- }
48
-
49
- if (e.xpr) {
50
- return { xpr: e.xpr.map(e1 => _addAliasToElement(e1, alias)) }
51
- }
52
-
53
- return e
54
- }
55
-
56
- const _addAliasToExpression = (expression, alias) => {
57
- if (!alias) {
58
- return expression
59
- }
60
-
61
- return expression.map(e => _addAliasToElement(e, alias))
62
- }
63
-
64
20
  const _elementFromRef = (name, entity) => {
65
21
  if (!entity) return
66
22
 
@@ -112,7 +68,7 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
112
68
  const currentSelect = SELECT.from(`${currentEntityName} as ${tableAlias}`)
113
69
 
114
70
  if (fromClause.ref[i].where) {
115
- currentSelect.where(_addAliasToExpression(fromClause.ref[i].where, tableAlias))
71
+ currentSelect.where(addAliasToExpression(fromClause.ref[i].where, tableAlias))
116
72
  }
117
73
 
118
74
  // REVISIT: Only args in last segment are handled, intermediate ones are ignored
@@ -326,35 +282,15 @@ const _createWindowCQN = (SELECT, model) => {
326
282
  delete SELECT.groupBy
327
283
  }
328
284
 
329
- const _isAny = element => {
330
- return Array.isArray(element.ref) && element.ref.slice(-1)[0].id
285
+ const _unshiftRefsWithNavigation = nav => el => {
286
+ if (el.ref) return { ref: [...nav, ...el.ref] }
287
+ if (el.xpr) return { xpr: el.xpr.map(_unshiftRefsWithNavigation(nav)) }
288
+ return el
331
289
  }
332
290
 
333
- const _isAll = element => {
334
- const last = Array.isArray(element.ref) && element.ref.slice(-1)[0]
335
- return last && last.id && last.where && last.where[0] === 'not' && last.where[1].xpr
336
- }
337
-
338
- // eslint-disable-next-line complexity
339
- const _getWhereExistsSubSelect = (cqn, where, index, model, options) => {
340
- const _unshiftRefsWithNavigation = nav => el => {
341
- if (el.ref) return { ref: [...nav, ...el.ref] }
342
- if (el.xpr) return { xpr: el.xpr.map(_unshiftRefsWithNavigation(nav)) }
343
- return el
344
- }
345
-
346
- if (!options.lambdaIteration) options.lambdaIteration = 1
347
- const outerAlias =
348
- (cqn.SELECT.from.ref && cqn.SELECT.from.as) ||
349
- (cqn.SELECT.from.args && cqn.SELECT.from.args[0].ref && cqn.SELECT.from.args[0].as) ||
350
- PARENT_ALIAS + options.lambdaIteration
351
- const innerAlias = FOREIGN_ALIAS + options.lambdaIteration
352
- cqn.SELECT.from.as = outerAlias
353
- const ref = cqn.SELECT.from.ref || (cqn.SELECT.from.args && cqn.SELECT.from.args[0].ref)
354
- const queryTarget = getEntityFromPath(getPathFromRef(ref), model)
355
-
356
- let nav = where[index].ref.map(el => (el.id ? el.id : el))
357
- const last = where[index].ref.slice(-1)[0]
291
+ const _getWhereExistsSubSelect = (queryTarget, outerAlias, innerAlias, ref, model, options) => {
292
+ let nav = ref.map(el => (el.id ? el.id : el))
293
+ const last = ref.slice(-1)[0]
358
294
  const navName = queryTarget.elements[nav[0]] ? nav[0] : nav[nav.length - 1]
359
295
  const navElement = queryTarget.elements[navName]
360
296
 
@@ -381,17 +317,24 @@ const _getWhereExistsSubSelect = (cqn, where, index, model, options) => {
381
317
  : last.where
382
318
  : undefined
383
319
 
320
+ if (!innerAlias && !outerAlias) {
321
+ innerAlias = navElement.target
322
+ outerAlias = navElement.parent.name
323
+ }
324
+
384
325
  const subSelect = SELECT.from({ ref: [navElement.target], as: innerAlias })
385
326
  if (condition) {
386
327
  if (subSelect.SELECT.from.as) {
387
328
  for (let i = 0; i < condition.length; i++) {
329
+ if (!condition[i].ref) continue
388
330
  if (
389
- condition[i].ref &&
390
331
  condition[i].ref.length > 1 &&
391
332
  condition[i].ref.every(r => typeof r === 'string') &&
392
333
  condition[i].ref[0] === navName
393
334
  ) {
394
335
  condition[i].ref[0] = subSelect.SELECT.from.as
336
+ } else if (condition[i].ref.length === 1) {
337
+ condition[i].ref.unshift(subSelect.SELECT.from.as)
395
338
  }
396
339
  }
397
340
  }
@@ -446,42 +389,9 @@ const _ensureExpandedNestedWhereExists = ({ ref }, aliases) => {
446
389
  return { ref: [acc] }
447
390
  }
448
391
 
449
- const _convertWhereExists = (where, cqn, model, options) => {
450
- for (let i = 0; i < where.length; i++) {
451
- const element = where[i]
452
-
453
- // ensure where exists are fully expanded
454
- if (
455
- (element === 'exists' && where[i + 1].ref && where[i > 1 ? i - 1 : 0] !== 'not') ||
456
- (element === 'not' && where[i + 1] === 'exists' && where[i + 2].ref)
457
- ) {
458
- const offset = element === 'not' ? 2 : 1
459
- const aliases = cqn.SELECT.from.as
460
- ? [cqn.SELECT.from.as]
461
- : cqn.SELECT.from.join
462
- ? cqn.SELECT.from.args.map(arg => arg.as)
463
- : []
464
- where[i + offset] = _ensureExpandedNestedWhereExists(where[i + offset], aliases)
465
- }
466
-
467
- if (element.xpr) {
468
- _convertWhereExists(element.xpr, cqn, model, options) // > recursing into nested {xpr}
469
- } else if (element === 'exists' && _isAny(where[i + 1])) {
470
- where[i + 1] = _getWhereExistsSubSelect(cqn, where, i + 1, model, options)
471
- } else if (element === 'not' && where[i + 1] === 'exists' && _isAll(where[i + 2])) {
472
- where[i + 2] = _getWhereExistsSubSelect(cqn, where, i + 2, model, options)
473
- }
474
- }
475
- }
476
-
477
- const convertWhereExists = (cqn, model, options) => {
478
- if (cqn.SELECT.where) _convertWhereExists(cqn.SELECT.where, cqn, model, options)
479
- }
480
-
481
392
  const _getRefIndex = (where, index) => {
482
393
  if (
483
394
  where[index - 1].ref &&
484
- // REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called there
485
395
  where[index - 1].ref[where[index - 1].ref.length - 1] !== 'InProcessByUser' &&
486
396
  where[index + 1].val !== undefined &&
487
397
  where[index + 1].val !== null &&
@@ -491,7 +401,6 @@ const _getRefIndex = (where, index) => {
491
401
  }
492
402
  if (
493
403
  where[index + 1].ref &&
494
- // REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called there
495
404
  where[index + 1].ref[where[index + 1].ref.length - 1] !== 'InProcessByUser' &&
496
405
  where[index - 1].val !== undefined &&
497
406
  where[index - 1].val !== null &&
@@ -501,6 +410,77 @@ const _getRefIndex = (where, index) => {
501
410
  }
502
411
  }
503
412
 
413
+ const _convertWhereExistsColumn = (column, model, options, queryTarget) => {
414
+ const lambdaIteration = options.lambdaIteration
415
+ if (typeof column === 'object') {
416
+ if (column.SELECT) {
417
+ convertWhereExists(column.SELECT, model, options)
418
+ options.lambdaIteration = lambdaIteration
419
+ } else if (column.where) {
420
+ convertWhereExists(column, model, options, queryTarget)
421
+ options.lambdaIteration = lambdaIteration
422
+ }
423
+ }
424
+ }
425
+
426
+ // eslint-disable-next-line complexity
427
+ const convertWhereExists = (query, model, options, currentTarget) => {
428
+ const { where, columns, expand } = query
429
+ let innerAlias, outerAlias, queryTarget
430
+ const { ref, as } =
431
+ (query.ref && query) ||
432
+ (query.from &&
433
+ ((query.from.ref && query.from) || (query.from.args && query.from.args[0].ref && query.from.args[0]))) ||
434
+ {}
435
+ if (!ref) return
436
+
437
+ if (!options.lambdaIteration) options.lambdaIteration = 1
438
+ const lambdaIteration = options.lambdaIteration
439
+ if (!currentTarget) {
440
+ queryTarget = getEntityFromPath(getPathFromRef(ref), model)
441
+ outerAlias = as || PARENT_ALIAS + lambdaIteration
442
+ innerAlias = FOREIGN_ALIAS + lambdaIteration
443
+ } else {
444
+ const element = currentTarget.elements[ref]
445
+ if (element && element.isAssociation) {
446
+ queryTarget = element._target
447
+ }
448
+ }
449
+
450
+ if (where) {
451
+ for (let i = 0; i < where.length; i++) {
452
+ const element = where[i]
453
+
454
+ // ensure where exists are fully expanded
455
+ if (element === 'exists' && where[i + 1].ref) {
456
+ const ref = query.from || query
457
+ const aliases = ref.as ? [ref.as] : ref.join ? ref.args.map(arg => arg.as) : []
458
+ where[i + 1] = _ensureExpandedNestedWhereExists(where[i + 1], aliases)
459
+ }
460
+
461
+ if (element.xpr) {
462
+ convertWhereExists({ ...query, where: element.xpr }, model, options) // > recursing into nested {xpr}
463
+ } else if (element === 'exists' && where[i + 1].ref) {
464
+ if (query.from) {
465
+ query.from.as = outerAlias
466
+ }
467
+ where[i + 1] = _getWhereExistsSubSelect(queryTarget, outerAlias, innerAlias, where[i + 1].ref, model, options)
468
+ i += 1
469
+ } else if (element.SELECT) {
470
+ convertWhereExists(element.SELECT, model, options)
471
+ }
472
+ options.lambdaIteration = lambdaIteration
473
+ }
474
+ }
475
+
476
+ if (columns) {
477
+ columns.forEach(column => _convertWhereExistsColumn(column, model, options, queryTarget))
478
+ }
479
+ if (expand && expand !== '*') {
480
+ expand.forEach(column => _convertWhereExistsColumn(column, model, options, queryTarget))
481
+ }
482
+ }
483
+
504
484
  const _convertNotEqual = (container, partName = 'where') => {
505
485
  const where = container[partName]
506
486
 
@@ -668,7 +648,7 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
668
648
  if (alias) {
669
649
  SELECT.from.as = alias
670
650
  if (SELECT.where && alias !== prevAlias) {
671
- SELECT.where = _addAliasToExpression(SELECT.where, alias)
651
+ SELECT.where = addAliasToExpression(SELECT.where, alias)
672
652
  }
673
653
  }
674
654
  if (columns) {
@@ -691,7 +671,7 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
691
671
  }
692
672
  // TODO: REVISIT: We need to add alias to subselect in .where, .columns, .from, ... etc
693
673
  if (where) {
694
- if (options.isDraft) {
674
+ if (options._4fiori) {
695
675
  addToWhere({ SELECT }, where)
696
676
  } else {
697
677
  addToWhere({ SELECT }, removeIsActiveEntityRecursively(where))
@@ -703,8 +683,7 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
703
683
  const _convertSelect = (query, model, _options) => {
704
684
  const options = Object.assign(
705
685
  {
706
- isDB: _options.service instanceof cds.DatabaseService,
707
- isDraft: _options.draft,
686
+ _4db: _options.service instanceof cds.DatabaseService,
708
687
  isStreaming: query._streaming
709
688
  },
710
689
  _options
@@ -720,7 +699,7 @@ const _convertSelect = (query, model, _options) => {
720
699
  if (cds.env.features.odata_new_parser) _flattenCQN(query)
721
700
 
722
701
  // lambda functions
723
- convertWhereExists(query, model, options)
702
+ convertWhereExists(query.SELECT, model, options)
724
703
 
725
704
  // add 'or is null' in case of '!='
726
705
  if (query.SELECT._4odata) {
@@ -729,7 +708,7 @@ const _convertSelect = (query, model, _options) => {
729
708
  }
730
709
 
731
710
  _convertPathExpression(query.SELECT, model, options)
732
- rewriteAsterisks(query, model, options.isDB, options.isDraft)
711
+ rewriteAsterisks(query, model, options)
733
712
  if (query.SELECT.where && _isCountNavigation(query.SELECT.where)) {
734
713
  _convertCountNavigation(query.SELECT, model)
735
714
  }
@@ -780,12 +759,6 @@ const _convertInsert = (query, model, options) => {
780
759
 
781
760
  const resolvedView = resolveView(insert, model, cds.db)
782
761
 
783
- if (query.INSERT.into.ref && query.INSERT.into.ref.length > 1) {
784
- const copyFrom = [...query.INSERT.into.ref]
785
- copyFrom.pop()
786
- resolvedView._validationQuery = _convertSelect(SELECT.from({ ref: copyFrom }).columns([1]), model, options)
787
- }
788
-
789
762
  return resolvedView
790
763
  }
791
764
 
@@ -831,7 +804,7 @@ const _convertDelete = (query, model, options) => {
831
804
 
832
805
  if (alias) deleet.DELETE.from = { ref: [target], as: alias }
833
806
  if (where) deleet.where(where)
834
- if (query.DELETE.where) deleet.where(_addAliasToExpression(query.DELETE.where, alias))
807
+ if (query.DELETE.where) deleet.where(addAliasToExpression(query.DELETE.where, alias))
835
808
 
836
809
  const targetEntity = model.definitions[target]
837
810
  if (!targetEntity) return deleet
@@ -868,7 +841,7 @@ const _convertUpdate = (query, model, options) => {
868
841
 
869
842
  if (alias) update.UPDATE.entity = { ref: [target], as: alias }
870
843
  if (where) update.where(where)
871
- if (query.UPDATE.where) update.where(_addAliasToExpression(query.UPDATE.where, alias))
844
+ if (query.UPDATE.where) update.where(addAliasToExpression(query.UPDATE.where, alias))
872
845
 
873
846
  const targetEntity = model.definitions[target]
874
847
  if (!targetEntity) return update
@@ -6,17 +6,27 @@ const getEtagElement = entity => {
6
6
  return Object.values(entity.elements).find(element => element['@odata.etag'])
7
7
  }
8
8
 
9
- const _getUps = (entity, model) => {
9
+ const getComp2oneParents = (entity, model) => {
10
+ if (!entity) return []
11
+ if (entity.own('__comp2oneParents')) return entity.__comp2oneParents
12
+ const invalidationFn = element => !(element.is2one && element._isCompositionEffective)
13
+ const comp2oneParents = _getUps(entity, model, invalidationFn)
14
+ return entity.set('__comp2oneParents', comp2oneParents)
15
+ }
16
+
17
+ const _getUps = (entity, model, invalidationFn) => {
18
+ if (entity.own('__parents')) return entity.__parents
10
19
  const ups = []
11
20
  for (const def of Object.values(model.definitions)) {
12
21
  if (def.kind !== 'entity' || !def.associations) continue
13
22
  for (const element of Object.values(def.associations)) {
14
23
  if (element.target !== entity.name || element._isBacklink) continue
15
24
  if (element.name === 'SiblingEntity') continue
25
+ if (invalidationFn && invalidationFn(element)) continue
16
26
  ups.push(element)
17
27
  }
18
28
  }
19
- return ups
29
+ return entity.set('__parents', ups)
20
30
  }
21
31
 
22
32
  const _ifDataSubject = (entity, role) => {
@@ -189,5 +199,6 @@ module.exports = {
189
199
  getElementDeep,
190
200
  isRootEntity,
191
201
  getDataSubject,
192
- alias2ref
202
+ alias2ref,
203
+ getComp2oneParents
193
204
  }
@@ -183,7 +183,24 @@ const _resolvedKeys = (foreignKeys, targetKeys, fillChild) => {
183
183
 
184
184
  for (let i = 0; i < foreignKeys.length; i++) {
185
185
  const fk = foreignKeys[i]
186
- const tk = targetKeys[i]
186
+ let tk
187
+
188
+ /*
189
+ * REVISIT: poor man's look-up of target key with fallback to targetKeys[i]
190
+ * Look at elements, then try to find it in query and resolve recursively until you have the full path.
191
+ * Once you have the full path, you can find it in the target entity.
192
+ * NOTE: There can be projections upon projections and renamings in every projection. -> not yet covered!!!
193
+ */
194
+ const tkCol =
195
+ targetKeys[i].parent.query &&
196
+ targetKeys[i].parent.query.SELECT.columns &&
197
+ targetKeys[i].parent.query.SELECT.columns.find(
198
+ c => c.ref && `${fk['@odata.foreignKey4']}_${c.ref.join('_')}` === fk.name
199
+ )
200
+ tk = tkCol && targetKeys.find(tk => tk.name === (tkCol.as ? tkCol.as : tkCol.ref.join('_')))
201
+ // with composition of aspects, the lookup fails -> we need this final fallback
202
+ if (!tk) tk = targetKeys[i]
203
+
187
204
  if (fk._isStructured) {
188
205
  i += _resolve4struct(targetKeys, fk, foreignKeyPropagations, fillChild, false, i)
189
206
  } else if (tk._isStructured) {
@@ -1,4 +1,5 @@
1
1
  const { where2obj } = require('./cqn')
2
+ const { deepCopyArray } = require('./copy')
2
3
 
3
4
  function _getOnCondElements(onCond, onCondElements = []) {
4
5
  const andIndex = onCond.indexOf('and')
@@ -17,7 +18,7 @@ function _getOnCondElements(onCond, onCondElements = []) {
17
18
  function _modifyWhereWithNavigations(where, newWhere, targetKeyElement, keyName) {
18
19
  if (where) {
19
20
  // copy where else query will be modified
20
- const whereCopy = JSON.parse(JSON.stringify(where))
21
+ const whereCopy = deepCopyArray(where)
21
22
  if (newWhere.length > 0) newWhere.push('and')
22
23
  newWhere.push(...whereCopy)
23
24
  }
@@ -24,7 +24,7 @@ const getEntityFromPath = (path, model) => {
24
24
  while (segments.length) {
25
25
  const segment = segments.shift()
26
26
  current = current.elements[segment.id || segment]
27
- if (current.target) current = model.definitions[current.target]
27
+ if (current && current.target) current = model.definitions[current.target]
28
28
  }
29
29
  return current
30
30
  }
@@ -152,9 +152,15 @@ const _newColumns = (columns = [], transition, service, withAlias = false) => {
152
152
  const newColumns = []
153
153
 
154
154
  columns.forEach(column => {
155
+ let newColumn
156
+ if (column.func) {
157
+ newColumn = { ...column }
158
+ newColumn.args = _newColumns(column.args, transition, service, withAlias)
159
+ newColumns.push(newColumn)
160
+ return newColumns
161
+ }
155
162
  const mapped = column.ref && transition.mapping.get(column.ref[0])
156
163
 
157
- let newColumn
158
164
  if (mapped && mapped.ref) {
159
165
  newColumn = { ...column }
160
166
 
@@ -238,7 +244,7 @@ const _newEntries = (entries = [], transition, service) =>
238
244
  const _newWhere = (where = [], transition, tableName, alias, isSubselect = false) => {
239
245
  const newWhere = where.map(whereElement => {
240
246
  const newWhereElement = { ...whereElement }
241
- if (!whereElement.ref && !whereElement.SELECT) return whereElement
247
+ if (!whereElement.ref && !whereElement.SELECT && !whereElement.func) return whereElement
242
248
  if (whereElement.SELECT && whereElement.SELECT.where) {
243
249
  newWhereElement.SELECT.where = _newWhere(whereElement.SELECT.where, transition, tableName, alias, true)
244
250
  return newWhereElement
@@ -246,6 +252,9 @@ const _newWhere = (where = [], transition, tableName, alias, isSubselect = false
246
252
  if (newWhereElement.ref) {
247
253
  _newWhereRef(newWhereElement, transition, alias, tableName, isSubselect)
248
254
  return newWhereElement
255
+ } else if (newWhereElement.func) {
256
+ newWhereElement.args = _newWhere(newWhereElement.args, transition, tableName, alias)
257
+ return newWhereElement
249
258
  } else {
250
259
  return whereElement
251
260
  }
@@ -337,8 +346,7 @@ const _newSelect = (query, transitions, service) => {
337
346
  }
338
347
  if (!newSelect.columns && targetTransition.mapping.size) newSelect.columns = _initialColumns(targetTransition)
339
348
  if (newSelect.columns) {
340
- const isDB = service instanceof cds.DatabaseService
341
- rewriteAsterisks({ SELECT: query.SELECT }, service.model, isDB)
349
+ rewriteAsterisks({ SELECT: query.SELECT }, service.model, { _4db: service instanceof cds.DatabaseService })
342
350
  newSelect.columns = _newColumns(newSelect.columns, targetTransition, service, service.kind !== 'app-service')
343
351
  }
344
352
  if (newSelect.having) newSelect.having = _newColumns(newSelect.having, targetTransition)
@@ -30,10 +30,11 @@ const _cqlDraftColumns = target => {
30
30
  ]
31
31
  }
32
32
 
33
- const _expandColumn = (column, target, db) => {
33
+ const _expandColumn = (column, target, _4db) => {
34
34
  if (!(column.ref && column.expand)) return
35
35
  const nextTarget = getNavigationIfStruct(target, column.ref)
36
- if (nextTarget && nextTarget._target && nextTarget._target.elements) _rewriteAsterisks(column, nextTarget._target, db)
36
+ if (nextTarget && nextTarget._target && nextTarget._target.elements)
37
+ _rewriteAsterisks(column, nextTarget._target, _4db)
37
38
  return column
38
39
  }
39
40
 
@@ -46,32 +47,33 @@ const rewriteExpandAsterisk = (columns, target) => {
46
47
  const { expand } = columns.splice(expandAllColIdx, 1)[0]
47
48
  for (const elName in target.elements) {
48
49
  if (target.elements[elName]._target && !columns.find(col => col.expand && col.ref && col.ref[0] === elName)) {
50
+ if (elName === 'SiblingEntity') continue
49
51
  columns.push({ ref: [elName], expand: [...expand] })
50
52
  }
51
53
  }
52
54
  }
53
55
  }
54
56
 
55
- const _rewriteAsterisk = (columns, target, db, isRoot) => {
57
+ const _rewriteAsterisk = (columns, target, _4db, isRoot) => {
56
58
  const asteriskColumnIndex = columns.findIndex(col => isAsteriskColumn(col))
57
59
  if (asteriskColumnIndex > -1) {
58
60
  columns.splice(
59
61
  asteriskColumnIndex,
60
62
  1,
61
- ...getColumns(target, { db })
63
+ ...getColumns(target, { _4db })
62
64
  .map(c => ({ ref: [c.name] }))
63
65
  .filter(c => !columns.find(_isDuplicate(c)) && (isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID'))
64
66
  )
65
67
  }
66
68
  }
67
69
 
68
- const _rewriteAsterisks = (cqn, target, db, isRoot) => {
70
+ const _rewriteAsterisks = (cqn, target, _4db, isRoot) => {
69
71
  if (cqn.expand === '*') cqn.expand = ['*']
70
72
  const columns = cqn.expand || cqn.columns
71
- _rewriteAsterisk(columns, target, db, isRoot)
73
+ _rewriteAsterisk(columns, target, _4db, isRoot)
72
74
  rewriteExpandAsterisk(columns, target)
73
75
  for (const column of columns) {
74
- _expandColumn(column, target, db)
76
+ _expandColumn(column, target, _4db)
75
77
  }
76
78
  return columns
77
79
  }
@@ -83,9 +85,17 @@ const _targetOfQueryIfNotDraft = (query, model) => {
83
85
  return target
84
86
  }
85
87
 
86
- const rewriteAsterisks = (query, model, db = false, isDraft = false, onlyKeys = false) => {
88
+ const rewriteAsterisks = (query, model, options) => {
89
+ /*
90
+ * REVISIT:
91
+ * - _4db: called on db level
92
+ * - _4fiori: cqn2cqn4sql called in a fiori handler
93
+ * this is extremely obfuscated!
94
+ */
95
+ const { _4db, _4fiori } = options
96
+
87
97
  if (!query.SELECT.columns || !query.SELECT.columns.length) {
88
- if (isDraft || db) {
98
+ if (_4db || _4fiori) {
89
99
  if (
90
100
  query.SELECT.from.SET &&
91
101
  query.SELECT.from.SET.args[0] &&
@@ -101,7 +111,7 @@ const rewriteAsterisks = (query, model, db = false, isDraft = false, onlyKeys =
101
111
  for (const arg of query.SELECT.from.args) {
102
112
  const _targetName = arg.ref[0].id || arg.ref[0]
103
113
  const _target = model.definitions[ensureNoDraftsSuffix(_targetName)]
104
- const columns = getColumns(_target, { db, onlyKeys })
114
+ const columns = getColumns(_target, { _4db })
105
115
  .filter(
106
116
  c =>
107
117
  !query.SELECT.columns.some(
@@ -116,16 +126,20 @@ const rewriteAsterisks = (query, model, db = false, isDraft = false, onlyKeys =
116
126
  } else {
117
127
  const target = _targetOfQueryIfNotDraft(query, model)
118
128
  if (!target) return
119
- query.SELECT.columns = getColumns(target, { db, onlyKeys }).map(col => ({ ref: [col.name] }))
120
- if (db && target._isDraftEnabled) query.SELECT.columns.push(..._cqlDraftColumns(target))
129
+
130
+ query.SELECT.columns = getColumns(target, { _4db }).map(col => ({ ref: [col.name] }))
131
+ if (_4db && target._isDraftEnabled) query.SELECT.columns.push(..._cqlDraftColumns(target))
121
132
  }
122
133
  }
134
+
123
135
  return
124
136
  }
137
+
125
138
  const target = _targetOfQueryIfNotDraft(query, model)
126
139
  if (!target) return
140
+
127
141
  // REVISIT: Also support JOINs/SETs here
128
- query.SELECT.columns = _rewriteAsterisks(query.SELECT, target, db, true)
142
+ query.SELECT.columns = _rewriteAsterisks(query.SELECT, target, _4db, true)
129
143
  }
130
144
 
131
145
  module.exports = {
@@ -18,8 +18,8 @@ const _search2cqn4sql = (query, model, options = {}) => {
18
18
 
19
19
  // Call custom (optimized search to cqn for sql implementation) that tries
20
20
  // to optimize the search behavior for a specific database service.
21
- // Note: $search query option combined with $filter is not currently optimized
22
- if (typeof search2cqn4sql === 'function' && !query.SELECT.where) {
21
+ // REVISIT: $search query option combined with $count is not currently optimized
22
+ if (typeof search2cqn4sql === 'function' && !query.SELECT.count) {
23
23
  const search2cqnOptions = { columns, locale: options.locale }
24
24
  return search2cqn4sql(query, entity, search2cqnOptions)
25
25
  }
@@ -30,9 +30,14 @@ const _search2cqn4sql = (query, model, options = {}) => {
30
30
  query._aggregated || /* if new parser */ query.SELECT.groupBy ? query.having(expression) : query.where(expression)
31
31
  }
32
32
 
33
- // convert $search system query option to WHERE/HAVING clause using
34
- // the operator LIKE or CONTAINS
35
- module.exports = (query, model, options) => {
36
- if (query.SELECT.from.SET) return query.SELECT.from.SET.args.forEach(arg => _search2cqn4sql(arg, model, options))
33
+ const search2cqn4sql = (query, model, options) => {
34
+ if (query.SELECT.from.SET) {
35
+ return query.SELECT.from.SET.args.forEach(arg => _search2cqn4sql(arg, model, options))
36
+ }
37
+
37
38
  return _search2cqn4sql(query, model, options)
38
39
  }
40
+
41
+ // convert $search system query option to WHERE/HAVING clause using
42
+ // the operator LIKE or CONTAINS
43
+ module.exports = search2cqn4sql
@@ -1,11 +1,28 @@
1
- const vcapApplication = process.env.VCAP_APPLICATION && JSON.parse(process.env.VCAP_APPLICATION)
2
-
3
- module.exports = {
4
- appName: vcapApplication && vcapApplication.application_name,
5
- appID: vcapApplication && vcapApplication.application_id,
6
- appURL:
7
- vcapApplication &&
8
- vcapApplication.application_uris &&
9
- vcapApplication.application_uris[0] &&
10
- `https://${vcapApplication.application_uris[0].replace(/^https?:\/\//, '')}`
1
+ const cds = require('../../../../libx/_runtime/cds')
2
+
3
+ const getAppMetadata = () => {
4
+ const appMetadata = cds.env.app
5
+
6
+ if (appMetadata) {
7
+ return {
8
+ appID: appMetadata.id,
9
+ appName: appMetadata.name,
10
+ appURL: appMetadata.url
11
+ }
12
+ }
13
+
14
+ // fallback: if the app metadata is undefined, then extract the metadata from the underlying environment (CF/Kyma/...)
15
+ const vcapApplication = process.env.VCAP_APPLICATION && JSON.parse(process.env.VCAP_APPLICATION)
16
+
17
+ return {
18
+ appID: vcapApplication && vcapApplication.application_id,
19
+ appName: vcapApplication && vcapApplication.application_name,
20
+ appURL:
21
+ vcapApplication &&
22
+ vcapApplication.application_uris &&
23
+ vcapApplication.application_uris[0] &&
24
+ `https://${vcapApplication.application_uris[0].replace(/^https?:\/\//, '')}`
25
+ }
11
26
  }
27
+
28
+ module.exports = getAppMetadata()