@sap/cds 5.7.2 → 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 (148) hide show
  1. package/CHANGELOG.md +108 -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 -42
  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 +18 -8
  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 +7 -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 +21 -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 +1 -1
  54. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
  55. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
  56. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  57. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +18 -5
  58. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
  59. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
  60. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +80 -21
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +7 -5
  62. package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
  63. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
  64. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
  65. package/libx/_runtime/cds-services/services/Service.js +1 -1
  66. package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
  67. package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
  68. package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
  69. package/libx/_runtime/common/aspects/Association.js +16 -0
  70. package/libx/_runtime/common/composition/data.js +28 -37
  71. package/libx/_runtime/common/composition/delete.js +107 -58
  72. package/libx/_runtime/common/composition/index.js +2 -1
  73. package/libx/_runtime/common/composition/insert.js +13 -13
  74. package/libx/_runtime/common/composition/update.js +39 -34
  75. package/libx/_runtime/common/error/frontend.js +17 -2
  76. package/libx/_runtime/common/generic/auth.js +20 -85
  77. package/libx/_runtime/common/generic/crud.js +22 -1
  78. package/libx/_runtime/common/i18n/messages.properties +3 -0
  79. package/libx/_runtime/common/utils/cqn.js +2 -6
  80. package/libx/_runtime/common/utils/cqn2cqn4sql.js +97 -123
  81. package/libx/_runtime/common/utils/csn.js +14 -3
  82. package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -1
  83. package/libx/_runtime/common/utils/keys.js +2 -1
  84. package/libx/_runtime/common/utils/path.js +1 -1
  85. package/libx/_runtime/common/utils/resolveView.js +12 -4
  86. package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
  87. package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
  88. package/libx/_runtime/common/utils/structured.js +1 -1
  89. package/libx/_runtime/common/utils/vcap.js +27 -10
  90. package/libx/_runtime/db/data-conversion/post-processing.js +42 -35
  91. package/libx/_runtime/db/expand/expand-v2.js +21 -12
  92. package/libx/_runtime/db/expand/expandCQNToJoin.js +27 -29
  93. package/libx/_runtime/db/expand/index.js +3 -0
  94. package/libx/_runtime/db/generic/create.js +0 -10
  95. package/libx/_runtime/db/generic/index.js +3 -0
  96. package/libx/_runtime/db/generic/read.js +2 -24
  97. package/libx/_runtime/db/generic/rewrite.js +1 -3
  98. package/libx/_runtime/db/generic/update.js +1 -1
  99. package/libx/_runtime/db/query/delete.js +10 -4
  100. package/libx/_runtime/db/query/insert.js +3 -3
  101. package/libx/_runtime/db/query/read.js +15 -8
  102. package/libx/_runtime/db/query/update.js +5 -5
  103. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
  104. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
  105. package/libx/_runtime/db/sql-builder/index.js +3 -0
  106. package/libx/_runtime/db/utils/columns.js +5 -2
  107. package/libx/_runtime/db/utils/deep.js +6 -8
  108. package/libx/_runtime/db/utils/generateAliases.js +56 -6
  109. package/libx/_runtime/fiori/generic/before.js +73 -49
  110. package/libx/_runtime/fiori/generic/edit.js +14 -18
  111. package/libx/_runtime/fiori/generic/patch.js +8 -11
  112. package/libx/_runtime/fiori/generic/read.js +22 -17
  113. package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
  114. package/libx/_runtime/hana/Service.js +1 -1
  115. package/libx/_runtime/hana/conversion.js +10 -0
  116. package/libx/_runtime/hana/execute.js +33 -16
  117. package/libx/_runtime/hana/localized.js +1 -1
  118. package/libx/_runtime/hana/search.js +3 -3
  119. package/libx/_runtime/hana/search2cqn4sql.js +22 -21
  120. package/libx/_runtime/hana/searchToContains.js +1 -1
  121. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
  122. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
  123. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  124. package/libx/_runtime/messaging/file-based.js +3 -1
  125. package/libx/_runtime/messaging/message-queuing-utils/options-messaging.js +1 -0
  126. package/libx/_runtime/messaging/service.js +16 -7
  127. package/libx/_runtime/remote/utils/client.js +33 -20
  128. package/libx/_runtime/remote/utils/data.js +53 -12
  129. package/libx/_runtime/sqlite/Service.js +1 -1
  130. package/libx/_runtime/sqlite/conversion.js +10 -0
  131. package/libx/_runtime/sqlite/localized.js +1 -1
  132. package/libx/_runtime/types/api.js +2 -2
  133. package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
  134. package/libx/odata/afterburner.js +29 -6
  135. package/libx/odata/cqn2odata.js +9 -0
  136. package/libx/odata/grammar.pegjs +101 -45
  137. package/libx/odata/index.js +7 -1
  138. package/libx/odata/parser.js +1 -1
  139. package/libx/odata/utils.js +2 -2
  140. package/libx/rest/RestAdapter.js +29 -1
  141. package/libx/rest/middleware/auth.js +1 -3
  142. package/libx/rest/middleware/parse.js +1 -0
  143. package/package.json +1 -1
  144. package/server.js +1 -1
  145. package/bin/deploy/to-hana/logger.js +0 -27
  146. package/bin/deploy/to-hana/runCommand.js +0 -113
  147. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
  148. package/libx/_runtime/common/utils/auth.js +0 -16
@@ -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
@@ -284,7 +284,7 @@ const flattenStructuredSelect = ({ SELECT }, model) => {
284
284
  _flattenColumns(SELECT, flattenedElements, toBeDeleted, entity)
285
285
  SELECT.columns = SELECT.columns.filter(column => {
286
286
  const columnName = column.ref ? column.ref[0] : column.as
287
- return (columnName && !toBeDeleted.includes(columnName)) || column.func || column.expand
287
+ return (columnName && !toBeDeleted.includes(columnName)) || column.func || column.expand || 'val' in column
288
288
  })
289
289
  if (flattenedElements.length) SELECT.columns.push(...flattenedElements)
290
290
  }
@@ -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]
@@ -626,7 +628,7 @@ class JoinCQNFromExpanded {
626
628
  // if union always only expand with active, otherwise evaluate flag
627
629
  // if flag shows false, we check entity for associations to non draft
628
630
  const activeTableRequired =
629
- readToOneCQN[IS_UNION_DRAFT] ||
631
+ readToOneCQN[IS_UNION_DRAFT] || // > REVISIT: blocks expanding comp2one
630
632
  readToOneCQN[IS_ACTIVE] ||
631
633
  (element && element.type === 'cds.Association' && !element['@odata.draft.enclosed']) ||
632
634
  !this._csn.definitions[target]._isDraftEnabled
@@ -665,9 +667,9 @@ class JoinCQNFromExpanded {
665
667
  readToOneCQN.from.args[1] = {
666
668
  SELECT: {
667
669
  columns: cols,
668
- from: unionFrom,
669
- as: tableAlias
670
- }
670
+ from: unionFrom
671
+ },
672
+ as: tableAlias
671
673
  }
672
674
  }
673
675
 
@@ -755,9 +757,9 @@ class JoinCQNFromExpanded {
755
757
  return {
756
758
  SELECT: {
757
759
  columns: Array.from(readToOneCQN.columns),
758
- from: readToOneCQN.from,
759
- as: readToOneCQN.from.as
760
- }
760
+ from: readToOneCQN.from
761
+ },
762
+ as: readToOneCQN.from.as
761
763
  }
762
764
  }
763
765
 
@@ -858,18 +860,18 @@ class JoinCQNFromExpanded {
858
860
 
859
861
  if (arg.args) {
860
862
  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)
863
+ } else if (arg.SELECT.from.SET && (arg.as === parentAlias || arg.SELECT.from.as === parentAlias)) {
864
+ for (const _arg of arg.SELECT.from.SET.args) {
865
+ this._addColumns(_arg.SELECT.columns, on, parentAlias)
866
+ }
867
+ if (arg.SELECT.columns) {
868
+ this._addColumns(arg.SELECT.columns, on, parentAlias, true)
869
+ }
863
870
  }
864
871
  }
865
872
  }
866
873
 
867
- _addColumns(args, on, parentAlias) {
868
- const [
869
- {
870
- SELECT: { columns }
871
- }
872
- ] = args
874
+ _addColumns(columns, on, parentAlias, withAlias = false) {
873
875
  const keyColumns = on
874
876
  .filter(entry => {
875
877
  return (
@@ -878,15 +880,11 @@ class JoinCQNFromExpanded {
878
880
  !columns.some(column => column.ref && column.ref[column.ref.length - 1] === entry.ref[1])
879
881
  )
880
882
  })
881
- .map(entry => ({ ref: [entry.ref[1]] }))
882
-
883
+ .map(entry =>
884
+ withAlias ? { ref: [parentAlias, entry.ref[1]], as: `${parentAlias}_${entry.ref[1]}` } : { ref: [entry.ref[1]] }
885
+ )
883
886
  if (keyColumns.length === 0) return
884
-
885
- for (const {
886
- SELECT: { columns }
887
- } of args) {
888
- columns.push(...keyColumns)
889
- }
887
+ columns.push(...keyColumns)
890
888
  }
891
889
 
892
890
  /**
@@ -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,