@sap/cds 5.7.3 → 5.8.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 (151) hide show
  1. package/CHANGELOG.md +111 -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/minify.js +1 -1
  13. package/lib/compile/resolve.js +1 -1
  14. package/lib/compile/to/srvinfo.js +1 -1
  15. package/lib/core/classes.js +21 -1
  16. package/lib/env/index.js +3 -2
  17. package/lib/env/requires.js +4 -0
  18. package/lib/i18n/localize.js +5 -8
  19. package/lib/index.js +1 -0
  20. package/lib/log/errors.js +1 -1
  21. package/lib/ql/SELECT.js +2 -2
  22. package/lib/req/cds-context.js +1 -1
  23. package/lib/req/context.js +1 -1
  24. package/lib/serve/Transaction.js +9 -5
  25. package/lib/serve/index.js +13 -21
  26. package/lib/utils/tests.js +90 -66
  27. package/libx/_runtime/audit/generic/personal/modification.js +0 -8
  28. package/libx/_runtime/auth/index.js +7 -6
  29. package/libx/_runtime/auth/strategies/dwc.js +43 -0
  30. package/libx/_runtime/auth/utils.js +24 -0
  31. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -38
  32. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  37. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +13 -7
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
  43. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
  44. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
  47. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +5 -8
  48. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
  49. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
  50. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
  51. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
  52. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
  53. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +2 -2
  54. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ErrorJsonSerializer.js +2 -0
  55. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
  56. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
  57. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  58. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +18 -5
  59. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
  60. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +80 -21
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +47 -10
  63. package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
  64. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
  65. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
  66. package/libx/_runtime/cds-services/services/Service.js +1 -1
  67. package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
  68. package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
  69. package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
  70. package/libx/_runtime/common/aspects/Association.js +16 -0
  71. package/libx/_runtime/common/composition/data.js +28 -37
  72. package/libx/_runtime/common/composition/delete.js +107 -58
  73. package/libx/_runtime/common/composition/index.js +3 -3
  74. package/libx/_runtime/common/composition/insert.js +14 -27
  75. package/libx/_runtime/common/composition/update.js +39 -34
  76. package/libx/_runtime/common/error/frontend.js +19 -5
  77. package/libx/_runtime/common/generic/auth.js +20 -85
  78. package/libx/_runtime/common/generic/crud.js +22 -1
  79. package/libx/_runtime/common/i18n/messages.properties +2 -1
  80. package/libx/_runtime/common/utils/cqn.js +2 -6
  81. package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
  82. package/libx/_runtime/common/utils/csn.js +15 -4
  83. package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -1
  84. package/libx/_runtime/common/utils/keys.js +2 -1
  85. package/libx/_runtime/common/utils/path.js +1 -1
  86. package/libx/_runtime/common/utils/resolveView.js +12 -4
  87. package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
  88. package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
  89. package/libx/_runtime/common/utils/structured.js +11 -5
  90. package/libx/_runtime/common/utils/vcap.js +27 -10
  91. package/libx/_runtime/db/data-conversion/post-processing.js +42 -35
  92. package/libx/_runtime/db/expand/expand-v2.js +21 -12
  93. package/libx/_runtime/db/expand/expandCQNToJoin.js +57 -29
  94. package/libx/_runtime/db/expand/index.js +3 -0
  95. package/libx/_runtime/db/generic/create.js +0 -10
  96. package/libx/_runtime/db/generic/index.js +3 -0
  97. package/libx/_runtime/db/generic/read.js +2 -24
  98. package/libx/_runtime/db/generic/rewrite.js +1 -3
  99. package/libx/_runtime/db/generic/update.js +1 -1
  100. package/libx/_runtime/db/query/delete.js +10 -4
  101. package/libx/_runtime/db/query/insert.js +3 -4
  102. package/libx/_runtime/db/query/read.js +4 -1
  103. package/libx/_runtime/db/query/update.js +5 -5
  104. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
  105. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
  106. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -3
  107. package/libx/_runtime/db/sql-builder/index.js +3 -0
  108. package/libx/_runtime/db/utils/columns.js +5 -2
  109. package/libx/_runtime/db/utils/deep.js +6 -8
  110. package/libx/_runtime/db/utils/generateAliases.js +56 -6
  111. package/libx/_runtime/fiori/generic/before.js +73 -49
  112. package/libx/_runtime/fiori/generic/edit.js +14 -18
  113. package/libx/_runtime/fiori/generic/patch.js +8 -11
  114. package/libx/_runtime/fiori/generic/read.js +22 -20
  115. package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
  116. package/libx/_runtime/fiori/utils/handler.js +1 -11
  117. package/libx/_runtime/hana/Service.js +1 -1
  118. package/libx/_runtime/hana/conversion.js +12 -1
  119. package/libx/_runtime/hana/execute.js +31 -16
  120. package/libx/_runtime/hana/localized.js +1 -1
  121. package/libx/_runtime/hana/search.js +3 -3
  122. package/libx/_runtime/hana/search2cqn4sql.js +23 -25
  123. package/libx/_runtime/hana/searchToContains.js +1 -1
  124. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
  125. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
  126. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  127. package/libx/_runtime/messaging/file-based.js +3 -1
  128. package/libx/_runtime/messaging/service.js +16 -7
  129. package/libx/_runtime/remote/utils/client.js +37 -20
  130. package/libx/_runtime/remote/utils/data.js +53 -12
  131. package/libx/_runtime/sqlite/Service.js +1 -1
  132. package/libx/_runtime/sqlite/conversion.js +10 -0
  133. package/libx/_runtime/sqlite/localized.js +1 -1
  134. package/libx/_runtime/types/api.js +2 -2
  135. package/libx/gql/resolvers/crud/update.js +8 -5
  136. package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
  137. package/libx/odata/afterburner.js +29 -6
  138. package/libx/odata/cqn2odata.js +9 -0
  139. package/libx/odata/grammar.pegjs +50 -22
  140. package/libx/odata/index.js +2 -2
  141. package/libx/odata/parser.js +1 -1
  142. package/libx/odata/utils.js +2 -2
  143. package/libx/rest/RestAdapter.js +29 -1
  144. package/libx/rest/middleware/auth.js +1 -3
  145. package/libx/rest/middleware/parse.js +1 -0
  146. package/package.json +1 -1
  147. package/server.js +1 -1
  148. package/bin/deploy/to-hana/logger.js +0 -27
  149. package/bin/deploy/to-hana/runCommand.js +0 -113
  150. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
  151. package/libx/_runtime/common/utils/auth.js +0 -16
@@ -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()
@@ -1,6 +1,6 @@
1
1
  const cds = require('../../cds')
2
2
 
3
- const { ensureUnlocalized } = require('../../common/utils/draft')
3
+ const { ensureUnlocalized, ensureNoDraftsSuffix } = require('../../common/utils/draft')
4
4
 
5
5
  /**
6
6
  * Check if the value is a function or reference to private function.
@@ -48,49 +48,50 @@ const _getEntityName = (csn, from) => {
48
48
 
49
49
  const _refs = (refs, as) => {
50
50
  const arr = []
51
- const hasOwnProperty = Object.prototype.hasOwnProperty
52
-
53
51
  for (const element of refs) {
54
52
  // multiple join are nested, so we need to find all the table names in there as well
55
- if (hasOwnProperty.call(element, 'join')) {
53
+ if (Object.prototype.hasOwnProperty.call(element, 'join')) {
56
54
  arr.push(..._extractRefs(element))
57
55
  // Likely a union
58
- } else if (hasOwnProperty.call(element, 'SELECT')) {
59
- arr.push(..._extractRefs(element.SELECT.from, as))
56
+ } else if (Object.prototype.hasOwnProperty.call(element, 'SELECT')) {
57
+ arr.push(..._extractRefs(element.SELECT.from, element.as))
60
58
  } else {
61
- arr.push(element)
59
+ arr.push(..._extractRefs(element, as))
62
60
  }
63
61
  }
64
62
 
65
63
  return arr
66
64
  }
67
65
 
66
+ const _getActiveFromUnion = refs => {
67
+ if (refs.length !== 2) return
68
+ const [maybeDraft, maybeActive] = refs
69
+ if (ensureNoDraftsSuffix(maybeDraft.ref[0]) === maybeActive.ref[0]) return maybeActive
70
+ if (ensureNoDraftsSuffix(maybeActive.ref[0]) === maybeDraft.ref[0]) return maybeDraft
71
+ }
72
+
68
73
  const _extractRefs = (from, as) => {
69
74
  if (from.SELECT) {
70
- return _extractRefs(from.SELECT.from, from.SELECT.as)
75
+ return _extractRefs(from.SELECT.from, as || from.SELECT.as)
71
76
  }
72
-
73
- const hasOwnProperty = Object.prototype.hasOwnProperty
74
-
75
- if (hasOwnProperty.call(from, 'join')) {
77
+ if (Object.prototype.hasOwnProperty.call(from, 'join')) {
76
78
  // cqn with join in from
77
79
  return _refs(from.args)
78
80
  }
79
-
80
- if (hasOwnProperty.call(from, 'SET')) {
81
- return _refs(from.SET.args, from.SET.as || from.as)
81
+ if (Object.prototype.hasOwnProperty.call(from, 'SET')) {
82
+ let refs = _refs(from.SET.args).filter(a => !a.as || a.as !== 'filterAdmin')
83
+ refs = _getActiveFromUnion(refs) ? [_getActiveFromUnion(refs)] : refs
84
+ if (as) return refs.map(({ ref }) => ({ as, ref }))
85
+ return refs
82
86
  }
83
-
84
- const ref = { ref: from.ref, as: from.as }
85
-
86
- if (as) {
87
- ref.as = as
88
- }
89
-
87
+ if (!from.ref) return []
88
+ const ref = { ref: [...from.ref] }
89
+ if (as || from.as) ref.as = as || from.as
90
90
  return [ref]
91
91
  }
92
92
 
93
- const _addMapperFunction = (elements, toService, key, type, from, includeAlias) => {
93
+ // REVISIT: check why we need includeAlias for some draft cases (check AFC tests)
94
+ const _addMapperFunction = (elements, toService, key, type, alias, includeAlias) => {
94
95
  if (!toService.has(type)) {
95
96
  return
96
97
  }
@@ -99,9 +100,8 @@ const _addMapperFunction = (elements, toService, key, type, from, includeAlias)
99
100
 
100
101
  // ambiguous cases will lead to SQL syntax errors anyway, so no need for a check
101
102
  elements.set(key, convertFunction)
102
-
103
- if (includeAlias) {
104
- elements.set(`${from.as}_${key}`, convertFunction)
103
+ if (includeAlias && alias) {
104
+ elements.set(`${alias}_${key}`, convertFunction)
105
105
  }
106
106
  }
107
107
 
@@ -109,13 +109,13 @@ const _filterUnique = (value, index, arr) => {
109
109
  return arr.indexOf(value) === index
110
110
  }
111
111
 
112
- const _addMapperFunction4struct = (elements, toService, parent, struct, from, includeAlias) => {
112
+ const _addMapperFunction4struct = (elements, toService, parent, struct, alias, includeAlias) => {
113
113
  for (const k in struct.elements) {
114
114
  const current = struct.elements[k]
115
115
  if (current._isStructured) {
116
- _addMapperFunction4struct(elements, toService, `${parent}_${k}`, current, from, includeAlias)
116
+ _addMapperFunction4struct(elements, toService, `${parent}_${k}`, current, alias, includeAlias)
117
117
  } else {
118
- _addMapperFunction(elements, toService, `${parent}_${k}`, current.type, from, includeAlias)
118
+ _addMapperFunction(elements, toService, `${parent}_${k}`, current.type, alias, includeAlias)
119
119
  }
120
120
  }
121
121
  }
@@ -126,7 +126,6 @@ const _addMapperFunction4struct = (elements, toService, parent, struct, from, in
126
126
  * @param {Map} toService - Mapping instructions for data conversions based on CDS data types
127
127
  * @param {object} csn - Reflected CSN
128
128
  * @param {object} cqn - CQN that is used to query the DB.
129
- * @param {boolean} [includeAlias] - Include mapping for aliases. Defaults to false.
130
129
  * @returns {Map<any, any>}
131
130
  * @private
132
131
  */
@@ -142,13 +141,16 @@ const _getElementCombinations = (toService, csn, cqn, includeAlias = false) => {
142
141
  }
143
142
 
144
143
  const entity = csn.definitions[ensureUnlocalized(entityName)]
145
-
146
144
  for (const key in entity.elements) {
147
145
  const element = entity.elements[key]
148
146
  if (element._isStructured) {
149
- _addMapperFunction4struct(elements, toService, key, element, from, includeAlias)
147
+ _addMapperFunction4struct(elements, toService, key, element, from.as, includeAlias)
150
148
  } else {
151
- _addMapperFunction(elements, toService, key, element.type, from, includeAlias)
149
+ if ('SET' in cqn.SELECT.from) {
150
+ _addMapperFunction(elements, toService, key, element.type, cqn.SELECT.from.as, includeAlias)
151
+ } else {
152
+ _addMapperFunction(elements, toService, key, element.type, from.as, includeAlias)
153
+ }
152
154
  }
153
155
  }
154
156
  }
@@ -182,15 +184,17 @@ const _getMapperForListedElements = (toService, csn, cqn) => {
182
184
 
183
185
  for (const element of cqn.SELECT.columns) {
184
186
  if (element.ref) {
185
- const identifier = element.ref[element.ref.length - 1]
187
+ const identifier = element.ref.length === 1 ? element.ref[0] : undefined
186
188
  const name = element.as ? element.as : identifier
187
189
 
188
190
  if (element.cast) {
189
191
  mapper.set(name, _getCastFunction(element.cast))
190
192
  } else if (elements.has(name)) {
191
193
  mapper.set(name, elements.get(name))
192
- } else if (elements.has(identifier) && !cqn.SELECT.from.args) {
194
+ } else if (elements.has(identifier)) {
193
195
  mapper.set(name, elements.get(identifier))
196
+ } else if (elements.has(element.ref.join('_'))) {
197
+ mapper.set(name || element.ref[element.ref.length - 1], elements.get(element.ref.join('_')))
194
198
  }
195
199
  } else if (element.as && element.cast) {
196
200
  mapper.set(element.as, _getCastFunction(element.cast))
@@ -514,6 +518,9 @@ const getPropertyMapper = (csn, cqn) => {
514
518
  return new Map()
515
519
  }
516
520
 
521
+ /*
522
+ * this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
523
+ */
517
524
  module.exports = {
518
525
  getPropertyMapper,
519
526
  getPostProcessMapper,
@@ -11,9 +11,10 @@ const _removeParentKeysFromRow = (row, prefix, keys) => {
11
11
  }
12
12
  }
13
13
 
14
- const _autoExpandNavsAndAttachToResult = async (entity, previousResult, depth) => {
14
+ const _autoExpandNavsAndAttachToResult = async (entity, previousResult, depth, options) => {
15
15
  for (const nav in entity._associations) {
16
16
  const navigation = entity._associations[nav]
17
+ if (options.onlyCompositions && navigation._isAssociationEffective) continue
17
18
 
18
19
  // do not expand backlinks
19
20
  if (navigation._isBacklink) continue
@@ -26,7 +27,9 @@ const _autoExpandNavsAndAttachToResult = async (entity, previousResult, depth) =
26
27
  .on(entity._relations[navigation.name].join(childAlias, parentAlias))
27
28
 
28
29
  // set alias for expanded columns already
29
- const childColumns = getColumns(navigation._target).map(c => ({ ref: [childAlias, c.name] }))
30
+ const childColumns = getColumns(navigation._target, { _4db: true, onlyKeys: options.onlyKeys }).map(c => ({
31
+ ref: [childAlias, c.name]
32
+ }))
30
33
  const parentKeys = Object.keys(entity.keys).filter(k => !entity.keys[k].isAssociation)
31
34
  // mark parent key with prefix in alias
32
35
  const parentKeysWithAlias = parentKeys.map(pk => ({ ref: [parentAlias, pk], as: `$$pk_${pk}` }))
@@ -69,16 +72,17 @@ const _autoExpandNavsAndAttachToResult = async (entity, previousResult, depth) =
69
72
 
70
73
  // expand next level if needed
71
74
  if (depth - 1 !== 0 && result.length && navigation._target._associations) {
72
- await _autoExpandNavsAndAttachToResult(navigation._target, result, depth - 1)
75
+ await _autoExpandNavsAndAttachToResult(navigation._target, result, depth - 1, options)
73
76
  }
74
77
  }
75
78
 
76
79
  return previousResult
77
80
  }
78
81
 
79
- const _foreignKeysOfTopLevelNavs = entity => {
82
+ const _foreignKeysOfTopLevelNavs = (entity, options) => {
80
83
  const requiredFks = new Set()
81
84
  for (const nav in entity._associations) {
85
+ if (options.onlyCompositions && entity._associations[nav]._isAssociationEffective) continue
82
86
  const onCond = entity._relations[nav].join('child', 'parent')
83
87
  for (const ele of onCond) {
84
88
  if (ele.ref && ele.ref[0] === 'parent') {
@@ -89,6 +93,15 @@ const _foreignKeysOfTopLevelNavs = entity => {
89
93
  return [...requiredFks]
90
94
  }
91
95
 
96
+ const _addForeignKeys = (columns, entity, options) => {
97
+ const fks = _foreignKeysOfTopLevelNavs(entity, options)
98
+ fks.forEach(fk => {
99
+ if (!columns.some(c => c.ref[0] === fk)) {
100
+ columns.push({ ref: [fk] })
101
+ }
102
+ })
103
+ }
104
+
92
105
  /**
93
106
  * 1. Creates flattened SQL statements for each expand layer
94
107
  * 2. Mixes in foreign keys if needed
@@ -98,18 +111,15 @@ const _foreignKeysOfTopLevelNavs = entity => {
98
111
  * @returns object
99
112
  */
100
113
  const expandV2 = async (model, dbc, query, user, locale, txTimestamp, executeSelectCQN) => {
114
+ const expandColumn = query.SELECT.columns.find(c => c.expand && typeof c.expand === 'string')
115
+ const options = Object.assign({ onlyKeys: false, onlyCompositions: false }, expandColumn._options)
101
116
  // remove expand columns from query without modifying
102
117
  const topLevelSelect = query.clone().columns(query.SELECT.columns.filter(c => !c.expand))
103
118
 
104
119
  const entity = model.definitions[topLevelSelect.SELECT.from.ref[0]]
105
120
 
106
121
  // ensure foreign keys are selected if needed
107
- const fks = _foreignKeysOfTopLevelNavs(entity)
108
- fks.forEach(fk => {
109
- if (!topLevelSelect.SELECT.columns.some(c => c.ref[0] === fk)) {
110
- topLevelSelect.SELECT.columns.push({ ref: [fk] })
111
- }
112
- })
122
+ _addForeignKeys(topLevelSelect.SELECT.columns, entity, options)
113
123
 
114
124
  const result = await executeSelectCQN(model, dbc, topLevelSelect, user, locale, txTimestamp)
115
125
 
@@ -119,9 +129,8 @@ const expandV2 = async (model, dbc, query, user, locale, txTimestamp, executeSel
119
129
 
120
130
  // _associations contains compositions and associations
121
131
  if (entity._associations) {
122
- const expandColumn = query.SELECT.columns.find(c => c.expand && typeof c.expand === 'string')
123
132
  const depth = expandColumn.expand === '**' ? -1 : Number(expandColumn.expand.replace('*', ''))
124
- await _autoExpandNavsAndAttachToResult(entity, Array.isArray(result) ? result : [result], depth)
133
+ await _autoExpandNavsAndAttachToResult(entity, Array.isArray(result) ? result : [result], depth, options)
125
134
  }
126
135
 
127
136
  return result
@@ -2,10 +2,15 @@ const cds = require('../../cds')
2
2
 
3
3
  const { getAllKeys } = require('../../cds-services/adapter/odata-v4/odata-to-cqn/utils')
4
4
 
5
+ const { deepCopyObject } = require('../../common/utils/copy')
5
6
  const { getNavigationIfStruct } = require('../../common/utils/structured')
6
7
  const { ensureNoDraftsSuffix, ensureDraftsSuffix, ensureUnlocalized } = require('../../common/utils/draft')
7
- const { filterKeys } = require('../../fiori/utils/handler')
8
8
  const { isAsteriskColumn } = require('../../common/utils/rewriteAsterisks')
9
+ const { getCQNUnionFrom } = require('../../common/utils/union')
10
+
11
+ const { DRAFT_COLUMNS } = require('../../common/constants/draft')
12
+
13
+ const { filterKeys } = require('../../fiori/utils/handler')
9
14
 
10
15
  // Symbols are used to add extra information in response structure
11
16
  const GET_KEY_VALUE = Symbol.for('sap.cds.getKeyValue')
@@ -16,12 +21,9 @@ const IDENTIFIER = Symbol.for('sap.cds.identifier')
16
21
  const IS_ACTIVE = Symbol.for('sap.cds.isActive')
17
22
  const IS_UNION_DRAFT = Symbol.for('sap.cds.isUnionDraft')
18
23
 
19
- const { DRAFT_COLUMNS } = require('../../common/constants/draft')
20
-
21
- const { getCQNUnionFrom } = require('../../common/utils/union')
22
-
23
24
  function getCqnCopy(readToOneCQN) {
24
- const readToOneCQNCopy = JSON.parse(JSON.stringify(readToOneCQN))
25
+ // REVISIT: Use query.clone() instead
26
+ const readToOneCQNCopy = deepCopyObject(readToOneCQN)
25
27
  if (readToOneCQN[GET_KEY_VALUE] !== undefined) readToOneCQNCopy[GET_KEY_VALUE] = readToOneCQN[GET_KEY_VALUE]
26
28
  if (readToOneCQN[TO_MANY] !== undefined) readToOneCQNCopy[TO_MANY] = readToOneCQN[TO_MANY]
27
29
  if (readToOneCQN[TO_ACTIVE] !== undefined) readToOneCQNCopy[TO_ACTIVE] = readToOneCQN[TO_ACTIVE]
@@ -518,6 +520,17 @@ class JoinCQNFromExpanded {
518
520
 
519
521
  // REVISIT required for other cqn properties as well?
520
522
  this.adjustOrderBy(readToOneCQN.orderBy, mappings, column, tableAlias)
523
+
524
+ // In case active parent entity has orderBy with draft specific columns we need to add them to parent CQN
525
+ if (
526
+ readToOneCQN[IS_ACTIVE] &&
527
+ readToOneCQN.orderBy &&
528
+ column.as &&
529
+ (column.as === 'IsActiveEntity' || column.as === 'HasActiveEntity' || column.as === 'HasDraftEntity')
530
+ ) {
531
+ readToOneCQNCopy.orderBy = readToOneCQN.orderBy
532
+ this._addColumnsInCaseOrderByHasDraft(readToOneCQNCopy, readToOneCQN.columns[readToOneCQN.columns.length - 1])
533
+ }
521
534
  }
522
535
  }
523
536
 
@@ -549,6 +562,14 @@ class JoinCQNFromExpanded {
549
562
  }
550
563
  }
551
564
 
565
+ _addColumnsInCaseOrderByHasDraft(readToOneCQNCopy, column) {
566
+ readToOneCQNCopy.orderBy.forEach(order => {
567
+ if (order.as === column.as) {
568
+ readToOneCQNCopy.columns.push(column)
569
+ }
570
+ })
571
+ }
572
+
552
573
  /**
553
574
  * Follow the tree to get to the relevant config object.
554
575
  *
@@ -626,7 +647,7 @@ class JoinCQNFromExpanded {
626
647
  // if union always only expand with active, otherwise evaluate flag
627
648
  // if flag shows false, we check entity for associations to non draft
628
649
  const activeTableRequired =
629
- readToOneCQN[IS_UNION_DRAFT] ||
650
+ readToOneCQN[IS_UNION_DRAFT] || // > REVISIT: blocks expanding comp2one
630
651
  readToOneCQN[IS_ACTIVE] ||
631
652
  (element && element.type === 'cds.Association' && !element['@odata.draft.enclosed']) ||
632
653
  !this._csn.definitions[target]._isDraftEnabled
@@ -665,9 +686,9 @@ class JoinCQNFromExpanded {
665
686
  readToOneCQN.from.args[1] = {
666
687
  SELECT: {
667
688
  columns: cols,
668
- from: unionFrom,
669
- as: tableAlias
670
- }
689
+ from: unionFrom
690
+ },
691
+ as: tableAlias
671
692
  }
672
693
  }
673
694
 
@@ -755,9 +776,9 @@ class JoinCQNFromExpanded {
755
776
  return {
756
777
  SELECT: {
757
778
  columns: Array.from(readToOneCQN.columns),
758
- from: readToOneCQN.from,
759
- as: readToOneCQN.from.as
760
- }
779
+ from: readToOneCQN.from
780
+ },
781
+ as: readToOneCQN.from.as
761
782
  }
762
783
  }
763
784
 
@@ -858,18 +879,18 @@ class JoinCQNFromExpanded {
858
879
 
859
880
  if (arg.args) {
860
881
  this._addJoinKeyColumnsToUnion(arg.args, on, parentAlias)
861
- } else if (arg.SELECT.from.SET && arg.SELECT.as === parentAlias) {
862
- this._addColumns(arg.SELECT.from.SET.args, on, parentAlias)
882
+ } else if (arg.SELECT.from.SET && (arg.as === parentAlias || arg.SELECT.from.as === parentAlias)) {
883
+ for (const _arg of arg.SELECT.from.SET.args) {
884
+ this._addColumns(_arg.SELECT.columns, on, parentAlias)
885
+ }
886
+ if (arg.SELECT.columns) {
887
+ this._addColumns(arg.SELECT.columns, on, parentAlias, true)
888
+ }
863
889
  }
864
890
  }
865
891
  }
866
892
 
867
- _addColumns(args, on, parentAlias) {
868
- const [
869
- {
870
- SELECT: { columns }
871
- }
872
- ] = args
893
+ _addColumns(columns, on, parentAlias, withAlias = false) {
873
894
  const keyColumns = on
874
895
  .filter(entry => {
875
896
  return (
@@ -878,15 +899,11 @@ class JoinCQNFromExpanded {
878
899
  !columns.some(column => column.ref && column.ref[column.ref.length - 1] === entry.ref[1])
879
900
  )
880
901
  })
881
- .map(entry => ({ ref: [entry.ref[1]] }))
882
-
902
+ .map(entry =>
903
+ withAlias ? { ref: [parentAlias, entry.ref[1]], as: `${parentAlias}_${entry.ref[1]}` } : { ref: [entry.ref[1]] }
904
+ )
883
905
  if (keyColumns.length === 0) return
884
-
885
- for (const {
886
- SELECT: { columns }
887
- } of args) {
888
- columns.push(...keyColumns)
889
- }
906
+ columns.push(...keyColumns)
890
907
  }
891
908
 
892
909
  /**
@@ -1328,6 +1345,17 @@ class JoinCQNFromExpanded {
1328
1345
  }
1329
1346
  }
1330
1347
 
1348
+ if (readToOneCQN[IS_ACTIVE] && readToOneCQN.columns.length > 0) {
1349
+ readToOneCQN.columns.forEach(column => {
1350
+ if (
1351
+ column.as === `${parentAlias}_IsActiveEntity` ||
1352
+ column.as === `${parentAlias}_HasActiveEntity` ||
1353
+ column.as === `${parentAlias}_HasDraftEntity`
1354
+ )
1355
+ columns.push(column)
1356
+ })
1357
+ }
1358
+
1331
1359
  const subSelect = Object.assign({}, readToOneCQN, { columns })
1332
1360
 
1333
1361
  const SELECT = { from: { SELECT: subSelect }, columns: outerColumns, distinct: true }
@@ -2,6 +2,9 @@ const { hasExpand, createJoinCQNFromExpanded } = require('./expandCQNToJoin')
2
2
  const rawToExpanded = require('./rawToExpanded')
3
3
  const expandV2 = require('./expand-v2')
4
4
 
5
+ /*
6
+ * this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
7
+ */
5
8
  module.exports = {
6
9
  hasExpand,
7
10
  createJoinCQNFromExpanded,
@@ -15,16 +15,6 @@ module.exports = async function (req) {
15
15
  }
16
16
 
17
17
  try {
18
- // REVISIT: should be handled in protocol adapter
19
- // execute validation query first to fail early
20
- if (req.query._validationQuery) {
21
- const validationResult = await this._read(this.model, this.dbc, req.query._validationQuery, req)
22
-
23
- if (validationResult.length === 0) {
24
- // > validation target (e.g., root of navigation) doesn't exist
25
- req.reject(404)
26
- }
27
- }
28
18
  const results = await this._insert(this.model, this.dbc, req.query, req)
29
19
  return new InsertResult(req, results)
30
20
  } catch (err) {
@@ -12,6 +12,9 @@ const DELETE = require('./delete')
12
12
  const structured = require('./structured')
13
13
  const arrayed = require('./arrayed')
14
14
 
15
+ /*
16
+ * this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
17
+ */
15
18
  module.exports = {
16
19
  rewrite,
17
20
  virtual,
@@ -7,32 +7,10 @@
7
7
  *
8
8
  * @param req - cds.Request
9
9
  */
10
- module.exports = async function (req) {
10
+ module.exports = function (req) {
11
11
  if (typeof req.query === 'string') {
12
12
  return this._execute.sql(this.dbc, req.query, req.data)
13
13
  }
14
14
 
15
- // REVISIT: should be handled in protocol adapter
16
- // execute validation query first to fail early
17
- if (req.query._validationQuery) {
18
- const validationResult = await this._read(this.model, this.dbc, req.query._validationQuery, req)
19
-
20
- if (validationResult.length === 0) {
21
- // > validation target (e.g., root of navigation) doesn't exist
22
- req.reject(404)
23
- }
24
- }
25
-
26
- const result = await this._read(this.model, this.dbc, req.query, req)
27
-
28
- if (
29
- req.query._validationQuery &&
30
- req.query._validationQuery.__navToManyWithKeys &&
31
- (!result || result.length === 0)
32
- ) {
33
- // > navigation to collection with key specified without result -> 404
34
- req.reject(404)
35
- }
36
-
37
- return result
15
+ return this._read(this.model, this.dbc, req.query, req)
38
16
  }
@@ -1,5 +1,5 @@
1
1
  const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
2
- const generateAliases = require('../utils/generateAliases')
2
+ const { generateAliases } = require('../utils/generateAliases')
3
3
  const { restoreLink } = require('../../common/utils/resolveView')
4
4
 
5
5
  const _isLinked = req => {
@@ -18,7 +18,6 @@ function handler(req) {
18
18
  }
19
19
 
20
20
  const streaming = req.query._streaming
21
- const validationQuery = req.query._validationQuery
22
21
 
23
22
  // for restore link to req.data
24
23
  const linked = _isLinked(req)
@@ -31,7 +30,6 @@ function handler(req) {
31
30
  if (linked) restoreLink(req)
32
31
 
33
32
  if (streaming) req.query._streaming = streaming
34
- if (validationQuery) req.query._validationQuery = validationQuery
35
33
 
36
34
  generateAliases(req.query)
37
35
  }
@@ -70,7 +70,7 @@ module.exports = async function (req) {
70
70
  if (onlyKeysRemain(req)) return
71
71
 
72
72
  try {
73
- const result = await this._update(this.model, this.dbc, req.query, req)
73
+ const result = await this._update(this.model, this.dbc, req)
74
74
  return result
75
75
  } catch (err) {
76
76
  // REVISIT: db specifics
@@ -1,14 +1,14 @@
1
1
  const { getFlatArray, processCQNs } = require('../utils/deep')
2
2
  const { timestampToISO } = require('../data-conversion/timestamp')
3
- const { hasDeepDelete, getDeepDeleteCQNs } = require('../../common/composition')
3
+ const { hasDeepDelete, getDeepDeleteCQNs, getSetNullParentForeignKeyCQNs } = require('../../common/composition')
4
4
 
5
- const deleteFn = executeDeleteCQN => async (model, dbc, query, req) => {
5
+ const deleteFn = (executeDeleteCQN, executeUpdateCQN) => async (model, dbc, query, req) => {
6
6
  const { user, locale, timestamp } = req
7
7
  const isoTs = timestampToISO(timestamp)
8
8
 
9
9
  let result
10
- if (hasDeepDelete(model && model.definitions, query)) {
11
- let cqns = getDeepDeleteCQNs(model && model.definitions, query)
10
+ if (hasDeepDelete(model, query)) {
11
+ let cqns = await getDeepDeleteCQNs(model, req)
12
12
 
13
13
  // the delete chunks, i.e., how many deletes can be processed in parallel
14
14
  const chunks = []
@@ -25,6 +25,12 @@ const deleteFn = executeDeleteCQN => async (model, dbc, query, req) => {
25
25
  result = results[results.length - 1]
26
26
  } else {
27
27
  result = await executeDeleteCQN(model, dbc, query, user, locale, isoTs)
28
+ if (result) {
29
+ const updateCQNs = await getSetNullParentForeignKeyCQNs(model, req)
30
+ for (const cqn of updateCQNs) {
31
+ await executeUpdateCQN(model, dbc, cqn, user, locale, isoTs)
32
+ }
33
+ }
28
34
  }
29
35
 
30
36
  return result
@@ -1,4 +1,4 @@
1
- const { hasDeepInsert, getDeepInsertCQNs, cleanEmptyCompositionsOfMany } = require('../../common/composition')
1
+ const { hasDeepInsert, getDeepInsertCQNs } = require('../../common/composition')
2
2
  const { getFlatArray, processCQNs } = require('../utils/deep')
3
3
  const { timestampToISO } = require('../data-conversion/timestamp')
4
4
 
@@ -6,8 +6,8 @@ const insert = executeInsertCQN => async (model, dbc, query, req) => {
6
6
  const { user, locale, timestamp } = req
7
7
  const isoTs = timestampToISO(timestamp)
8
8
 
9
- if (hasDeepInsert(model && model.definitions, query)) {
10
- const cqns = getFlatArray(getDeepInsertCQNs(model && model.definitions, query))
9
+ if (hasDeepInsert(model, query)) {
10
+ const cqns = getFlatArray(getDeepInsertCQNs(model, query))
11
11
 
12
12
  // return array of individual results
13
13
  if (cqns.length === 0) return []
@@ -15,7 +15,6 @@ const insert = executeInsertCQN => async (model, dbc, query, req) => {
15
15
  return getFlatArray(results)
16
16
  }
17
17
 
18
- cleanEmptyCompositionsOfMany(model && model.definitions, query)
19
18
  return executeInsertCQN(model, dbc, query, user, locale, isoTs)
20
19
  }
21
20
 
@@ -1,5 +1,7 @@
1
1
  const { timestampToISO } = require('../data-conversion/timestamp')
2
2
 
3
+ const { deepCopyObject } = require('../../common/utils/copy')
4
+
3
5
  function _arrayWithCount(a, count) {
4
6
  const _map = a.map
5
7
  const map = (..._) => _arrayWithCount(_map.call(a, ..._), count)
@@ -10,7 +12,8 @@ function _arrayWithCount(a, count) {
10
12
  }
11
13
 
12
14
  function _createCountQuery(query) {
13
- let _query = JSON.parse(JSON.stringify(query)) // REVISIT: Use query.clone() instead
15
+ // REVISIT: Use query.clone() instead
16
+ let _query = { SELECT: deepCopyObject(query.SELECT) }
14
17
  delete _query.SELECT.orderBy // not necessary to keep that
15
18
  delete _query.SELECT.limit
16
19
  // Also change columns in sub queries
@@ -50,11 +50,11 @@ const _getFilteredCqns = (cqns, model) => {
50
50
  return cqns
51
51
  }
52
52
 
53
- const update = (executeUpdateCQN, executeSelectCQN) => async (model, dbc, query, req) => {
54
- const { user, locale, timestamp } = req
53
+ const update = executeUpdateCQN => async (model, dbc, req) => {
54
+ const { query, user, locale, timestamp } = req
55
55
  const isoTs = timestampToISO(timestamp)
56
56
 
57
- if (hasDeepUpdate(model && model.definitions, query)) {
57
+ if (hasDeepUpdate(model, query)) {
58
58
  // REVISIT: _activeData gets set in case of draftActivate for performance, but this is a layer violation
59
59
  let selectData = req._ && req._.query && req._.query._activeData
60
60
 
@@ -62,10 +62,10 @@ const update = (executeUpdateCQN, executeSelectCQN) => async (model, dbc, query,
62
62
  selectData = [selectData]
63
63
  } else {
64
64
  // REVISIT: avoid additional read
65
- selectData = await selectDeepUpdateData(model && model.definitions, query, req, false, false, cds.db)
65
+ selectData = await selectDeepUpdateData(cds.db, model, req)
66
66
  }
67
67
 
68
- let cqns = getDeepUpdateCQNs(model && model.definitions, query, selectData)
68
+ let cqns = await getDeepUpdateCQNs(model, req, selectData)
69
69
 
70
70
  // the delete chunks, i.e., how many deletes can be processed in parallel
71
71
  const chunks = []