@sap/cds 5.7.5 → 5.8.2

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 +97 -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/log/format/kibana.js +3 -3
  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/ODataRequest.js +1 -3
  32. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -32
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
  37. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  38. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
  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/ResourcePathParser.js +23 -2
  47. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
  48. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +2 -5
  49. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
  50. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
  51. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
  52. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
  53. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
  54. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +2 -2
  55. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ErrorJsonSerializer.js +2 -0
  56. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
  57. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
  58. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  59. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -4
  60. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +60 -18
  63. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +47 -10
  64. package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
  65. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
  66. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
  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/tree.js +1 -1
  76. package/libx/_runtime/common/composition/update.js +39 -34
  77. package/libx/_runtime/common/error/frontend.js +19 -5
  78. package/libx/_runtime/common/generic/auth.js +20 -85
  79. package/libx/_runtime/common/generic/crud.js +22 -1
  80. package/libx/_runtime/common/i18n/messages.properties +2 -1
  81. package/libx/_runtime/common/utils/cqn.js +2 -6
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
  83. package/libx/_runtime/common/utils/csn.js +29 -6
  84. package/libx/_runtime/common/utils/foreignKeyPropagations.js +21 -1
  85. package/libx/_runtime/common/utils/keys.js +2 -1
  86. package/libx/_runtime/common/utils/path.js +1 -1
  87. package/libx/_runtime/common/utils/resolveView.js +12 -4
  88. package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
  89. package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
  90. package/libx/_runtime/common/utils/structured.js +10 -4
  91. package/libx/_runtime/common/utils/vcap.js +27 -10
  92. package/libx/_runtime/db/data-conversion/post-processing.js +20 -13
  93. package/libx/_runtime/db/expand/expand-v2.js +21 -12
  94. package/libx/_runtime/db/expand/expandCQNToJoin.js +67 -26
  95. package/libx/_runtime/db/expand/index.js +3 -0
  96. package/libx/_runtime/db/generic/create.js +0 -10
  97. package/libx/_runtime/db/generic/index.js +3 -0
  98. package/libx/_runtime/db/generic/read.js +2 -24
  99. package/libx/_runtime/db/generic/rewrite.js +1 -3
  100. package/libx/_runtime/db/generic/update.js +1 -1
  101. package/libx/_runtime/db/query/delete.js +10 -4
  102. package/libx/_runtime/db/query/insert.js +3 -4
  103. package/libx/_runtime/db/query/read.js +4 -1
  104. package/libx/_runtime/db/query/update.js +5 -5
  105. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
  106. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
  107. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -3
  108. package/libx/_runtime/db/sql-builder/index.js +3 -0
  109. package/libx/_runtime/db/utils/columns.js +5 -2
  110. package/libx/_runtime/db/utils/deep.js +16 -14
  111. package/libx/_runtime/db/utils/generateAliases.js +56 -6
  112. package/libx/_runtime/fiori/generic/before.js +73 -49
  113. package/libx/_runtime/fiori/generic/edit.js +14 -18
  114. package/libx/_runtime/fiori/generic/patch.js +8 -11
  115. package/libx/_runtime/fiori/generic/read.js +20 -19
  116. package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
  117. package/libx/_runtime/fiori/utils/handler.js +1 -11
  118. package/libx/_runtime/hana/Service.js +1 -1
  119. package/libx/_runtime/hana/conversion.js +12 -1
  120. package/libx/_runtime/hana/dynatrace.js +11 -5
  121. package/libx/_runtime/hana/execute.js +132 -19
  122. package/libx/_runtime/hana/search.js +3 -3
  123. package/libx/_runtime/hana/search2cqn4sql.js +23 -25
  124. package/libx/_runtime/hana/searchToContains.js +1 -1
  125. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  126. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
  127. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  128. package/libx/_runtime/messaging/file-based.js +3 -1
  129. package/libx/_runtime/messaging/service.js +4 -1
  130. package/libx/_runtime/remote/utils/client.js +41 -24
  131. package/libx/_runtime/remote/utils/data.js +54 -12
  132. package/libx/_runtime/sqlite/Service.js +1 -1
  133. package/libx/_runtime/sqlite/conversion.js +10 -0
  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 +49 -21
  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
@@ -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
@@ -99,6 +99,7 @@ const _getVal = (data, name) => {
99
99
 
100
100
  const _filterForStructProperty = (structElement, structData, op, prefix = '', nav = []) => {
101
101
  const filterArray = []
102
+ const andOr = op === '!=' ? 'or' : 'and'
102
103
 
103
104
  for (const elementName in structElement.elements) {
104
105
  const element = structElement.elements[elementName]
@@ -123,8 +124,8 @@ const _filterForStructProperty = (structElement, structData, op, prefix = '', na
123
124
  for (const key in assoc._target.keys) {
124
125
  if (element.name === `${assocName}_${key}`) {
125
126
  const ref = [`${prefix}_${assocName}_${key}`]
126
- const val = _getVal(structData[assocName], key)
127
- filterArray.push({ ref }, op, { val }, 'and')
127
+ const val = _getVal(structData && structData[assocName], key)
128
+ filterArray.push({ ref }, op, { val }, andOr)
128
129
  }
129
130
  }
130
131
  }
@@ -134,7 +135,7 @@ const _filterForStructProperty = (structElement, structData, op, prefix = '', na
134
135
  { ref: [...nav, `${prefix}_${element.name}`] },
135
136
  op,
136
137
  { val: _getVal(structData, element.name) },
137
- 'and'
138
+ andOr
138
139
  )
139
140
  }
140
141
  }
@@ -182,7 +183,12 @@ const _transformStructToFlatWhereHaving = ([first, op, second], resArray, struct
182
183
  } else {
183
184
  // transform complex structured to multiple single structured
184
185
  const { nestedElement, prefix } = _nestedStructElement(structProperties, structElement)
185
- resArray.push(..._filterForStructProperty(nestedElement, structData, op, prefix, nav))
186
+ const filterForStructProperty = _filterForStructProperty(nestedElement, structData, op, prefix, nav)
187
+ if (filterForStructProperty.length) {
188
+ filterForStructProperty.pop() // last and/or
189
+ if (op === '!=') resArray.push('(', ...filterForStructProperty, ')')
190
+ else resArray.push(...filterForStructProperty)
191
+ }
186
192
  }
187
193
 
188
194
  if (resArray[resArray.length - 1] === 'and') {
@@ -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()
@@ -90,7 +90,8 @@ const _extractRefs = (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
  *
@@ -1130,28 +1151,37 @@ class JoinCQNFromExpanded {
1130
1151
  }
1131
1152
  }
1132
1153
  }
1133
- const ks = Object.keys(expandedEntity.keys).filter(
1134
- c => !expandedEntity.keys[c].isAssociation && !DRAFT_COLUMNS.includes(c)
1135
- )
1136
- const user = (cds.context && cds.context.user && cds.context.user.id) || 'anonymous'
1137
- const unionFrom = getCQNUnionFrom(cols, ref.replace(/_drafts$/, ''), ref, ks, user)
1138
- for (const each of cqn.columns) {
1139
- if (!each.as) continue
1140
- // replace val with ref
1141
- if (each.as === 'IsActiveEntity' || each.as === 'HasActiveEntity') {
1142
- delete each.val
1143
- each.ref = [tableAlias, each.as]
1144
- each.as = tableAlias + '_' + each.as
1145
- }
1146
- // ensure the cast
1147
- if (each.as.match(/IsActiveEntity$/) || each.as.match(/HasActiveEntity$/) || each.as.match(/HasDraftEntity$/)) {
1148
- each.cast = { type: 'cds.Boolean' }
1154
+
1155
+ if (!cqn[IS_ACTIVE]) {
1156
+ const ks = Object.keys(expandedEntity.keys).filter(
1157
+ c => !expandedEntity.keys[c].isAssociation && !DRAFT_COLUMNS.includes(c)
1158
+ )
1159
+ const user = (cds.context && cds.context.user && cds.context.user.id) || 'anonymous'
1160
+ const unionFrom = getCQNUnionFrom(cols, ref.replace(/_drafts$/, ''), ref, ks, user)
1161
+ for (const each of cqn.columns) {
1162
+ if (!each.as) continue
1163
+ // replace val with ref
1164
+ if (each.as === 'IsActiveEntity' || each.as === 'HasActiveEntity') {
1165
+ delete each.val
1166
+ each.ref = [tableAlias, each.as]
1167
+ each.as = tableAlias + '_' + each.as
1168
+ }
1169
+ // ensure the cast
1170
+ if (
1171
+ each.as.match(/IsActiveEntity$/) ||
1172
+ each.as.match(/HasActiveEntity$/) ||
1173
+ each.as.match(/HasDraftEntity$/)
1174
+ ) {
1175
+ each.cast = { type: 'cds.Boolean' }
1176
+ }
1149
1177
  }
1178
+ const cs = cqn.columns
1179
+ .filter(c => !c.expand && c.ref && c.ref[0] === tableAlias)
1180
+ .map(c => ({ ref: [c.ref[1]] }))
1181
+ const unionArgs = cqn.from.args
1182
+ unionArgs[0].SELECT = { columns: cs, from: unionFrom, distinct: true }
1183
+ delete unionArgs[0].ref
1150
1184
  }
1151
- const cs = cqn.columns.filter(c => !c.expand && c.ref && c.ref[0] === tableAlias).map(c => ({ ref: [c.ref[1]] }))
1152
- const unionArgs = cqn.from.args
1153
- unionArgs[0].SELECT = { columns: cs, from: unionFrom, distinct: true }
1154
- delete unionArgs[0].ref
1155
1185
  }
1156
1186
 
1157
1187
  return cqn
@@ -1324,6 +1354,17 @@ class JoinCQNFromExpanded {
1324
1354
  }
1325
1355
  }
1326
1356
 
1357
+ if (readToOneCQN[IS_ACTIVE] && readToOneCQN.columns.length > 0) {
1358
+ readToOneCQN.columns.forEach(column => {
1359
+ if (
1360
+ column.as === `${parentAlias}_IsActiveEntity` ||
1361
+ column.as === `${parentAlias}_HasActiveEntity` ||
1362
+ column.as === `${parentAlias}_HasDraftEntity`
1363
+ )
1364
+ columns.push(column)
1365
+ })
1366
+ }
1367
+
1327
1368
  const subSelect = Object.assign({}, readToOneCQN, { columns })
1328
1369
 
1329
1370
  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
  }