@sap/cds 5.8.4 → 5.9.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 (244) hide show
  1. package/CHANGELOG.md +174 -77
  2. package/app/fiori/preview.js +16 -11
  3. package/app/index.js +1 -1
  4. package/bin/build/buildTaskFactory.js +3 -3
  5. package/bin/build/buildTaskProviderFactory.js +1 -1
  6. package/bin/build/constants.js +1 -1
  7. package/bin/build/provider/buildTaskHandlerEdmx.js +12 -7
  8. package/bin/build/provider/buildTaskHandlerInternal.js +1 -1
  9. package/bin/build/provider/buildTaskProviderInternal.js +8 -2
  10. package/bin/build/provider/hana/2migration.js +27 -24
  11. package/bin/build/provider/hana/index.js +17 -18
  12. package/bin/build/provider/hana/migrationtable.js +9 -10
  13. package/bin/build/provider/java-cf/index.js +4 -5
  14. package/bin/build/provider/node-cf/index.js +99 -6
  15. package/bin/cds.js +17 -18
  16. package/bin/deploy/to-hana/cfUtil.js +16 -19
  17. package/bin/deploy/to-hana/hana.js +7 -24
  18. package/bin/deploy/to-hana/hdiDeployUtil.js +8 -4
  19. package/bin/mtx/in-cds.js +2 -2
  20. package/bin/serve.js +10 -3
  21. package/bin/utils/modules.js +7 -0
  22. package/bin/version.js +56 -3
  23. package/lib/compile/cdsc.js +26 -3
  24. package/lib/compile/etc/_localized.js +36 -25
  25. package/lib/compile/etc/csv.js +8 -8
  26. package/lib/compile/for/drafts.js +9 -0
  27. package/lib/compile/for/java.js +16 -0
  28. package/lib/compile/for/nodejs.js +12 -0
  29. package/lib/compile/for/odata.js +1 -1
  30. package/lib/compile/index.js +3 -0
  31. package/lib/compile/minify.js +16 -2
  32. package/lib/compile/parse.js +2 -2
  33. package/lib/compile/resolve.js +35 -18
  34. package/lib/compile/to/json.js +3 -1
  35. package/lib/compile/to/sql.js +2 -2
  36. package/lib/compile/to/srvinfo.js +4 -2
  37. package/lib/connect/index.js +1 -1
  38. package/lib/core/entities.js +15 -14
  39. package/lib/core/index.js +39 -36
  40. package/lib/core/reflect.js +4 -2
  41. package/lib/deploy.js +114 -127
  42. package/lib/env/defaults.js +1 -0
  43. package/lib/env/index.js +165 -165
  44. package/lib/env/presets.js +1 -0
  45. package/lib/env/requires.js +120 -49
  46. package/lib/index.js +1 -0
  47. package/lib/log/format/kibana.js +2 -2
  48. package/lib/ql/SELECT.js +10 -0
  49. package/lib/ql/parse.js +1 -0
  50. package/lib/req/cds-context.js +4 -1
  51. package/lib/req/context.js +50 -56
  52. package/lib/req/event.js +1 -6
  53. package/lib/req/locale.js +6 -5
  54. package/lib/req/request.js +2 -0
  55. package/lib/req/user.js +7 -5
  56. package/lib/serve/Service-api.js +10 -7
  57. package/lib/serve/Service-dispatch.js +9 -11
  58. package/lib/serve/Service-methods.js +30 -41
  59. package/lib/serve/Transaction.js +10 -7
  60. package/lib/serve/adapters.js +7 -5
  61. package/lib/serve/index.js +24 -12
  62. package/lib/utils/data.js +1 -1
  63. package/lib/utils/index.js +27 -30
  64. package/lib/utils/resources/index.js +101 -0
  65. package/lib/utils/resources/tar.js +71 -0
  66. package/lib/utils/resources/utils.js +11 -0
  67. package/libx/_runtime/audit/Service.js +36 -39
  68. package/libx/_runtime/audit/generic/personal/access.js +3 -4
  69. package/libx/_runtime/audit/generic/personal/modification.js +3 -4
  70. package/libx/_runtime/audit/utils/v2.js +1 -2
  71. package/libx/_runtime/auth/index.js +126 -84
  72. package/libx/_runtime/auth/strategies/JWT.js +12 -19
  73. package/libx/_runtime/auth/strategies/dummy.js +1 -5
  74. package/libx/_runtime/auth/strategies/dwc.js +11 -9
  75. package/libx/_runtime/auth/strategies/mock.js +0 -4
  76. package/libx/_runtime/auth/strategies/{utils/xssec.js → xssecUtils.js} +7 -4
  77. package/libx/_runtime/auth/strategies/xsuaa.js +12 -19
  78. package/libx/_runtime/auth/utils.js +22 -1
  79. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +104 -98
  80. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -2
  81. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  82. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  83. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/language.js +2 -8
  84. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +4 -29
  85. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
  86. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +3 -2
  87. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +2 -2
  88. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +4 -6
  89. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +24 -21
  90. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +8 -2
  91. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +2 -0
  92. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
  93. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -12
  94. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +33 -9
  95. package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +50 -0
  96. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -2
  97. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +10 -3
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +9 -11
  99. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +6 -3
  100. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +4 -2
  101. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/utils.js +1 -1
  102. package/libx/_runtime/cds-services/adapter/rest/utils/binary.js +1 -1
  103. package/libx/_runtime/cds-services/adapter/rest/utils/key-value-utils.js +2 -3
  104. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +6 -4
  105. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +1 -0
  106. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +8 -5
  107. package/libx/_runtime/cds-services/services/Service.js +40 -0
  108. package/libx/_runtime/cds-services/services/utils/columns.js +4 -3
  109. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -4
  110. package/libx/_runtime/cds-services/services/utils/differ.js +3 -3
  111. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +4 -4
  112. package/libx/_runtime/cds-services/services/utils/restrictions.js +78 -0
  113. package/libx/_runtime/cds-services/util/assert.js +20 -14
  114. package/libx/_runtime/cds.js +9 -1
  115. package/libx/_runtime/common/aspects/any.js +5 -0
  116. package/libx/_runtime/common/aspects/entity.js +25 -7
  117. package/libx/_runtime/common/aspects/utils.js +2 -2
  118. package/libx/_runtime/common/composition/data.js +6 -0
  119. package/libx/_runtime/common/composition/insert.js +3 -2
  120. package/libx/_runtime/common/composition/tree.js +4 -10
  121. package/libx/_runtime/common/composition/update.js +4 -4
  122. package/libx/_runtime/common/constants/draft.js +29 -26
  123. package/libx/_runtime/common/error/constants.js +2 -2
  124. package/libx/_runtime/common/error/frontend.js +7 -15
  125. package/libx/_runtime/common/generic/auth/capabilities.js +59 -0
  126. package/libx/_runtime/common/generic/auth/constants.js +20 -0
  127. package/libx/_runtime/common/generic/auth/expand.js +54 -0
  128. package/libx/_runtime/common/generic/auth/index.js +32 -0
  129. package/libx/_runtime/common/generic/auth/insertOnly.js +15 -0
  130. package/libx/_runtime/common/generic/auth/readOnly.js +26 -0
  131. package/libx/_runtime/common/generic/auth/requires.js +34 -0
  132. package/libx/_runtime/common/generic/auth/restrict.js +296 -0
  133. package/libx/_runtime/common/generic/auth/utils.js +213 -0
  134. package/libx/_runtime/common/generic/crud.js +8 -6
  135. package/libx/_runtime/common/generic/etag.js +1 -1
  136. package/libx/_runtime/common/generic/input.js +35 -35
  137. package/libx/_runtime/common/generic/sorting.js +2 -3
  138. package/libx/_runtime/common/generic/temporal.js +2 -2
  139. package/libx/_runtime/common/i18n/messages.properties +1 -1
  140. package/libx/_runtime/common/toggles/handler.js +21 -0
  141. package/libx/_runtime/common/utils/copy.js +10 -1
  142. package/libx/_runtime/common/utils/cqn2cqn4sql.js +100 -29
  143. package/libx/_runtime/common/utils/csn.js +63 -1
  144. package/libx/_runtime/common/utils/dollar.js +10 -1
  145. package/libx/_runtime/common/utils/draft.js +46 -7
  146. package/libx/_runtime/common/utils/entityFromCqn.js +13 -9
  147. package/libx/_runtime/common/utils/extensibilityUtils.js +18 -0
  148. package/libx/_runtime/common/utils/foreignKeyPropagations.js +88 -104
  149. package/libx/_runtime/common/utils/generateOnCond.js +4 -1
  150. package/libx/_runtime/common/utils/quotingStyles.js +2 -0
  151. package/libx/_runtime/common/utils/resolveStructured.js +25 -9
  152. package/libx/_runtime/common/utils/resolveView.js +4 -1
  153. package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -16
  154. package/libx/_runtime/common/utils/structured.js +33 -37
  155. package/libx/_runtime/common/utils/template.js +17 -8
  156. package/libx/_runtime/common/utils/templateProcessor.js +28 -28
  157. package/libx/_runtime/db/data-conversion/post-processing.js +118 -417
  158. package/libx/_runtime/db/expand/expandCQNToJoin.js +45 -41
  159. package/libx/_runtime/db/expand/rawToExpanded.js +29 -8
  160. package/libx/_runtime/db/generic/index.js +1 -3
  161. package/libx/_runtime/db/generic/input.js +5 -10
  162. package/libx/_runtime/db/generic/rewrite.js +5 -2
  163. package/libx/_runtime/db/generic/structured.js +2 -2
  164. package/libx/_runtime/db/query/delete.js +2 -2
  165. package/libx/_runtime/db/query/insert.js +1 -1
  166. package/libx/_runtime/db/query/update.js +9 -14
  167. package/libx/_runtime/db/sql-builder/CreateBuilder.js +4 -3
  168. package/libx/_runtime/db/sql-builder/InsertBuilder.js +14 -1
  169. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -2
  170. package/libx/_runtime/db/sql-builder/dataTypes.js +3 -3
  171. package/libx/_runtime/db/utils/columns.js +3 -3
  172. package/libx/_runtime/db/utils/normalizeTimeData.js +2 -2
  173. package/libx/_runtime/db/utils/propagateForeignKeys.js +6 -2
  174. package/libx/_runtime/extensibility/mps/index.js +5 -0
  175. package/libx/_runtime/extensibility/mps/service.js +111 -0
  176. package/libx/_runtime/extensibility/mps/tar.js +42 -0
  177. package/libx/_runtime/extensibility/mps/utils.js +11 -0
  178. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformREAD.js +0 -0
  179. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformRESULT.js +17 -5
  180. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformWRITE.js +1 -0
  181. package/libx/_runtime/extensibility/uiflex/index.js +54 -0
  182. package/libx/_runtime/extensibility/uiflex/service.js +276 -0
  183. package/libx/_runtime/{fiori → extensibility}/uiflex/utils.js +22 -7
  184. package/libx/_runtime/fiori/generic/activate.js +2 -2
  185. package/libx/_runtime/fiori/generic/before.js +4 -4
  186. package/libx/_runtime/fiori/generic/new.js +3 -3
  187. package/libx/_runtime/fiori/generic/patch.js +1 -1
  188. package/libx/_runtime/fiori/generic/read.js +58 -66
  189. package/libx/_runtime/fiori/generic/readOverDraft.js +71 -16
  190. package/libx/_runtime/fiori/utils/handler.js +6 -13
  191. package/libx/_runtime/fiori/utils/where.js +6 -5
  192. package/libx/_runtime/hana/Service.js +4 -10
  193. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +1 -1
  194. package/libx/_runtime/hana/driver.js +2 -2
  195. package/libx/_runtime/hana/execute.js +27 -74
  196. package/libx/_runtime/hana/pool.js +1 -1
  197. package/libx/_runtime/hana/streaming.js +2 -1
  198. package/libx/_runtime/index.js +6 -6
  199. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +5 -21
  200. package/libx/_runtime/messaging/Outbox.js +2 -2
  201. package/libx/_runtime/messaging/common-utils/AMQPClient.js +4 -14
  202. package/libx/_runtime/messaging/common-utils/connections.js +5 -7
  203. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +30 -0
  204. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -1
  205. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +36 -30
  206. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -12
  207. package/libx/_runtime/messaging/enterprise-messaging.js +8 -8
  208. package/libx/_runtime/messaging/file-based.js +5 -5
  209. package/libx/_runtime/messaging/message-queuing.js +14 -12
  210. package/libx/_runtime/messaging/outbox/utils.js +18 -19
  211. package/libx/_runtime/messaging/redis-messaging.js +91 -0
  212. package/libx/_runtime/messaging/service.js +8 -6
  213. package/libx/_runtime/remote/Service.js +44 -8
  214. package/libx/_runtime/remote/utils/client.js +20 -13
  215. package/libx/_runtime/remote/utils/data.js +11 -11
  216. package/libx/_runtime/sqlite/Service.js +6 -9
  217. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +5 -2
  218. package/libx/_runtime/types/api.js +10 -2
  219. package/libx/common/utils/ucsn.js +109 -0
  220. package/libx/gql/resolvers/crud/update.js +5 -0
  221. package/libx/gql/resolvers/parse/ast2cqn/columns.js +3 -1
  222. package/libx/gql/schema/typeDefMap.js +2 -2
  223. package/libx/odata/afterburner.js +110 -16
  224. package/libx/odata/grammar.pegjs +9 -1
  225. package/libx/odata/parseToCqn.js +39 -0
  226. package/libx/odata/parser.js +1 -1
  227. package/libx/rest/RestAdapter.js +9 -1
  228. package/libx/rest/middleware/input.js +54 -0
  229. package/libx/rest/middleware/operation.js +14 -1
  230. package/libx/rest/middleware/parse.js +11 -7
  231. package/package.json +1 -1
  232. package/server.js +34 -19
  233. package/srv/audit-log.cds +2 -2
  234. package/srv/flex.cds +8 -2
  235. package/srv/flex.js +1 -1
  236. package/srv/mps.cds +23 -0
  237. package/srv/mps.js +1 -0
  238. package/libx/_runtime/auth/strategies/utils/uaa.js +0 -21
  239. package/libx/_runtime/common/generic/auth.js +0 -874
  240. package/libx/_runtime/common/toggles/alpha.js +0 -43
  241. package/libx/_runtime/db/generic/arrayed.js +0 -33
  242. package/libx/_runtime/fiori/uiflex/index.js +0 -35
  243. package/libx/_runtime/fiori/uiflex/service.js +0 -150
  244. package/libx/rest/utils/data.js +0 -60
@@ -34,9 +34,8 @@ const getSimpleCategory = category => {
34
34
  }
35
35
 
36
36
  const rowKeysGenerator = eventName => {
37
+ if (eventName === 'UPDATE') return
37
38
  return (keyNames, row, template) => {
38
- if (eventName === 'UPDATE') return
39
-
40
39
  for (const keyName of keyNames) {
41
40
  if (Object.prototype.hasOwnProperty.call(row, keyName)) {
42
41
  continue
@@ -59,7 +58,10 @@ const _isDraftCoreComputed = (req, element, event) =>
59
58
  element['@Core.Computed'] &&
60
59
  !((event === 'CREATE' && element['@cds.on.insert']) || element['@cds.on.update'])
61
60
 
62
- const _getMediaTypeProperty = element => element['@Core.MediaType'] && element['@Core.MediaType']['=']
61
+ const _isStreamingProperty = (elements, row, property) =>
62
+ Object.values(elements).some(
63
+ element => element['@Core.MediaType'] && element['@Core.MediaType']['='] === property && row[element.name]
64
+ )
63
65
 
64
66
  const _getMediaTypeValue = req =>
65
67
  req._.req &&
@@ -101,15 +103,15 @@ const _processCategory = ({ row, key, category, isRoot, event, value, req, eleme
101
103
  }
102
104
 
103
105
  // set media type from content-type header if streaming
104
- const mtProperty = _getMediaTypeProperty(element)
106
+ const isStreaming = _isStreamingProperty(element.parent.elements, row, key)
105
107
  const mtValue = _getMediaTypeValue(req)
106
- if (category === 'stream' && row[key] && mtProperty && mtValue) row[mtProperty] = mtValue
108
+ if (category === 'stream' && isStreaming && mtValue) row[key] = mtValue
107
109
  }
108
110
 
109
111
  const processorFn = (errors, req) => {
110
112
  const { event } = req
111
113
 
112
- return ({ row, key, element, plain, isRoot, pathSegments }) => {
114
+ return ({ row, key, element, plain, isRoot, path }) => {
113
115
  const categories = plain.categories
114
116
  // ugly pointer passing for sonar
115
117
  const value = { mandatory: false, val: row && row[key] }
@@ -123,7 +125,7 @@ const processorFn = (errors, req) => {
123
125
  }
124
126
 
125
127
  // REVISIT: Convert checkInputConstraints to template mechanism
126
- checkInputConstraints({ element, value: value.val, errors, pathSegments, event })
128
+ checkInputConstraints({ element, value: value.val, errors, path, event })
127
129
  }
128
130
  }
129
131
 
@@ -153,11 +155,11 @@ const _pick = element => {
153
155
  categories.push('associationEffective')
154
156
  }
155
157
 
156
- if (element.key && !DRAFT_COLUMNS_MAP[element.name] && element.type === 'cds.UUID') {
158
+ if (element.key && !DRAFT_COLUMNS_MAP[element.name] && element.isUUID) {
157
159
  categories.push('uuid')
158
160
  }
159
161
 
160
- if (element['@Core.MediaType']) categories.push('stream')
162
+ if (element['@Core.IsMediaType']) categories.push('stream')
161
163
 
162
164
  if (categories.length) return { categories }
163
165
  }
@@ -188,38 +190,34 @@ function _handler(req) {
188
190
  if (template.elements.size === 0) return
189
191
 
190
192
  const errors = []
193
+ const args = {
194
+ processFn: processorFn(errors, req),
195
+ template,
196
+ pathOptions: {
197
+ rowKeysGenerator: rowKeysGenerator(req.event),
198
+ includeKeyValues: true,
199
+ path: []
200
+ }
201
+ }
202
+ if (_isBoundAction(req)) {
203
+ const pathSegment = _getBoundActionBindingParameter(req)
204
+ const keys = req._ && req._.params && req._.params[0]
205
+ if (pathSegment) {
206
+ args.pathOptions.path.push({ key: pathSegment, url: pathSegment })
207
+ }
208
+
209
+ if (keys && 'IsActiveEntity' in keys) {
210
+ args.pathOptions.extraKeys = { IsActiveEntity: keys.IsActiveEntity }
211
+ }
212
+ }
213
+
191
214
  const data = getDataFromCQN(req.query) // REVISIT: req.data should point into req.query
192
215
 
193
216
  enrichDataWithKeysFromWhere(data, req, this)
194
217
 
195
218
  const arrayData = Array.isArray(data) ? data : [data]
196
219
  for (const row of arrayData) {
197
- let pathSegments
198
- let extraKeys
199
-
200
- if (_isBoundAction(req)) {
201
- const pathSegment = _getBoundActionBindingParameter(req)
202
- const keys = req._ && req._.params && req._.params[0]
203
- pathSegments = pathSegment ? [pathSegment] : []
204
-
205
- if (keys && 'IsActiveEntity' in keys) {
206
- extraKeys = { IsActiveEntity: keys.IsActiveEntity }
207
- }
208
- }
209
-
210
- const args = {
211
- processFn: processorFn(errors, req),
212
- row,
213
- template,
214
- pathOptions: {
215
- extraKeys,
216
- rowKeysGenerator: rowKeysGenerator(req.event),
217
- segments: pathSegments,
218
- includeKeyValues: true
219
- }
220
- }
221
-
222
- templateProcessor(args)
220
+ templateProcessor(Object.assign(args, { row }))
223
221
  }
224
222
 
225
223
  setDataFromCQN(req) // REVISIT: req.data should point into req.query
@@ -257,6 +255,7 @@ const _processActionFunctionRow = (row, param, key, errors, event, service) => {
257
255
  const _processActionFunction = (row, eventParams, errors, event, service) => {
258
256
  for (const key in eventParams) {
259
257
  let param = eventParams[key]
258
+ // .type of action/function behaves different to .type of other csn elements
260
259
  const _type = param.type
261
260
  if (!_type && param.items) param = param.items
262
261
  _processActionFunctionRow(row, param, key, errors, event, service)
@@ -292,6 +291,7 @@ function _actionFunctionHandler(req) {
292
291
  // REVISIT: find better solution, maybe compiler?
293
292
  // resolve enums like format, range, etc.
294
293
  for (const param of Object.values(eventParams)) {
294
+ // .type of action/function behaves different to .type of other csn elements
295
295
  const _type = param.type && this.model && this.model.definitions[param.type]
296
296
  if (_type) {
297
297
  param.enum = _type.enum
@@ -1,8 +1,7 @@
1
1
  const cds = require('../../cds')
2
2
  const LOG = cds.log('app')
3
3
  const { getAllKeys } = require('../../cds-services/adapter/odata-v4/odata-to-cqn/utils')
4
-
5
- const DRAFT_COLUMNS = ['IsActiveEntity', 'HasDraftEntity', 'HasActiveEntity']
4
+ const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
6
5
 
7
6
  const _getStaticOrders = req => {
8
7
  const { target: entity, query } = req
@@ -20,7 +19,7 @@ const _getStaticOrders = req => {
20
19
  if (cds.env.features.implicit_sorting !== false && (req.target._isSingleton || query.SELECT.limit)) {
21
20
  const keys = getAllKeys(entity, true)
22
21
  for (const key of keys) {
23
- if (!DRAFT_COLUMNS.includes(key) && !defaultOrders.some(o => o.by['='] === key)) {
22
+ if (!(key in DRAFT_COLUMNS_MAP) && !defaultOrders.some(o => o.by['='] === key)) {
24
23
  ordersFromKeys.push({ by: { '=': key } })
25
24
  }
26
25
  }
@@ -21,14 +21,14 @@ const _getTimeDelta = (target, queryOption) => {
21
21
 
22
22
  if (
23
23
  _isDate(queryOption) ||
24
- Object.values(target.elements).some(el => el['@cds.valid.from'] && el.type === 'cds.Date')
24
+ Object.values(target.elements).some(el => el['@cds.valid.from'] && el._type === 'cds.Date')
25
25
  ) {
26
26
  return 1000 * 60 * 60 * 24
27
27
  }
28
28
 
29
29
  if (
30
30
  _isTimestamp(queryOption) &&
31
- Object.values(target.elements).some(el => el['@cds.valid.from'] && el.type === 'cds.Timestamp')
31
+ Object.values(target.elements).some(el => el['@cds.valid.from'] && el._type === 'cds.Timestamp')
32
32
  ) {
33
33
  return 1
34
34
  }
@@ -71,7 +71,7 @@ ENTITY_IS_INSERT_ONLY=Entity "{0}" is insert-only
71
71
  ENTITY_IS_READ_ONLY=Entity "{0}" is read-only
72
72
  ENTITY_IS_NOT_CRUD=Entity "{0}" is not {1}
73
73
  ENTITY_IS_NOT_CRUD_VIA_NAVIGATION=Entity "{0}" is not {1} via association "{2}"
74
- ENTITY_IS_AUTOEXPOSED=Entity "{0}" is not explicitely exposed as part of the service
74
+ ENTITY_IS_AUTOEXPOSED=Entity "{0}" is not explicitly exposed as part of the service
75
75
  EXPAND_IS_RESTRICTED=Navigation property "{0}" is not allowed for expand operation
76
76
  EXPAND_COUNT_UNSUPPORTED="$count" is not supported for expand operation
77
77
  ORDERBY_LAMBDA_UNSUPPORTED="$orderby" does not support lambda
@@ -0,0 +1,21 @@
1
+ /*
2
+ * addition for feature toggles
3
+ */
4
+ module.exports = cds => {
5
+ if (!cds.requires.toggles) return (req, res, next) => next()
6
+
7
+ return (req, res, next) => {
8
+ // inject features from dwc header
9
+ const fth = req.headers['dwc-product-configuration']
10
+ if (fth) {
11
+ const { features } = JSON.parse(Buffer.from(fth, 'base64').toString('utf-8'))
12
+ req.features = features
13
+ .filter(f => f.enabled)
14
+ .map(f => f.name)
15
+ .sort((a, b) => a.localeCompare(b))
16
+ Object.freeze(req.features)
17
+ }
18
+
19
+ next()
20
+ }
21
+ }
@@ -29,7 +29,16 @@ const deepCopyObject = obj => {
29
29
  return clone
30
30
  }
31
31
 
32
+ const deepCopy = data => {
33
+ if (Array.isArray(data)) {
34
+ return deepCopyArray(data)
35
+ }
36
+
37
+ return deepCopyObject(data)
38
+ }
39
+
32
40
  module.exports = {
33
41
  deepCopyObject,
34
- deepCopyArray
42
+ deepCopyArray,
43
+ deepCopy
35
44
  }
@@ -1,9 +1,11 @@
1
+ /* eslint-disable complexity */
2
+
1
3
  const cds = require('../../cds')
2
4
  const { SELECT, INSERT, DELETE, UPDATE } = cds.ql
3
5
  const Query = require('../../../../lib/ql/Query')
4
6
 
5
7
  const { resolveView } = require('./resolveView')
6
- const { ensureNoDraftsSuffix } = require('./draft')
8
+ const { ensureNoDraftsSuffix, getDraftColumnsCQNForDraft } = require('./draft')
7
9
  const { flattenStructuredSelect } = require('./structured')
8
10
  const search2cqn4sql = require('./search2cqn4sql')
9
11
  const { getEntityNameFromCQN } = require('./entityFromCqn')
@@ -14,8 +16,12 @@ const { addToWhere } = require('../../common/utils/cqn')
14
16
  const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
15
17
  const { addRefToWhereIfNecessary } = require('../../../odata/afterburner')
16
18
  const { addAliasToExpression, PARENT_ALIAS, FOREIGN_ALIAS } = require('../../db/utils/generateAliases')
19
+ const { getColumns } = require('../../cds-services/services/utils/columns')
17
20
 
18
- const OPERATIONS = ['=', '>', '<', '!=', '<>', '>=', '<=', 'like', 'between', 'in', 'not in']
21
+ const OPERATIONS_MAP = ['=', '>', '<', '!=', '<>', '>=', '<=', 'like', 'between', 'in', 'not in'].reduce((acc, cur) => {
22
+ acc[cur] = 1
23
+ return acc
24
+ }, {})
19
25
 
20
26
  const _elementFromRef = (name, entity) => {
21
27
  if (!entity) return
@@ -104,7 +110,7 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
104
110
  }
105
111
  }
106
112
 
107
- const _convertPathExpressionForInsertOrDelete = (intoClause, model) => {
113
+ const _convertPathExpressionForInsert = (intoClause, model) => {
108
114
  // .into is plain string or csn entity
109
115
  if (typeof intoClause === 'string' || intoClause.name) {
110
116
  return intoClause
@@ -177,10 +183,7 @@ const _getWindColumns = (columns, groupBy, bottomTop) => {
177
183
  return [].concat(columns, _getWindowXpr(groupBy, bottomTop))
178
184
  }
179
185
 
180
- const _convertCountNavigation = (SELECT, model) => {
181
- const entityName = SELECT.from.ref[0].id || SELECT.from.ref[0]
182
- const entity = model.definitions[entityName]
183
-
186
+ const _convertCountNavigation = (SELECT, target) => {
184
187
  const newWhere = []
185
188
  for (let i = 0; i < SELECT.where.length; i++) {
186
189
  const element = SELECT.where[i]
@@ -192,8 +195,8 @@ const _convertCountNavigation = (SELECT, model) => {
192
195
 
193
196
  const navigations = element.args[0].ref
194
197
  const navigationName = navigations[0].id || navigations[0]
195
- if (entity.elements[navigationName]) {
196
- let currentEntity = entity
198
+ if (target.elements[navigationName]) {
199
+ let currentEntity = target
197
200
  let topQuery
198
201
  let lastQuery
199
202
 
@@ -344,7 +347,7 @@ const _getWhereExistsSubSelect = (queryTarget, outerAlias, innerAlias, ref, mode
344
347
  }
345
348
 
346
349
  subSelect.where(queryTarget._relations[navName].join(innerAlias, outerAlias))
347
- if (cds.env.effective.odata.structs) {
350
+ if (cds.env.effective.odata.structs || cds.env.features.ucsn_struct_conversion) {
348
351
  flattenStructuredSelect(subSelect, model)
349
352
  }
350
353
  subSelect.columns([{ val: 1 }])
@@ -459,7 +462,7 @@ const convertWhereExists = (query, model, options, currentTarget) => {
459
462
  }
460
463
 
461
464
  if (element.xpr) {
462
- convertWhereExists({ ...query, where: element.xpr }, model, options) // > recursing into nested {xpr}
465
+ convertWhereExists({ ...query, where: element.xpr }, model, options, currentTarget) // > recursing into nested {xpr}
463
466
  } else if (element === 'exists' && where[i + 1].ref) {
464
467
  if (query.from) {
465
468
  query.from.as = outerAlias
@@ -551,7 +554,7 @@ const _convertOrderByIfSkip = (orderByCQN, index) => {
551
554
 
552
555
  const _convertWhereIfSkip = (whereCQN, index) => {
553
556
  whereCQN.splice(index, 1, '1 = 1')
554
- OPERATIONS.includes(whereCQN[index + 1]) ? whereCQN.splice(index + 1, 2) : whereCQN.splice(index - 2, 2)
557
+ whereCQN[index + 1] in OPERATIONS_MAP ? whereCQN.splice(index + 1, 2) : whereCQN.splice(index - 2, 2)
555
558
  }
556
559
 
557
560
  const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, target, model, alias, processFn) => {
@@ -564,7 +567,7 @@ const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, target, model, alias, proc
564
567
 
565
568
  const _convertOrderByOrWhereIfSkip = (cqn, target, model) => {
566
569
  const alias = cqn.SELECT.from.as
567
- if (cqn.SELECT.orderBy && cqn.SELECT.orderBy.length > 1) {
570
+ if (cqn.SELECT.orderBy && cqn.SELECT.orderBy.length) {
568
571
  _convertOrderByOrWhereCQN(cqn.SELECT.orderBy, target, model, alias, _convertOrderByIfSkip)
569
572
  }
570
573
 
@@ -659,7 +662,11 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
659
662
  // Okra always wants to have the key values, remove once we relax this requirement
660
663
  if (model.definitions[target] && model.definitions[target].keys) {
661
664
  SELECT.columns = Object.keys(model.definitions[target].keys)
662
- .filter(k => !model.definitions[target].keys[k].isAssociation)
665
+ .filter(
666
+ k =>
667
+ !model.definitions[target].keys[k].isAssociation &&
668
+ !columns.find(element => element.ref && element.ref[element.ref.length - 1] === k)
669
+ )
663
670
  .map(k => ({ ref: [k] }))
664
671
  } else SELECT.columns = []
665
672
  }
@@ -679,6 +686,33 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
679
686
  }
680
687
  }
681
688
 
689
+ const _convertToOneEqNullInFilter = (query, target) => {
690
+ // we do not handle join or union
691
+ if (!target) return
692
+
693
+ for (let i = 0; i < query.where.length; i++) {
694
+ const w = query.where[i]
695
+ const w2 = query.where[i + 2]
696
+ if (!w2 || !w.ref || w2.val !== null) {
697
+ continue
698
+ }
699
+ const element = target.elements[w.ref[w.ref.length - 1]]
700
+ if (element && element.is2one && !element.on) {
701
+ const foreignKeys = Object.values(element.parent.elements).filter(e => e._foreignKey4 === element.name)
702
+ const replacedKeys = foreignKeys.reduce((arr, e, idx) => {
703
+ arr.push({ ref: [...w.ref.slice(0, w.ref.length - 1), e.name] }, query.where[i + 1], query.where[i + 2])
704
+ if (idx < foreignKeys.length - 1) {
705
+ arr.push('and')
706
+ }
707
+ return arr
708
+ }, [])
709
+
710
+ query.where.splice(i, 3, '(', ...replacedKeys, ')')
711
+ i += replacedKeys.length + 2
712
+ }
713
+ }
714
+ }
715
+
682
716
  // eslint-disable-next-line complexity
683
717
  const _convertSelect = (query, model, _options) => {
684
718
  const options = Object.assign(
@@ -688,6 +722,7 @@ const _convertSelect = (query, model, _options) => {
688
722
  },
689
723
  _options
690
724
  )
725
+
691
726
  // ensure query is ql enabled
692
727
  if (!(query instanceof Query)) Object.setPrototypeOf(query, Object.getPrototypeOf(SELECT()))
693
728
  if (query.SELECT.from && query.SELECT.from.SELECT) {
@@ -709,8 +744,14 @@ const _convertSelect = (query, model, _options) => {
709
744
 
710
745
  _convertPathExpression(query.SELECT, model, options)
711
746
  rewriteAsterisks(query, model, options)
712
- if (query.SELECT.where && _isCountNavigation(query.SELECT.where)) {
713
- _convertCountNavigation(query.SELECT, model)
747
+ if (query.SELECT.where) {
748
+ const entityName =
749
+ (query.SELECT.from.ref && (query.SELECT.from.ref[0].id || query.SELECT.from.ref[0])) || query.SELECT.from
750
+ const target = model.definitions[entityName]
751
+ if (_isCountNavigation(query.SELECT.where)) {
752
+ _convertCountNavigation(query.SELECT, target)
753
+ }
754
+ _convertToOneEqNullInFilter(query.SELECT, target)
714
755
  }
715
756
 
716
757
  // extract where clause if it is in column expand ref
@@ -727,7 +768,7 @@ const _convertSelect = (query, model, _options) => {
727
768
  search2cqn4sql(query, model, { ...query._searchOptions, ...{ entityName, alias } })
728
769
  }
729
770
 
730
- if (query.SELECT.columns && cds.env.effective.odata.structs) {
771
+ if (query.SELECT.columns && (cds.env.effective.odata.structs || cds.env.features.ucsn_struct_conversion)) {
731
772
  flattenStructuredSelect(query, model)
732
773
  }
733
774
 
@@ -736,30 +777,43 @@ const _convertSelect = (query, model, _options) => {
736
777
  _createWindowCQN(query.SELECT, model)
737
778
  }
738
779
 
780
+ // best-effort ensure columns
781
+ if (options._4db && !query.SELECT.columns) {
782
+ let target = query._target
783
+ if (target && target._unresolved && typeof target.name === 'string') {
784
+ target = model.definitions[ensureNoDraftsSuffix(target.name)] || target
785
+ }
786
+ if (target && !target._unresolved) {
787
+ const cols = getColumns(target, { onlyNames: true })
788
+ query.columns(cols)
789
+ if (target._isDraftEnabled && query._target._unresolved) {
790
+ query.SELECT.columns.push(...getDraftColumnsCQNForDraft(target))
791
+ query.SELECT.columns.push({ ref: ['DraftAdministrativeData_DraftUUID'] })
792
+ }
793
+ }
794
+ }
795
+
739
796
  return query
740
797
  }
741
798
 
742
799
  const _convertInsert = (query, model, options) => {
743
800
  // resolve path expression
744
- const resolvedIntoClause = _convertPathExpressionForInsertOrDelete(query.INSERT.into, model)
801
+ const resolvedIntoClause = _convertPathExpressionForInsert(query.INSERT.into, model)
745
802
 
746
803
  // overwrite only .into, foreign keys are already set
747
- const insert = INSERT.into(resolvedIntoClause)
804
+ // 'a' added as placeholder since its overwritten by Object.assign below
805
+ const insert = INSERT.into('a')
748
806
 
749
807
  // REVISIT flatten structured types, currently its done in SQL builder
750
808
 
751
809
  // We add all previous properties ot the newly created query.
752
810
  // Reason is to not lose the query API functionality
753
- Object.assign(insert.INSERT, query.INSERT, { into: resolvedIntoClause })
754
-
755
- const targetName = insert.INSERT.into.name || insert.INSERT.into
811
+ Object.assign(insert.INSERT, query.INSERT, { into: { ref: [resolvedIntoClause], as: query.INSERT.into.as } })
756
812
 
757
- const target = model.definitions[targetName]
813
+ const target = model.definitions[resolvedIntoClause]
758
814
  if (!target) return insert
759
815
 
760
- const resolvedView = resolveView(insert, model, cds.db)
761
-
762
- return resolvedView
816
+ return resolveView(insert, model, cds.db)
763
817
  }
764
818
 
765
819
  function _modifyNavigationInWhere(whereClause, target) {
@@ -783,6 +837,11 @@ function _modifyNavigationInWhere(whereClause, target) {
783
837
  const _plainDelete = (cqn, model) => {
784
838
  const name = cqn.DELETE.from.name || (cqn.DELETE.from.ref && cqn.DELETE.from.ref[0]) || cqn.DELETE.from
785
839
  const target = model.definitions[name]
840
+
841
+ if (cqn.DELETE.where) {
842
+ _convertToOneEqNullInFilter(cqn.DELETE, target)
843
+ }
844
+
786
845
  if (!target) return cqn
787
846
 
788
847
  return resolveView(cqn, model, cds.db)
@@ -804,9 +863,13 @@ const _convertDelete = (query, model, options) => {
804
863
 
805
864
  if (alias) deleet.DELETE.from = { ref: [target], as: alias }
806
865
  if (where) deleet.where(where)
807
- if (query.DELETE.where) deleet.where(addAliasToExpression(query.DELETE.where, alias))
808
866
 
809
867
  const targetEntity = model.definitions[target]
868
+ if (query.DELETE.where) {
869
+ deleet.where(addAliasToExpression(query.DELETE.where, alias))
870
+ _convertToOneEqNullInFilter(deleet.DELETE, targetEntity)
871
+ }
872
+
810
873
  if (!targetEntity) return deleet
811
874
 
812
875
  return resolveView(deleet, model, cds.db)
@@ -815,6 +878,11 @@ const _convertDelete = (query, model, options) => {
815
878
  function _plainUpdate(cqn, model) {
816
879
  const name = cqn.UPDATE.entity.name || (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) || cqn.UPDATE.entity
817
880
  const target = model.definitions[name]
881
+
882
+ if (cqn.UPDATE.where) {
883
+ _convertToOneEqNullInFilter(cqn.UPDATE, target)
884
+ }
885
+
818
886
  if (!target) return cqn
819
887
 
820
888
  return resolveView(cqn, model, cds.db)
@@ -841,9 +909,12 @@ const _convertUpdate = (query, model, options) => {
841
909
 
842
910
  if (alias) update.UPDATE.entity = { ref: [target], as: alias }
843
911
  if (where) update.where(where)
844
- if (query.UPDATE.where) update.where(addAliasToExpression(query.UPDATE.where, alias))
845
-
846
912
  const targetEntity = model.definitions[target]
913
+ if (query.UPDATE.where) {
914
+ update.where(addAliasToExpression(query.UPDATE.where, alias))
915
+ _convertToOneEqNullInFilter(update.UPDATE, targetEntity)
916
+ }
917
+
847
918
  if (!targetEntity) return update
848
919
 
849
920
  return resolveView(update, model, cds.db)
@@ -1,4 +1,5 @@
1
1
  const cds = require('../../cds')
2
+ const resolveStructured = require('../../common/utils/resolveStructured')
2
3
 
3
4
  const { ensureNoDraftsSuffix } = require('./draft')
4
5
 
@@ -140,6 +141,7 @@ const findCsnTargetFor = (edmName, model, namespace) => {
140
141
  // probably, a combination of '_' and '.', resolving
141
142
  const finding = _findRootEntity(model, edmName, namespace)
142
143
  target = finding.target
144
+
143
145
  // something left in navigation path => x4 navigation
144
146
  // resolving within found entity
145
147
  if (target && finding.left > 0) {
@@ -151,10 +153,12 @@ const findCsnTargetFor = (edmName, model, namespace) => {
151
153
  }
152
154
  }
153
155
  }
156
+
154
157
  // remember edm <-> csn
155
158
  if (target) {
156
159
  mapping[edmName] = target
157
160
  }
161
+
158
162
  return mapping[edmName]
159
163
  }
160
164
 
@@ -191,7 +195,40 @@ const isRootEntity = (definitions, entityName) => {
191
195
  return true
192
196
  }
193
197
 
198
+ function _alias2RefRest(service) {
199
+ for (const each of Object.values(service.entities)) {
200
+ each._alias2ref = {}
201
+ const keys = each.keys
202
+ for (const key in keys) {
203
+ if (keys[key].elements) {
204
+ const structKeys = resolveStructured({ structName: key, structProperties: [] }, keys[key].elements, false, true)
205
+ for (const structKey of structKeys) {
206
+ if (each._alias2ref[structKey.key] != null) {
207
+ // key clash, aliasing not possible
208
+ each._alias2ref = {}
209
+ return
210
+ }
211
+ each._alias2ref[structKey.key] = structKey.resolved
212
+ }
213
+ }
214
+ }
215
+ }
216
+ }
217
+
218
+ const prefixForStruct = element => {
219
+ const prefixes = []
220
+ let parent = element.parent
221
+ while (parent && parent.kind !== 'entity') {
222
+ prefixes.push(parent.name)
223
+ parent = parent.parent
224
+ }
225
+ return prefixes.length ? prefixes.reverse().join('_') + '_' : ''
226
+ }
227
+
194
228
  function alias2ref(service, edm) {
229
+ if (!edm) {
230
+ return _alias2RefRest(service)
231
+ }
195
232
  const defs = edm[service.definition.name]
196
233
  for (const each of Object.values(service.entities)) {
197
234
  const def = defs[each.name.replace(service.definition.name + '.', '').replace(/\./g, '_')]
@@ -205,6 +242,29 @@ function alias2ref(service, edm) {
205
242
  }
206
243
  }
207
244
 
245
+ function getDraftTreeRoot(entity, model) {
246
+ if (entity.own('__draftTreeRoot')) return entity.__draftTreeRoot
247
+
248
+ let parent
249
+ let current = entity
250
+ while (current && !current['@Common.DraftRoot.ActivationAction']) {
251
+ const parents = []
252
+ for (const k in model.definitions) {
253
+ const e = model.definitions[k]
254
+ if (e.kind !== 'entity' || !e.compositions) continue
255
+ for (const c in e.compositions) if (e.compositions[c].target === current.name) parents.push(e)
256
+ }
257
+ if (parents.length > 1 && parents.some(p => p !== parents[0])) {
258
+ // > unable to determine single parent
259
+ parent = undefined
260
+ break
261
+ }
262
+ current = parent = parents[0]
263
+ }
264
+
265
+ return entity.set('__draftTreeRoot', parent)
266
+ }
267
+
208
268
  module.exports = {
209
269
  getEtagElement,
210
270
  findCsnTargetFor,
@@ -212,5 +272,7 @@ module.exports = {
212
272
  isRootEntity,
213
273
  getDataSubject,
214
274
  alias2ref,
215
- getComp2oneParents
275
+ getComp2oneParents,
276
+ prefixForStruct,
277
+ getDraftTreeRoot
216
278
  }
@@ -11,7 +11,16 @@ module.exports = (entryOrRow, keyOrIndex, user, timestamp) => {
11
11
  else if (entryOrRow[keyOrIndex] === '$now') entryOrRow[keyOrIndex] = timestamp
12
12
  else if (entryOrRow[keyOrIndex] === '$uuid') entryOrRow[keyOrIndex] = cds.utils.uuid()
13
13
  else if (typeof entryOrRow[keyOrIndex] === 'string') {
14
+ // NOTE: with xsuaa, user attributes are always arrays
14
15
  const attr = entryOrRow[keyOrIndex].match(/^\$user\.(.*)/)
15
- if (attr && attr.length > 1) entryOrRow[keyOrIndex] = (user.attr && user.attr[attr[1]]) || null
16
+ if (attr && attr.length > 1) {
17
+ const val = (user.attr && user.attr[attr[1]]) || null
18
+ if (Array.isArray(val)) {
19
+ if (val.length > 1) entryOrRow[keyOrIndex] = JSON.stringify(val)
20
+ else entryOrRow[keyOrIndex] = val.length > 0 ? val[0] : null
21
+ } else {
22
+ entryOrRow[keyOrIndex] = val
23
+ }
24
+ }
16
25
  }
17
26
  }