@sap/cds 5.8.4 → 5.9.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 (248) hide show
  1. package/CHANGELOG.md +198 -77
  2. package/app/fiori/preview.js +16 -11
  3. package/app/fiori/routes.js +15 -8
  4. package/app/index.js +1 -1
  5. package/bin/build/buildTaskFactory.js +3 -3
  6. package/bin/build/buildTaskProviderFactory.js +1 -1
  7. package/bin/build/constants.js +1 -1
  8. package/bin/build/provider/buildTaskHandlerEdmx.js +12 -7
  9. package/bin/build/provider/buildTaskHandlerInternal.js +1 -1
  10. package/bin/build/provider/buildTaskProviderInternal.js +8 -2
  11. package/bin/build/provider/hana/2migration.js +27 -24
  12. package/bin/build/provider/hana/index.js +17 -18
  13. package/bin/build/provider/hana/migrationtable.js +9 -10
  14. package/bin/build/provider/java-cf/index.js +4 -5
  15. package/bin/build/provider/node-cf/index.js +99 -6
  16. package/bin/cds.js +17 -18
  17. package/bin/deploy/to-hana/cfUtil.js +16 -19
  18. package/bin/deploy/to-hana/hana.js +7 -24
  19. package/bin/deploy/to-hana/hdiDeployUtil.js +8 -4
  20. package/bin/mtx/in-cds.js +2 -2
  21. package/bin/serve.js +10 -3
  22. package/bin/utils/modules.js +7 -0
  23. package/bin/version.js +56 -3
  24. package/lib/compile/cdsc.js +7 -2
  25. package/lib/compile/etc/_localized.js +37 -25
  26. package/lib/compile/etc/csv.js +8 -8
  27. package/lib/compile/for/drafts.js +9 -0
  28. package/lib/compile/for/java.js +16 -0
  29. package/lib/compile/for/nodejs.js +12 -0
  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/bindings.js +1 -1
  38. package/lib/connect/index.js +3 -4
  39. package/lib/core/entities.js +15 -14
  40. package/lib/core/index.js +39 -36
  41. package/lib/core/reflect.js +4 -2
  42. package/lib/deploy.js +114 -127
  43. package/lib/env/defaults.js +1 -0
  44. package/lib/env/index.js +165 -165
  45. package/lib/env/presets.js +1 -0
  46. package/lib/env/requires.js +121 -50
  47. package/lib/index.js +2 -0
  48. package/lib/log/format/kibana.js +2 -2
  49. package/lib/ql/SELECT.js +10 -0
  50. package/lib/ql/parse.js +1 -0
  51. package/lib/req/cds-context.js +4 -1
  52. package/lib/req/context.js +50 -56
  53. package/lib/req/event.js +1 -6
  54. package/lib/req/locale.js +6 -5
  55. package/lib/req/request.js +2 -0
  56. package/lib/req/user.js +7 -5
  57. package/lib/serve/Service-api.js +10 -7
  58. package/lib/serve/Service-dispatch.js +9 -11
  59. package/lib/serve/Service-methods.js +30 -41
  60. package/lib/serve/Transaction.js +10 -7
  61. package/lib/serve/adapters.js +11 -9
  62. package/lib/serve/factory.js +14 -9
  63. package/lib/serve/index.js +28 -15
  64. package/lib/utils/data.js +1 -1
  65. package/lib/utils/index.js +27 -30
  66. package/lib/utils/resources/index.js +101 -0
  67. package/lib/utils/resources/tar.js +71 -0
  68. package/lib/utils/resources/utils.js +11 -0
  69. package/libx/_runtime/audit/Service.js +36 -39
  70. package/libx/_runtime/audit/generic/personal/access.js +3 -4
  71. package/libx/_runtime/audit/generic/personal/modification.js +3 -4
  72. package/libx/_runtime/audit/utils/v2.js +1 -2
  73. package/libx/_runtime/auth/index.js +126 -84
  74. package/libx/_runtime/auth/strategies/JWT.js +12 -19
  75. package/libx/_runtime/auth/strategies/dummy.js +1 -5
  76. package/libx/_runtime/auth/strategies/dwc.js +11 -9
  77. package/libx/_runtime/auth/strategies/mock.js +0 -4
  78. package/libx/_runtime/auth/strategies/{utils/xssec.js → xssecUtils.js} +7 -4
  79. package/libx/_runtime/auth/strategies/xsuaa.js +12 -19
  80. package/libx/_runtime/auth/utils.js +22 -1
  81. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +104 -98
  82. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +8 -3
  83. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  84. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  85. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/language.js +2 -8
  86. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +4 -29
  87. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
  88. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +3 -2
  89. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +2 -2
  90. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +4 -6
  91. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +24 -21
  92. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +8 -2
  93. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +2 -0
  94. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
  95. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -12
  96. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +33 -9
  97. package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +56 -0
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -2
  99. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +10 -3
  100. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +9 -11
  101. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +6 -3
  102. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +4 -2
  103. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/utils.js +1 -1
  104. package/libx/_runtime/cds-services/adapter/rest/utils/binary.js +1 -1
  105. package/libx/_runtime/cds-services/adapter/rest/utils/key-value-utils.js +2 -3
  106. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +6 -4
  107. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +1 -0
  108. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +8 -5
  109. package/libx/_runtime/cds-services/services/Service.js +40 -0
  110. package/libx/_runtime/cds-services/services/utils/columns.js +4 -3
  111. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -4
  112. package/libx/_runtime/cds-services/services/utils/differ.js +3 -3
  113. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +4 -4
  114. package/libx/_runtime/cds-services/util/assert.js +20 -14
  115. package/libx/_runtime/cds.js +9 -1
  116. package/libx/_runtime/common/aspects/any.js +5 -0
  117. package/libx/_runtime/common/aspects/entity.js +25 -7
  118. package/libx/_runtime/common/aspects/utils.js +2 -2
  119. package/libx/_runtime/common/composition/data.js +6 -0
  120. package/libx/_runtime/common/composition/insert.js +3 -2
  121. package/libx/_runtime/common/composition/tree.js +4 -10
  122. package/libx/_runtime/common/composition/update.js +4 -4
  123. package/libx/_runtime/common/constants/draft.js +29 -26
  124. package/libx/_runtime/common/error/constants.js +2 -2
  125. package/libx/_runtime/common/error/frontend.js +7 -15
  126. package/libx/_runtime/common/generic/auth/capabilities.js +59 -0
  127. package/libx/_runtime/common/generic/auth/constants.js +20 -0
  128. package/libx/_runtime/common/generic/auth/expand.js +54 -0
  129. package/libx/_runtime/common/generic/auth/index.js +32 -0
  130. package/libx/_runtime/common/generic/auth/insertOnly.js +15 -0
  131. package/libx/_runtime/common/generic/auth/readOnly.js +26 -0
  132. package/libx/_runtime/common/generic/auth/requires.js +34 -0
  133. package/libx/_runtime/common/generic/auth/restrict.js +298 -0
  134. package/libx/_runtime/common/generic/auth/restrictions.js +85 -0
  135. package/libx/_runtime/common/generic/auth/utils.js +213 -0
  136. package/libx/_runtime/common/generic/crud.js +8 -6
  137. package/libx/_runtime/common/generic/etag.js +1 -1
  138. package/libx/_runtime/common/generic/input.js +35 -35
  139. package/libx/_runtime/common/generic/sorting.js +2 -3
  140. package/libx/_runtime/common/generic/temporal.js +2 -2
  141. package/libx/_runtime/common/i18n/messages.properties +1 -1
  142. package/libx/_runtime/common/toggles/handler.js +21 -0
  143. package/libx/_runtime/common/utils/copy.js +10 -1
  144. package/libx/_runtime/common/utils/cqn2cqn4sql.js +111 -35
  145. package/libx/_runtime/common/utils/csn.js +63 -1
  146. package/libx/_runtime/common/utils/dollar.js +10 -1
  147. package/libx/_runtime/common/utils/draft.js +46 -7
  148. package/libx/_runtime/common/utils/entityFromCqn.js +13 -9
  149. package/libx/_runtime/common/utils/extensibilityUtils.js +18 -0
  150. package/libx/_runtime/common/utils/foreignKeyPropagations.js +88 -104
  151. package/libx/_runtime/common/utils/generateOnCond.js +4 -1
  152. package/libx/_runtime/common/utils/quotingStyles.js +2 -0
  153. package/libx/_runtime/common/utils/resolveStructured.js +25 -9
  154. package/libx/_runtime/common/utils/resolveView.js +4 -1
  155. package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -16
  156. package/libx/_runtime/common/utils/structured.js +33 -37
  157. package/libx/_runtime/common/utils/template.js +17 -8
  158. package/libx/_runtime/common/utils/templateProcessor.js +28 -28
  159. package/libx/_runtime/db/data-conversion/post-processing.js +118 -412
  160. package/libx/_runtime/db/expand/expandCQNToJoin.js +45 -41
  161. package/libx/_runtime/db/expand/rawToExpanded.js +29 -8
  162. package/libx/_runtime/db/generic/index.js +1 -3
  163. package/libx/_runtime/db/generic/input.js +5 -10
  164. package/libx/_runtime/db/generic/rewrite.js +5 -2
  165. package/libx/_runtime/db/generic/structured.js +2 -2
  166. package/libx/_runtime/db/query/delete.js +2 -2
  167. package/libx/_runtime/db/query/insert.js +1 -1
  168. package/libx/_runtime/db/query/update.js +9 -14
  169. package/libx/_runtime/db/sql-builder/CreateBuilder.js +4 -3
  170. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +8 -8
  171. package/libx/_runtime/db/sql-builder/InsertBuilder.js +14 -1
  172. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -2
  173. package/libx/_runtime/db/sql-builder/dataTypes.js +3 -3
  174. package/libx/_runtime/db/utils/columns.js +3 -3
  175. package/libx/_runtime/db/utils/normalizeTimeData.js +2 -2
  176. package/libx/_runtime/db/utils/propagateForeignKeys.js +6 -2
  177. package/libx/_runtime/extensibility/mps/index.js +5 -0
  178. package/libx/_runtime/extensibility/mps/service.js +111 -0
  179. package/libx/_runtime/extensibility/mps/tar.js +42 -0
  180. package/libx/_runtime/extensibility/mps/utils.js +11 -0
  181. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformREAD.js +0 -0
  182. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformRESULT.js +17 -5
  183. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformWRITE.js +1 -0
  184. package/libx/_runtime/extensibility/uiflex/index.js +54 -0
  185. package/libx/_runtime/extensibility/uiflex/service.js +276 -0
  186. package/libx/_runtime/{fiori → extensibility}/uiflex/utils.js +22 -7
  187. package/libx/_runtime/fiori/generic/activate.js +2 -2
  188. package/libx/_runtime/fiori/generic/before.js +4 -4
  189. package/libx/_runtime/fiori/generic/new.js +3 -3
  190. package/libx/_runtime/fiori/generic/patch.js +1 -1
  191. package/libx/_runtime/fiori/generic/read.js +58 -66
  192. package/libx/_runtime/fiori/generic/readOverDraft.js +74 -16
  193. package/libx/_runtime/fiori/utils/handler.js +6 -13
  194. package/libx/_runtime/fiori/utils/where.js +6 -5
  195. package/libx/_runtime/hana/Service.js +4 -10
  196. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +1 -1
  197. package/libx/_runtime/hana/driver.js +2 -2
  198. package/libx/_runtime/hana/execute.js +45 -75
  199. package/libx/_runtime/hana/pool.js +1 -1
  200. package/libx/_runtime/hana/streaming.js +2 -1
  201. package/libx/_runtime/index.js +6 -6
  202. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +5 -21
  203. package/libx/_runtime/messaging/Outbox.js +2 -2
  204. package/libx/_runtime/messaging/common-utils/AMQPClient.js +4 -14
  205. package/libx/_runtime/messaging/common-utils/connections.js +5 -7
  206. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +30 -0
  207. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -1
  208. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +36 -30
  209. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -12
  210. package/libx/_runtime/messaging/enterprise-messaging.js +8 -8
  211. package/libx/_runtime/messaging/file-based.js +5 -5
  212. package/libx/_runtime/messaging/message-queuing.js +14 -12
  213. package/libx/_runtime/messaging/outbox/utils.js +18 -19
  214. package/libx/_runtime/messaging/redis-messaging.js +91 -0
  215. package/libx/_runtime/messaging/service.js +8 -6
  216. package/libx/_runtime/remote/Service.js +44 -8
  217. package/libx/_runtime/remote/utils/client.js +24 -19
  218. package/libx/_runtime/remote/utils/data.js +11 -11
  219. package/libx/_runtime/sqlite/Service.js +6 -9
  220. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +5 -2
  221. package/libx/_runtime/types/api.js +10 -2
  222. package/libx/common/utils/ucsn.js +109 -0
  223. package/libx/gql/resolvers/crud/update.js +5 -0
  224. package/libx/gql/resolvers/parse/ast2cqn/columns.js +3 -1
  225. package/libx/gql/schema/typeDefMap.js +2 -2
  226. package/libx/odata/afterburner.js +110 -16
  227. package/libx/odata/cqn2odata.js +24 -27
  228. package/libx/odata/grammar.pegjs +9 -1
  229. package/libx/odata/parseToCqn.js +39 -0
  230. package/libx/odata/parser.js +1 -1
  231. package/libx/rest/RestAdapter.js +9 -1
  232. package/libx/rest/middleware/input.js +54 -0
  233. package/libx/rest/middleware/operation.js +14 -1
  234. package/libx/rest/middleware/parse.js +11 -7
  235. package/package.json +2 -2
  236. package/server.js +34 -19
  237. package/srv/audit-log.cds +2 -2
  238. package/srv/flex.cds +8 -2
  239. package/srv/flex.js +1 -1
  240. package/srv/mps.cds +23 -0
  241. package/srv/mps.js +1 -0
  242. package/libx/_runtime/auth/strategies/utils/uaa.js +0 -21
  243. package/libx/_runtime/common/generic/auth.js +0 -874
  244. package/libx/_runtime/common/toggles/alpha.js +0 -43
  245. package/libx/_runtime/db/generic/arrayed.js +0 -33
  246. package/libx/_runtime/fiori/uiflex/index.js +0 -35
  247. package/libx/_runtime/fiori/uiflex/service.js +0 -150
  248. package/libx/rest/utils/data.js +0 -60
@@ -3,8 +3,6 @@ const LOG = cds.log('remote')
3
3
 
4
4
  const SANITIZE_VALUES = process.env.NODE_ENV === 'production' && cds.env.log.sanitize_values !== false
5
5
 
6
- const cdsLocale = require('../../../../lib/req/locale')
7
-
8
6
  const { convertV2ResponseData, deepSanitize, convertV2PayloadData } = require('./data')
9
7
 
10
8
  let _cloudSdkCore
@@ -249,10 +247,11 @@ const run = async (
249
247
 
250
248
  const sanitizedError = _getSanitizedError(e, requestConfig, suppressRemoteResponseBody)
251
249
 
252
- LOG._warn && LOG.warn(sanitizedError)
253
-
254
250
  // REVISIT: switch from innererror to reason in cds^6
255
- throw Object.assign(new Error(e.message), { statusCode: 502, innererror: sanitizedError })
251
+ const err = Object.assign(new Error(e.message), { statusCode: 502, innererror: sanitizedError })
252
+ LOG._warn && LOG.warn(err)
253
+
254
+ throw err
256
255
  }
257
256
 
258
257
  // text/html indicates a redirect -> reject
@@ -271,13 +270,15 @@ const run = async (
271
270
 
272
271
  const sanitizedError = _getSanitizedError(e, requestConfig, suppressRemoteResponseBody)
273
272
 
274
- LOG._warn && LOG.warn(sanitizedError)
275
-
276
273
  // REVISIT: switch from innererror to reason in cds^6
277
- throw Object.assign(new Error(`Error during request to remote service: ${e.message}`), {
274
+ const err = Object.assign(new Error(`Error during request to remote service: ${e.message}`), {
278
275
  statusCode: 502,
279
276
  innererror: sanitizedError
280
277
  })
278
+
279
+ LOG._warn && LOG.warn(err)
280
+
281
+ throw err
281
282
  }
282
283
 
283
284
  // get result of $batch
@@ -300,10 +301,13 @@ const run = async (
300
301
  ? 'Error during request to remote service: ' + contentJSON.message
301
302
  : 'Request to remote service failed.'
302
303
  const sanitizedError = _getSanitizedError(contentJSON, requestConfig)
303
- LOG._warn && LOG.warn(sanitizedError)
304
+
305
+ const err = Object.assign(new Error(contentJSON.message), { statusCode: 502, innererror: sanitizedError })
306
+
307
+ LOG._warn && LOG.warn(err)
304
308
 
305
309
  // REVISIT: switch from innererror to reason in cds^6
306
- throw Object.assign(new Error(contentJSON.message), { statusCode: 502, innererror: sanitizedError })
310
+ throw err
307
311
  }
308
312
  }
309
313
 
@@ -335,12 +339,10 @@ const _cqnToReqOptions = (query, kind, model, target) => {
335
339
  const queryObject = cds.odata.urlify(query, { kind, model })
336
340
  const reqOptions = {
337
341
  method: queryObject.method,
338
- url: encodeURI(
339
- queryObject.path
340
- // ugly workaround for Okra not allowing spaces in ( x eq 1 )
341
- .replace(/\( /g, '(')
342
- .replace(/ \)/g, ')')
343
- )
342
+ url: queryObject.path
343
+ // ugly workaround for Okra not allowing spaces in ( x eq 1 )
344
+ .replace(/\( /g, '(')
345
+ .replace(/ \)/g, ')')
344
346
  }
345
347
  if (queryObject.method !== 'GET' && queryObject.method !== 'HEAD') {
346
348
  reqOptions.data = kind === 'odata-v2' ? convertV2PayloadData(queryObject.body, target) : queryObject.body
@@ -399,9 +401,7 @@ const getReqOptions = (req, query, service) => {
399
401
  if (!_hasHeader(req.headers, 'accept-language')) {
400
402
  // Forward the locale properties from the original request (including region variants or weight factors),
401
403
  // if not given, it's taken from the user's locale (normalized and simplified)
402
- const locale =
403
- (req.context && req.context._ && req.context._.req && cdsLocale.from_req(req.context._.req)) ||
404
- (req.user && req.user.locale)
404
+ const locale = req._locale
405
405
  if (locale) reqOptions.headers['accept-language'] = locale
406
406
  }
407
407
 
@@ -442,6 +442,11 @@ const getReqOptions = (req, query, service) => {
442
442
 
443
443
  if (service.path) reqOptions.url = `${encodeURI(service.path)}${reqOptions.url}`
444
444
 
445
+ // set axios responseType to 'arraybuffer' if returning binary in rest
446
+ if (req._binary) {
447
+ reqOptions.responseType = 'arraybuffer'
448
+ }
449
+
445
450
  return reqOptions
446
451
  }
447
452
 
@@ -60,15 +60,15 @@ const _convertValue = (ieee754Compatible, exponentialDecimals) => (value, elemen
60
60
 
61
61
  const type = _elementType(element)
62
62
 
63
- if (['cds.Boolean'].includes(type)) {
63
+ if (type === 'cds.Boolean') {
64
64
  if (value === 'true') {
65
65
  value = true
66
66
  } else if (value === 'false') {
67
67
  value = false
68
68
  }
69
- } else if (['cds.Integer'].includes(type)) {
69
+ } else if (type === 'cds.Integer') {
70
70
  value = parseInt(value, 10)
71
- } else if (['cds.Decimal', 'cds.Integer64', 'cds.DecimalFloat'].includes(type)) {
71
+ } else if (type === 'cds.Decimal' || type === 'cds.DecimalFloat' || type === 'cds.Integer64') {
72
72
  const bigValue = big(value)
73
73
  if (ieee754Compatible) {
74
74
  // TODO test with arrayed => element.items.scale?
@@ -77,15 +77,15 @@ const _convertValue = (ieee754Compatible, exponentialDecimals) => (value, elemen
77
77
  // OData V2 does not even mention ieee754Compatible, but V4 requires JSON number if ieee754Compatible=false
78
78
  value = bigValue.toNumber()
79
79
  }
80
- } else if (['cds.Double'].includes(type)) {
80
+ } else if (type === 'cds.Double') {
81
81
  value = parseFloat(value)
82
- } else if (['cds.Time'].includes(type)) {
82
+ } else if (type === 'cds.Time') {
83
83
  const match = value.match(DurationRegex)
84
84
 
85
85
  if (match) {
86
86
  value = `${match[4] || '00'}:${match[5] || '00'}:${match[6] || '00'}`
87
87
  }
88
- } else if (['cds.Date', 'cds.DateTime', 'cds.Timestamp'].includes(type)) {
88
+ } else if (type === 'cds.Timestamp' || type === 'cds.DateTime' || type === 'cds.Date') {
89
89
  const match = value.match(/\/Date\((.*)\)\//)
90
90
  const ticksAndOffset = match && match.pop()
91
91
 
@@ -93,9 +93,9 @@ const _convertValue = (ieee754Compatible, exponentialDecimals) => (value, elemen
93
93
  value = new Date(_calculateTicksOffsetSum(ticksAndOffset)).toISOString() // always UTC
94
94
  }
95
95
 
96
- if (['cds.DateTime'].includes(type)) {
96
+ if (type === 'cds.DateTime') {
97
97
  value = value.slice(0, 19) + 'Z' // Cut millis
98
- } else if (['cds.Date'].includes(type)) {
98
+ } else if (type === 'cds.Date') {
99
99
  value = value.slice(0, 10) // Cut time
100
100
  }
101
101
  }
@@ -136,15 +136,15 @@ const _elementType = element => {
136
136
  let type
137
137
 
138
138
  if (element) {
139
- type = element.type
139
+ type = element._type
140
140
 
141
141
  if (element['@odata.Type']) {
142
142
  const odataType = element['@odata.Type'].match(/\w+$/)
143
143
  type = (odataType && DataTypeOData[odataType[0]]) || type
144
144
  }
145
145
 
146
- if (!type && element.items && element.items.type) {
147
- type = element.items.type
146
+ if (!type && element.items && element.items._type) {
147
+ type = element.items._type
148
148
  }
149
149
  }
150
150
 
@@ -91,17 +91,12 @@ module.exports = class SQLiteDatabase extends DatabaseService {
91
91
 
92
92
  _registerAfterHandlers() {
93
93
  // REVISIT: after phase runs in parallel -> side effects possible!
94
- const { effective } = cds.env
94
+ const { effective, features } = cds.env
95
95
 
96
- if (effective.odata.structs) {
96
+ if (effective.odata.structs && !features.ucsn_struct_conversion) {
97
97
  // REVISIT: only register for entities that contain structured or navigation to it
98
98
  this.after(['READ'], '*', this._structured)
99
99
  }
100
-
101
- if (effective.odata.version !== 'v2') {
102
- // REVISIT: only register for entities that contain arrayed or navigation to it
103
- this.after(['READ'], '*', this._arrayed)
104
- }
105
100
  }
106
101
 
107
102
  /*
@@ -110,7 +105,9 @@ module.exports = class SQLiteDatabase extends DatabaseService {
110
105
  async acquire(arg) {
111
106
  // in non-multi-tenant scenarios, the default db should be returned regardless of arg
112
107
  let tenant = 'anonymous'
113
- if (this.options.multiTenant && arg) {
108
+ const isMultitenant = 'multiTenant' in this.options ? this.options.multiTenant : cds.env.requires.multitenancy
109
+ // REVISIT: there should already be compat for the multitenancy flag, why is it not working here?
110
+ if (isMultitenant && arg) {
114
111
  if (typeof arg === 'string') tenant = arg
115
112
  else tenant = arg.tenant || (arg.user && arg.user.tenant) || tenant // > REVISIT: remove fallback arg.user.tenant with cds^6
116
113
  }
@@ -120,7 +117,7 @@ module.exports = class SQLiteDatabase extends DatabaseService {
120
117
  const credentials = this.options.credentials || this.options || {}
121
118
  let dbUrl = credentials.database || credentials.url || credentials.host || ':memory:'
122
119
 
123
- if (this.options.multiTenant && dbUrl.endsWith('.db')) {
120
+ if (isMultitenant && dbUrl.endsWith('.db')) {
124
121
  dbUrl = dbUrl.split('.db')[0] + '_' + tenant + '.db'
125
122
  }
126
123
 
@@ -8,7 +8,10 @@ const dateTimeFunctions = new Map([
8
8
  ['hour', "'%H'"],
9
9
  ['minute', "'%M'"]
10
10
  ])
11
- const standadFunctions = ['locate', 'substring', 'to_date', 'to_time']
11
+ const STANDAD_FUNCTIONS_MAP = ['locate', 'substring', 'to_date', 'to_time'].reduce((acc, cur) => {
12
+ acc[cur] = 1
13
+ return acc
14
+ }, {})
12
15
 
13
16
  class CustomFunctionBuilder extends FunctionBuilder {
14
17
  get ExpressionBuilder() {
@@ -35,7 +38,7 @@ class CustomFunctionBuilder extends FunctionBuilder {
35
38
 
36
39
  if (dateTimeFunctions.has(functionName)) {
37
40
  this._timeFunction(functionName, args)
38
- } else if (standadFunctions.includes(functionName)) {
41
+ } else if (functionName in STANDAD_FUNCTIONS_MAP) {
39
42
  this._standardFunction(functionName, args)
40
43
  } else if (functionName === 'seconds_between') {
41
44
  this._secondsBetweenFunction(args)
@@ -49,7 +49,7 @@
49
49
  * @typedef {object} TemplateProcessorPathOptions
50
50
  * @property {object} [extraKeys]
51
51
  * @property {function} [rowKeysGenerator]
52
- * @property {string[]} [segments=[]] - Path segments to relate the error message.
52
+ * @property {pathSegment[]} [path=[]] - Path segments to relate the error message.
53
53
  * @property {boolean} [includeKeyValues=false] Indicates whether the key values are included in the path segments
54
54
  * The path segments are used to build the error target (a relative resource path)
55
55
  */
@@ -70,7 +70,14 @@
70
70
  * @property {object} element
71
71
  * @property {boolean} plain
72
72
  * @property {boolean} isRoot
73
- * @property {Array<String>} [pathSegments]
73
+ * @property {Array<pathSegment>} [path]
74
+ */
75
+
76
+ /**
77
+ * @typedef {object} pathSegment
78
+ * @property {string} key
79
+ * @property {number} idx
80
+ * @property {string} url
74
81
  */
75
82
 
76
83
  // Search
@@ -102,6 +109,7 @@
102
109
  * @typedef {object} cqn2cqn4sqlOptions
103
110
  * @property {boolean} [suppressSearch=false] Indicates whether the search handler is called.
104
111
  * @property {boolean} [_4fiori]
112
+ * @property {boolean} [_4db]
105
113
  * @property {import('../db/Service')} [service]
106
114
  */
107
115
 
@@ -0,0 +1,109 @@
1
+ const cds = require('../../_runtime/cds')
2
+ const getTemplate = require('../../_runtime/common/utils/template')
3
+ const templateProcessor = require('../../_runtime/common/utils/templateProcessor')
4
+ const IS_PROXY = Symbol('flat2structProxy')
5
+
6
+ const proxifyIfFlattened = (definition, payload) => {
7
+ if (!definition || !definition._flat2struct || payload == null || payload[IS_PROXY]) return payload
8
+ return Object.setPrototypeOf(
9
+ payload,
10
+ new Proxy(
11
+ {},
12
+ {
13
+ get: function (_, k, cur) {
14
+ if (k === IS_PROXY) return true
15
+ if (!definition._flat2struct[k]) return Reflect.get(...arguments)
16
+ const segments = definition._flat2struct[k]
17
+ for (let i = 0; i < segments.length - 1; i++) {
18
+ cur = cur[segments[i]]
19
+ if (!cur) return cur
20
+ }
21
+ return cur[segments[segments.length - 1]]
22
+ },
23
+ set: function (_, k, v, o) {
24
+ let cur = o
25
+ if (definition._flat2struct[k]) {
26
+ const segments = definition._flat2struct[k]
27
+ for (let i = 0; i < segments.length - 1; i++) {
28
+ if (!cur[segments[i]]) {
29
+ cur[segments[i]] = {}
30
+ }
31
+ cur = cur[segments[i]]
32
+ }
33
+ cur[segments[segments.length - 1]] = v
34
+ } else if (k === IS_PROXY) {
35
+ // do nothing
36
+ } else {
37
+ Reflect.set(...arguments)
38
+ }
39
+ return o
40
+ }
41
+ }
42
+ )
43
+ )
44
+ }
45
+
46
+ const _picker = element => {
47
+ if (Array.isArray(element)) return { category: 'flat leaf' }
48
+ if (element.isAssociation) return { category: 'node' }
49
+ }
50
+
51
+ const _processor = ({ row, key, plain: { category }, element }) => {
52
+ if (!(key in row)) return
53
+ if (category === 'node') {
54
+ row[key] = Array.isArray(row[key])
55
+ ? row[key].map(data => proxifyIfFlattened(element._target, data))
56
+ : proxifyIfFlattened(element._target, row[key])
57
+ } else if (category === 'flat leaf') {
58
+ const data = row[key]
59
+ delete row[key]
60
+ row[key] = data
61
+ }
62
+ }
63
+
64
+ const _cleanup = (row, definition, cleanupNull) => {
65
+ if (!row || !definition) return
66
+ const elements = definition.elements || definition.params
67
+ for (const key of Object.keys(row)) {
68
+ const element = elements[key]
69
+ if (!element) {
70
+ if (!definition['@open']) delete row[key]
71
+ continue
72
+ }
73
+ if (!row[key]) continue
74
+ if (element.isAssociation) {
75
+ if (element.is2many) {
76
+ for (const r of row[key]) {
77
+ _cleanup(r, element._target, cleanupNull)
78
+ }
79
+ } else {
80
+ _cleanup(row[key], element._target, cleanupNull)
81
+ }
82
+ } else if (element.elements) {
83
+ _cleanup(row[key], element, cleanupNull)
84
+ if (cleanupNull && Object.values(row[key]).every(v => v == null)) row[key] = null
85
+ }
86
+ }
87
+ }
88
+
89
+ function convertStructured(service, definition, data, { cleanupNull = false } = {}) {
90
+ if (!definition) return
91
+ // REVISIT check `structs` mode only for now as uCSN is not yet available
92
+ const flatAccess = cds.env.features.compat_flat_access
93
+ const template = getTemplate('universal-input', service, definition, { pick: _picker, flatAccess })
94
+ const arrayData = Array.isArray(data) ? data : [data]
95
+ if (template && template.elements.size) {
96
+ for (let i = 0; i < arrayData.length; i++) {
97
+ const row = proxifyIfFlattened(definition, arrayData[i])
98
+ templateProcessor({ processFn: _processor, row, template, pathOptions: { path: [] } })
99
+ }
100
+ }
101
+ for (const row of arrayData) {
102
+ _cleanup(row, definition, cleanupNull)
103
+ }
104
+ }
105
+
106
+ module.exports = {
107
+ convertStructured,
108
+ proxifyIfFlattened
109
+ }
@@ -29,6 +29,11 @@ module.exports = async (service, entityFQN, selection) => {
29
29
  cds.context = tx
30
30
  // read needs to be done before the update, otherwise the where clause might become invalid (case that properties in where clause are updated by the mutation)
31
31
  resultBeforeUpdate = await tx.run(queryBeforeUpdate)
32
+
33
+ if (resultBeforeUpdate.length === 0) {
34
+ return []
35
+ }
36
+
32
37
  return tx.run(query)
33
38
  })
34
39
 
@@ -14,7 +14,9 @@ const astToColumns = selections => {
14
14
  }
15
15
 
16
16
  if (selection.selectionSet && selection.selectionSet.selections) {
17
- column.expand = astToColumns(selection.selectionSet.selections)
17
+ const columns = astToColumns(selection.selectionSet.selections)
18
+ // columns is empty if only __typename was selected (which was filtered out in the enriched AST)
19
+ column.expand = columns.length > 0 ? columns : ['*']
18
20
  }
19
21
 
20
22
  const filter = getArgumentByName(selection.arguments, ARGUMENT.FILTER)
@@ -30,8 +30,8 @@ const servicesToTypeDefMap = services => {
30
30
  // TODO structured types
31
31
  continue
32
32
  } else {
33
- if (CDS_TO_GRAPHQL_TYPES[ele.type]) {
34
- def[ele.name] = CDS_TO_GRAPHQL_TYPES[ele.type]
33
+ if (CDS_TO_GRAPHQL_TYPES[ele._type]) {
34
+ def[ele.name] = CDS_TO_GRAPHQL_TYPES[ele._type]
35
35
  }
36
36
  // TODO aspects
37
37
  }
@@ -3,10 +3,20 @@ const cds = require('../_runtime/cds')
3
3
  const { where2obj } = require('../_runtime/common/utils/cqn')
4
4
  const { findCsnTargetFor } = require('../_runtime/common/utils/csn')
5
5
 
6
- const _addKeysDeep = (keys, keysCollector) => {
6
+ const _addKeysDeep = (keys, keysCollector, ignoreManagedBacklinks) => {
7
7
  for (const keyName in keys) {
8
8
  const key = keys[keyName]
9
- if (key.type === 'cds.Association' || key['@odata.foreignKey4'] === 'up_') continue
9
+ const foreignKey = key._foreignKey4
10
+ if (key.isAssociation || foreignKey === 'up_' || key['@cds.api.ignore'] === true) continue
11
+
12
+ if (ignoreManagedBacklinks && foreignKey) {
13
+ const navigationElement = keys[foreignKey]
14
+ if (!navigationElement.on && navigationElement._isBacklink) {
15
+ // skip navigation elements that are backlinks
16
+ continue
17
+ }
18
+ }
19
+
10
20
  if ('elements' in key) {
11
21
  _addKeysDeep(key.elements, keysCollector)
12
22
  continue
@@ -15,10 +25,10 @@ const _addKeysDeep = (keys, keysCollector) => {
15
25
  }
16
26
  }
17
27
 
18
- function _keysOf(entity) {
28
+ function _keysOf(entity, ignoreManagedBacklinks) {
19
29
  if (!entity || !entity.keys) return
20
30
  const keysCollector = []
21
- _addKeysDeep(entity.keys, keysCollector)
31
+ _addKeysDeep(entity.keys, keysCollector, ignoreManagedBacklinks)
22
32
  return keysCollector
23
33
  }
24
34
 
@@ -31,18 +41,47 @@ function _getDefinition(definition, name, namespace) {
31
41
  )
32
42
  }
33
43
 
44
+ function _resolveAliasInParams(params, entity) {
45
+ if (!entity._alias2ref) return
46
+ const paramKeys = Object.keys(params)
47
+ for (const paramKey of paramKeys) {
48
+ if (entity._alias2ref[paramKey]) {
49
+ params[entity._alias2ref[paramKey].join('_')] = params[paramKey]
50
+ params[paramKey] = undefined
51
+ }
52
+ }
53
+ }
54
+
34
55
  function _resolveAliasInWhere(where, entity) {
35
56
  if (!entity._alias2ref) return
36
- for (let i = 0; i < where.length; i++) {
37
- if (!where[i].ref || where[i].ref.length > 1 || entity.keys[where[i].ref[0]]) continue
38
- where[i].ref = entity._alias2ref[where[i].ref[0]] || where[i].ref
57
+ for (const w of where) {
58
+ if (!w.ref || w.ref.length > 1 || entity.keys[w.ref[0]]) continue
59
+ w.ref = entity._alias2ref[w.ref[0]] || w.ref
60
+ }
61
+ }
62
+
63
+ function _addDefaultParams(ref, view) {
64
+ const params = view.params
65
+ const defaults = params && Object.values(params).filter(p => p.default)
66
+ if (defaults && defaults.length > 0) {
67
+ if (!ref.where) ref.where = []
68
+ for (const def of defaults) {
69
+ if (ref.where.find(e => e.ref && e.ref[0] === def.name)) {
70
+ continue
71
+ }
72
+ if (ref.where.length > 0) ref.where.push('and')
73
+ ref.where.push({ ref: [def.name] }, '=', { val: def.default.val })
74
+ }
39
75
  }
40
76
  }
41
77
 
42
78
  // case: single key without name, e.g., Foo(1)
43
79
  function addRefToWhereIfNecessary(where, entity) {
44
80
  if (!where || where.length !== 1) return 0
45
- const keys = _keysOf(entity)
81
+
82
+ const isView = !!entity.params
83
+
84
+ const keys = isView ? Object.keys(entity.params) : _keysOf(entity)
46
85
  if (keys.length !== 1) return 0
47
86
  where.unshift(...[{ ref: [keys[0]] }, '='])
48
87
  return 1
@@ -64,13 +103,14 @@ function _processSegments(cqn, model, namespace) {
64
103
 
65
104
  if (incompleteKeys) {
66
105
  // > key
67
- keys = keys || _keysOf(current)
68
- const key = keys[keyCount++]
106
+ keys = keys || _keysOf(current, !cds.env.features.rest_new_adapter && !cds.env.features.rest_new_parser) // if odata skip backlinks as key as they are used from structure
107
+ let key = keys[keyCount++]
69
108
  one = true
70
109
  const element = current.elements[key]
71
110
  let base = ref[i - keyCount]
72
111
  if (!base.id) base = { id: base, where: [] }
73
112
  if (base.where.length) base.where.push('and')
113
+
74
114
  if (ref[i].id) {
75
115
  // > fix case key value parsed to collection with filter
76
116
  const val = `${ref[i].id}(${Object.keys(params)
@@ -78,7 +118,7 @@ function _processSegments(cqn, model, namespace) {
78
118
  .join(',')})`
79
119
  base.where.push({ ref: [key] }, '=', { val })
80
120
  } else {
81
- base.where.push({ ref: [key] }, '=', { val: element.type === 'cds.Integer' ? Number(seg) : seg })
121
+ base.where.push({ ref: [key] }, '=', { val: element._type === 'cds.Integer' ? Number(seg) : seg })
82
122
  }
83
123
  ref[i] = null
84
124
  ref[i - keyCount] = base
@@ -95,13 +135,40 @@ function _processSegments(cqn, model, namespace) {
95
135
  // REVISIT: 404 or 400?
96
136
  if (!current) cds.error(`Invalid resource path "${path}"`, { code: 404 })
97
137
 
98
- if (current.kind === 'entity') {
138
+ if (current.params && current.kind === 'entity') {
139
+ // > View with params
140
+ if (ref[i].where) {
141
+ keyCount += addRefToWhereIfNecessary(ref[i].where, current)
142
+ _resolveAliasInWhere(ref[i].where, current)
143
+ _resolveAliasInParams(params, current)
144
+ }
145
+
146
+ _addDefaultParams(ref[i], current)
147
+ if ((!params || !Object.keys(params).length) && ref[i].where) params = where2obj(ref[i].where)
148
+
149
+ _checkAllKeysProvided(params, current)
150
+
151
+ ref[i].args = {}
152
+ for (let j = 0; j < ref[i].where.length; j++) {
153
+ const w = ref[i].where[j]
154
+ if (w === 'and' || !w.ref) continue
155
+ ref[i].args[w.ref[0]] = ref[i].where[j + 2]
156
+ j += 2
157
+ }
158
+ ref[i].where = undefined
159
+ if (ref[i + 1] !== 'Set') {
160
+ // /Set is missing
161
+ throw new Error(`Incorrect call to a view with parameter "${current.name}"`)
162
+ }
163
+ ref[++i] = null
164
+ } else if (current.kind === 'entity') {
99
165
  // > entity
100
166
  one = !!(ref[i].where || current._isSingleton)
101
167
  incompleteKeys = ref[i].where ? false : i === ref.length - 1 || one ? false : true
102
168
  if (ref[i].where) {
103
169
  keyCount += addRefToWhereIfNecessary(ref[i].where, current)
104
170
  _resolveAliasInWhere(ref[i].where, current)
171
+ _resolveAliasInParams(params, current)
105
172
  // in case of Foo(1), params will be {} (before addRefToWhereIfNecessary was called)
106
173
  if (!Object.keys(params).length) params = where2obj(ref[i].where)
107
174
  _checkAllKeysProvided(params, current)
@@ -114,7 +181,7 @@ function _processSegments(cqn, model, namespace) {
114
181
  }
115
182
  ref[i] = { operation: current.name }
116
183
  if (params) ref[i].args = params
117
- if (current.returns && current.returns.type) one = true
184
+ if (current.returns && current.returns._type) one = true
118
185
  } else if (current.isAssociation) {
119
186
  // > navigation
120
187
  one = !!(current.is2one || ref[i].where)
@@ -161,11 +228,38 @@ function _processSegments(cqn, model, namespace) {
161
228
  const _resolveFrom = from => (from.SELECT ? _resolveFrom(from.SELECT.from) : from)
162
229
 
163
230
  const _checkAllKeysProvided = (params, entity) => {
164
- const keysOfEntity = _keysOf(entity)
231
+ let keysOfEntity
232
+ const isView = !!entity.params
233
+ if (isView) {
234
+ // view with params
235
+ if (params === undefined) {
236
+ throw new Error(`Incorrect call to a view with parameter "${entity.name}"`)
237
+ } else if (Object.keys(params).length === 0) {
238
+ throw new Error('KEY_EXPECTED')
239
+ }
240
+
241
+ keysOfEntity = Object.keys(entity.params)
242
+ } else {
243
+ keysOfEntity = _keysOf(entity)
244
+ }
245
+
165
246
  if (!keysOfEntity) return
166
247
  for (const keyOfEntity of keysOfEntity) {
167
- if (!(keyOfEntity in params))
168
- throw Object.assign(new Error(`Key "${keyOfEntity}" is missing for entity "${entity.name}"`), { status: 400 })
248
+ if (!(keyOfEntity in params)) {
249
+ if (isView && entity.params[keyOfEntity].default) {
250
+ // will be added later?
251
+ continue
252
+ }
253
+
254
+ throw Object.assign(
255
+ new Error(
256
+ `${isView ? 'Parameter' : 'Key'} "${keyOfEntity}" is missing for ${isView ? 'view' : 'entity'} "${
257
+ entity.name
258
+ }"`
259
+ ),
260
+ { status: 400 }
261
+ )
262
+ }
169
263
  }
170
264
  }
171
265