@sap/cds 7.9.2 → 8.0.3

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 (279) hide show
  1. package/CHANGELOG.md +139 -3656
  2. package/_i18n/i18n_en_US_saptrc.properties +113 -0
  3. package/_i18n/i18n_zh_CN.properties +7 -4
  4. package/app/index.css +129 -0
  5. package/app/index.html +16 -64
  6. package/app/index.js +14 -9
  7. package/bin/args.js +34 -0
  8. package/bin/serve.js +18 -24
  9. package/bin/test.js +97 -0
  10. package/common.cds +5 -12
  11. package/eslint.config.mjs +133 -0
  12. package/lib/auth/basic-auth.js +16 -20
  13. package/lib/auth/dummy-auth.js +1 -1
  14. package/lib/auth/ias-auth.js +12 -30
  15. package/lib/auth/index.js +1 -14
  16. package/lib/auth/jwt-auth.js +14 -30
  17. package/lib/compile/cds-compile.js +1 -2
  18. package/lib/compile/cdsc.js +21 -26
  19. package/lib/compile/etc/_localized.js +1 -6
  20. package/lib/compile/etc/csv.js +1 -1
  21. package/lib/compile/etc/properties.js +1 -1
  22. package/lib/compile/for/java.js +1 -1
  23. package/lib/compile/for/lean_drafts.js +4 -6
  24. package/lib/compile/for/nodejs.js +1 -1
  25. package/lib/compile/parse.js +4 -0
  26. package/lib/compile/resolve.js +4 -4
  27. package/lib/compile/to/edm-files.js +16 -23
  28. package/lib/compile/to/hana.js +27 -0
  29. package/lib/compile/to/json.js +1 -1
  30. package/lib/compile/to/sql.js +5 -1
  31. package/lib/compile/to/srvinfo.js +1 -1
  32. package/lib/compile/to/yaml.js +3 -3
  33. package/lib/dbs/cds-deploy.js +4 -2
  34. package/lib/env/cds-env.js +10 -14
  35. package/lib/env/cds-requires.js +29 -13
  36. package/lib/env/defaults.js +46 -16
  37. package/lib/env/plugins.js +1 -1
  38. package/lib/env/schemas/cds-rc.js +8 -4
  39. package/lib/env/schemas/index.js +7 -7
  40. package/lib/env/serviceBindings.js +1 -1
  41. package/lib/index.js +12 -10
  42. package/lib/lazy.js +1 -1
  43. package/lib/linked/classes.js +36 -8
  44. package/lib/linked/entities.js +2 -10
  45. package/lib/linked/models.js +2 -1
  46. package/lib/linked/validate.js +292 -0
  47. package/lib/log/cds-error.js +0 -6
  48. package/lib/log/cds-log.js +3 -3
  49. package/lib/log/format/json.js +1 -1
  50. package/lib/log/service/index.js +0 -1
  51. package/lib/plugins.js +3 -3
  52. package/lib/ql/Query.js +2 -10
  53. package/lib/ql/SELECT.js +1 -1
  54. package/lib/ql/Whereable.js +3 -2
  55. package/lib/req/cds-context.js +14 -25
  56. package/lib/req/context.js +23 -25
  57. package/lib/req/request.js +1 -34
  58. package/lib/req/user.js +47 -35
  59. package/lib/srv/bindings.js +1 -1
  60. package/lib/srv/cds-connect.js +4 -4
  61. package/lib/srv/cds-serve.js +2 -2
  62. package/lib/srv/factory.js +1 -1
  63. package/lib/srv/middlewares/cds-context.js +11 -22
  64. package/lib/srv/middlewares/ctx-model.js +2 -3
  65. package/lib/srv/middlewares/errors.js +41 -8
  66. package/lib/srv/middlewares/index.js +3 -3
  67. package/lib/srv/middlewares/trace.js +0 -2
  68. package/lib/srv/protocols/hcql.js +15 -10
  69. package/lib/srv/protocols/http.js +44 -49
  70. package/lib/srv/protocols/index.js +1 -23
  71. package/lib/srv/protocols/odata-v4.js +12 -74
  72. package/lib/srv/protocols/rest.js +1 -13
  73. package/lib/srv/srv-api.js +0 -20
  74. package/lib/srv/srv-dispatch.js +3 -2
  75. package/lib/srv/srv-handlers.js +22 -11
  76. package/lib/srv/srv-methods.js +2 -2
  77. package/lib/srv/srv-models.js +3 -36
  78. package/lib/test/expect.js +343 -0
  79. package/lib/test/index.js +2 -0
  80. package/lib/test/reporter.js +176 -0
  81. package/lib/utils/axios.js +10 -9
  82. package/lib/utils/cds-test.js +86 -37
  83. package/lib/utils/cds-utils.js +54 -7
  84. package/lib/utils/check-version.js +0 -4
  85. package/lib/utils/colors.js +49 -0
  86. package/lib/utils/data.js +5 -4
  87. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -7
  88. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +3 -30
  89. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +6 -12
  90. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
  91. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -1
  92. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +4 -7
  93. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +12 -6
  94. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +2 -4
  95. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +1 -0
  96. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
  97. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
  98. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -3
  99. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
  100. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +1 -2
  101. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +5 -0
  102. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
  103. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +9 -43
  104. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +0 -1
  105. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +8 -3
  106. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +4 -2
  107. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -3
  108. package/libx/_runtime/cds-services/util/assert.js +1 -1
  109. package/libx/_runtime/cds.js +10 -3
  110. package/libx/_runtime/common/Service.js +12 -32
  111. package/libx/_runtime/common/aspects/any.js +1 -0
  112. package/libx/_runtime/common/code-ext/execute.js +1 -1
  113. package/libx/_runtime/common/code-ext/worker.js +0 -1
  114. package/libx/_runtime/common/composition/data.js +0 -1
  115. package/libx/_runtime/common/composition/delete.js +0 -1
  116. package/libx/_runtime/common/composition/insert.js +2 -2
  117. package/libx/_runtime/common/composition/tree.js +0 -1
  118. package/libx/_runtime/common/composition/update.js +3 -3
  119. package/libx/_runtime/common/error/frontend.js +21 -12
  120. package/libx/_runtime/common/error/log.js +36 -0
  121. package/libx/_runtime/common/error/utils.js +2 -5
  122. package/libx/_runtime/common/generic/auth/autoexpose.js +18 -17
  123. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  124. package/libx/_runtime/common/generic/auth/readOnly.js +1 -2
  125. package/libx/_runtime/common/generic/auth/restrict.js +23 -42
  126. package/libx/_runtime/common/generic/auth/restrictions.js +2 -7
  127. package/libx/_runtime/common/generic/auth/utils.js +91 -88
  128. package/libx/_runtime/common/generic/crud.js +6 -5
  129. package/libx/_runtime/common/generic/etag.js +7 -12
  130. package/libx/_runtime/common/generic/input.js +70 -68
  131. package/libx/_runtime/common/generic/paging.js +1 -0
  132. package/libx/_runtime/common/generic/sorting.js +1 -0
  133. package/libx/_runtime/common/generic/temporal.js +8 -2
  134. package/libx/_runtime/common/i18n/index.js +1 -1
  135. package/libx/_runtime/common/i18n/messages.properties +3 -1
  136. package/libx/_runtime/common/utils/binary.js +8 -2
  137. package/libx/_runtime/common/utils/compareJson.js +5 -1
  138. package/libx/_runtime/common/utils/copy.js +6 -11
  139. package/libx/_runtime/common/utils/cqn2cqn4sql.js +16 -14
  140. package/libx/_runtime/common/utils/differ.js +3 -6
  141. package/libx/_runtime/common/utils/keys.js +77 -18
  142. package/libx/_runtime/common/utils/postProcess.js +12 -15
  143. package/libx/_runtime/common/utils/propagateForeignKeys.js +0 -1
  144. package/libx/_runtime/common/utils/resolveView.js +2 -3
  145. package/libx/_runtime/common/utils/restrictions.js +45 -17
  146. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -8
  147. package/libx/_runtime/common/utils/stream.js +3 -16
  148. package/libx/_runtime/common/utils/streamProp.js +8 -18
  149. package/libx/_runtime/common/utils/structured.js +1 -1
  150. package/libx/_runtime/common/utils/ucsn.js +0 -2
  151. package/libx/_runtime/db/Service.js +0 -72
  152. package/libx/_runtime/db/data-conversion/post-processing.js +0 -1
  153. package/libx/_runtime/db/expand/expandCQNToJoin.js +9 -9
  154. package/libx/_runtime/db/expand/rawToExpanded.js +0 -8
  155. package/libx/_runtime/db/generic/input.js +3 -8
  156. package/libx/_runtime/db/generic/rewrite.js +27 -4
  157. package/libx/_runtime/db/query/read.js +2 -2
  158. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -1
  159. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  160. package/libx/_runtime/db/utils/columns.js +2 -6
  161. package/libx/_runtime/fiori/lean-draft.js +138 -56
  162. package/libx/_runtime/hana/Service.js +0 -1
  163. package/libx/_runtime/hana/driver.js +1 -1
  164. package/libx/_runtime/hana/dynatrace.js +1 -2
  165. package/libx/_runtime/hana/pool.js +11 -21
  166. package/libx/_runtime/hana/streaming.js +0 -1
  167. package/libx/_runtime/messaging/common-utils/AMQPClient.js +0 -1
  168. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  169. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
  170. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -1
  171. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -33
  172. package/libx/_runtime/messaging/event-broker.js +0 -12
  173. package/libx/_runtime/messaging/file-based.js +3 -3
  174. package/libx/_runtime/messaging/http-utils/token.js +1 -1
  175. package/libx/_runtime/messaging/kafka.js +2 -2
  176. package/libx/_runtime/messaging/redis-messaging.js +0 -1
  177. package/libx/_runtime/remote/Service.js +25 -25
  178. package/libx/_runtime/remote/utils/client.js +4 -5
  179. package/libx/_runtime/remote/utils/cloudSdkProvider.js +0 -3
  180. package/libx/_runtime/remote/utils/data.js +0 -1
  181. package/libx/_runtime/sqlite/Service.js +1 -2
  182. package/libx/_runtime/ucl/Service.js +37 -78
  183. package/libx/common/assert/index.js +22 -21
  184. package/libx/common/assert/type-relaxed.js +39 -0
  185. package/libx/common/assert/utils.js +3 -2
  186. package/libx/common/assert/validation.js +3 -8
  187. package/libx/common/utils/index.js +5 -0
  188. package/libx/common/utils/path.js +51 -0
  189. package/libx/odata/ODataAdapter.js +126 -0
  190. package/libx/odata/index.js +15 -2
  191. package/libx/odata/middleware/batch.js +261 -72
  192. package/libx/odata/middleware/body-parser.js +33 -0
  193. package/libx/odata/middleware/create.js +44 -59
  194. package/libx/odata/middleware/delete.js +23 -12
  195. package/libx/odata/middleware/error.js +30 -6
  196. package/libx/odata/middleware/metadata.js +38 -26
  197. package/libx/odata/middleware/operation.js +93 -69
  198. package/libx/odata/middleware/parse.js +6 -8
  199. package/libx/odata/middleware/read.js +117 -93
  200. package/libx/odata/middleware/service-document.js +22 -19
  201. package/libx/odata/middleware/stream.js +54 -56
  202. package/libx/odata/middleware/update.js +79 -87
  203. package/libx/odata/parse/afterburner.js +191 -175
  204. package/libx/odata/parse/cqn2odata.js +8 -8
  205. package/libx/odata/parse/grammar.peggy +27 -20
  206. package/libx/odata/parse/multipartToJson.js +17 -9
  207. package/libx/odata/parse/parser.js +1 -1
  208. package/libx/odata/utils/etag.js +14 -6
  209. package/libx/odata/utils/index.js +84 -12
  210. package/libx/odata/utils/metadata.js +161 -0
  211. package/libx/odata/utils/postProcess.js +89 -0
  212. package/libx/odata/utils/readAfterWrite.js +134 -17
  213. package/libx/odata/utils/result.js +36 -142
  214. package/libx/outbox/index.js +5 -4
  215. package/libx/rest/RestAdapter.js +115 -182
  216. package/libx/rest/middleware/create.js +28 -24
  217. package/libx/rest/middleware/delete.js +7 -10
  218. package/libx/rest/middleware/error.js +19 -16
  219. package/libx/rest/middleware/operation.js +48 -41
  220. package/libx/rest/middleware/parse.js +128 -126
  221. package/libx/rest/middleware/read.js +20 -27
  222. package/libx/rest/middleware/update.js +26 -31
  223. package/package.json +16 -12
  224. package/server.js +4 -2
  225. package/tasks/enterprise-messaging-deploy.js +1 -1
  226. package/apis/cds.d.ts +0 -3
  227. package/apis/core.d.ts +0 -21
  228. package/apis/cqn.d.ts +0 -18
  229. package/apis/csn.d.ts +0 -21
  230. package/apis/events.d.ts +0 -18
  231. package/apis/internal/inference.d.ts +0 -18
  232. package/apis/linked.d.ts +0 -18
  233. package/apis/log.d.ts +0 -20
  234. package/apis/models.d.ts +0 -18
  235. package/apis/ql.d.ts +0 -18
  236. package/apis/reflect.d.ts +0 -32
  237. package/apis/server.d.ts +0 -18
  238. package/apis/services.d.ts +0 -22
  239. package/bin/cds-serve.js +0 -56
  240. package/lib/compile/to/gql.js +0 -15
  241. package/lib/srv/protocols/_legacy.js +0 -44
  242. package/lib/utils/jest.js +0 -43
  243. package/libx/_runtime/auth/index.js +0 -193
  244. package/libx/_runtime/auth/strategies/JWT.js +0 -37
  245. package/libx/_runtime/auth/strategies/basic.js +0 -20
  246. package/libx/_runtime/auth/strategies/dummy.js +0 -14
  247. package/libx/_runtime/auth/strategies/ias-auth.js +0 -1
  248. package/libx/_runtime/auth/strategies/mock.js +0 -77
  249. package/libx/_runtime/auth/strategies/xssecUtils.js +0 -93
  250. package/libx/_runtime/auth/strategies/xsuaa.js +0 -38
  251. package/libx/_runtime/common/perf/index.js +0 -19
  252. package/libx/_runtime/common/utils/ensureIEEE754.js +0 -29
  253. package/libx/_runtime/fiori/draft.js +0 -2
  254. package/libx/_runtime/fiori/generic/activate.js +0 -190
  255. package/libx/_runtime/fiori/generic/before.js +0 -201
  256. package/libx/_runtime/fiori/generic/cancel.js +0 -19
  257. package/libx/_runtime/fiori/generic/delete.js +0 -21
  258. package/libx/_runtime/fiori/generic/edit.js +0 -157
  259. package/libx/_runtime/fiori/generic/index.js +0 -25
  260. package/libx/_runtime/fiori/generic/new.js +0 -82
  261. package/libx/_runtime/fiori/generic/patch.js +0 -101
  262. package/libx/_runtime/fiori/generic/prepare.js +0 -57
  263. package/libx/_runtime/fiori/generic/read.js +0 -1340
  264. package/libx/_runtime/fiori/generic/readOverDraft.js +0 -146
  265. package/libx/_runtime/fiori/utils/csn.js +0 -13
  266. package/libx/_runtime/fiori/utils/delete.js +0 -114
  267. package/libx/_runtime/fiori/utils/handler.js +0 -264
  268. package/libx/_runtime/fiori/utils/lockInfo.js +0 -27
  269. package/libx/_runtime/fiori/utils/req.js +0 -23
  270. package/libx/_runtime/fiori/utils/stream.js +0 -36
  271. package/libx/_runtime/fiori/utils/where.js +0 -254
  272. package/libx/_runtime/index.js +0 -22
  273. package/libx/odata/utils/handler.js +0 -120
  274. package/libx/odata/utils/metaInfo.js +0 -410
  275. package/libx/odata/utils/path.js +0 -75
  276. package/libx/rest/RestRequest.js +0 -32
  277. package/libx/rest/index.js +0 -3
  278. package/libx/rest/readme.md +0 -1
  279. /package/libx/common/assert/{type.js → type-strict.js} +0 -0
@@ -1,7 +1,6 @@
1
1
  // TODO: split into multiple files
2
2
 
3
3
  const cds = require('../../../')
4
- const _path = require('./path')
5
4
  const _etag = require('./etag')
6
5
 
7
6
  const { toBase64url } = require('../../_runtime/common/utils/binary')
@@ -227,7 +226,7 @@ const skipToken = (token, cqn) => {
227
226
  let decoded
228
227
  try {
229
228
  decoded = JSON.parse(Buffer.from(token, 'base64').toString())
230
- } catch (err) {
229
+ } catch {
231
230
  LOG.warn('$skiptoken is not in expected format. Ignoring it.')
232
231
  return
233
232
  }
@@ -277,35 +276,103 @@ const skipToken = (token, cqn) => {
277
276
  }
278
277
 
279
278
  const calculateLocationHeader = (target, srv, result) => {
280
- const targetName = target.name.replace(`${srv.name}.`, '')
281
-
282
- const keyValuePairs = Object.keys(target.keys).reduce((acc, key) => {
279
+ const targetName = target.name.replace(`${srv.definition.name}.`, '')
280
+ const filteredKeys = target.keys.filter(k => !k.isAssociation).map(k => k.name)
281
+ const keyValuePairs = filteredKeys.reduce((acc, key) => {
283
282
  const value = result[key]
283
+ if (value === undefined) return
284
284
  if (Buffer.isBuffer(value)) {
285
285
  acc[key] = value.toString('base64')
286
286
  } else {
287
- acc[key] = value
287
+ const _type = target.elements[key]._type
288
+ if (typeof value === 'string' && _type !== 'cds.UUID') acc[key] = `'${value}'`
289
+ else acc[key] = value
288
290
  }
289
291
  return acc
290
292
  }, {})
291
-
293
+ if (!keyValuePairs) return
292
294
  let keys
293
295
  const entries = Object.entries(keyValuePairs)
294
296
  if (entries.length === 1) {
295
297
  keys = entries[0][1]
298
+ if (target.elements[entries[0][0]]['@odata.Type'] === 'Edm.String') keys = `'${keys}'`
296
299
  } else {
297
- keys = entries.map(([key, value]) => `${key}=${value}`).join(',')
300
+ keys = entries
301
+ .map(([key, value]) => `${key}=${target.elements[key]['@odata.Type'] === 'Edm.String' ? `'${value}'` : value}`)
302
+ .join(',')
298
303
  }
299
-
300
304
  return `${targetName}(${keys})`
301
305
  }
302
306
 
303
307
  const handleSapMessages = (cdsReq, req, res) => {
304
- if (!cdsReq.messages || !cdsReq.messages.length) return
308
+ if (res.headersSent || !cdsReq.messages || !cdsReq.messages.length) return
305
309
  const msgs = getSapMessages(cdsReq.messages, req)
306
310
  if (msgs) res.setHeader('sap-messages', msgs)
307
311
  }
308
312
 
313
+ const isStream = query => {
314
+ const { _propertyAccess, target } = query
315
+ if (!_propertyAccess) return
316
+
317
+ const element = target.elements[_propertyAccess]
318
+ return element._type === 'cds.LargeBinary' && element['@Core.MediaType']
319
+ }
320
+
321
+ const isRedirect = query => {
322
+ const { _propertyAccess, target } = query
323
+ if (!_propertyAccess) return
324
+
325
+ const element = target.elements[_propertyAccess]
326
+ return element._type === 'cds.String' && element['@Core.MediaType'] && element['@Core.IsURL']
327
+ }
328
+
329
+ const _addKeysDeep = (keys, keysCollector, ignoreManagedBackLinks) => {
330
+ for (const keyName in keys) {
331
+ const key = keys[keyName]
332
+ const foreignKey = key._foreignKey4
333
+ if (key.isAssociation || foreignKey === 'up_' || key['@cds.api.ignore'] === true) continue
334
+
335
+ if (ignoreManagedBackLinks && foreignKey) {
336
+ const navigationElement = keys[foreignKey]
337
+ if (!navigationElement.on && navigationElement._isBacklink) {
338
+ // skip navigation elements that are backlinks
339
+ continue
340
+ }
341
+ }
342
+
343
+ if ('elements' in key) {
344
+ _addKeysDeep(key.elements, keysCollector)
345
+ continue
346
+ }
347
+
348
+ keysCollector.push(keyName)
349
+ }
350
+ }
351
+
352
+ function keysOf(entity, ignoreManagedBackLinks) {
353
+ const keysCollector = []
354
+ if (!entity || !entity.keys) return keysCollector
355
+
356
+ _addKeysDeep(entity.keys, keysCollector, ignoreManagedBackLinks)
357
+ return keysCollector
358
+ }
359
+
360
+ // case: single key without name, e.g., Foo(1)
361
+ function addRefToWhereIfNecessary(where, entity) {
362
+ if (!where || where.length !== 1) return 0
363
+
364
+ const isView = !!entity.params
365
+ const keys = isView ? Object.keys(entity.params) : keysOf(entity)
366
+
367
+ if (keys.length !== 1) return 0
368
+ where.unshift(...[{ ref: [keys[0]] }, '='])
369
+ return 1
370
+ }
371
+
372
+ function getBoundary(req) {
373
+ return req.headers['content-type']?.match(/boundary=([\d\w'()+_,\-./:=?]{1,70})/i)?.[1]
374
+ }
375
+
309
376
  module.exports = {
310
377
  cds2edm,
311
378
  getSafeNumber,
@@ -313,7 +380,12 @@ module.exports = {
313
380
  skipToken,
314
381
  calculateLocationHeader,
315
382
  handleSapMessages,
316
- getKeysAndParamsFromPath: _path.getKeysAndParamsFromPath,
317
383
  getPreferReturnHeader,
318
- validateIfNoneMatch: _etag.validateIfNoneMatch
384
+ isStream,
385
+ isRedirect,
386
+ keysOf,
387
+ addRefToWhereIfNecessary,
388
+ validateIfNoneMatch: _etag.validateIfNoneMatch,
389
+ extractIfNoneMatch: _etag.extractIfNoneMatch,
390
+ getBoundary
319
391
  }
@@ -0,0 +1,161 @@
1
+ const { where2obj } = require('../../_runtime/common/utils/cqn')
2
+
3
+ const _isNavToDraftAdmin = path => path.length > 1 && path[path.length - 1] === 'DraftAdministrativeData'
4
+
5
+ const _lastValidRef = ref => {
6
+ for (let i = ref.length - 1; i >= 0; i--) {
7
+ if (ref[i] in { DraftAdministrativeData: 1, SiblingEntity: 1 }) continue
8
+ return ref[i]
9
+ }
10
+ }
11
+
12
+ const _odataContext = (query, options) => {
13
+ const { result, isCollection, edmName } = options
14
+
15
+ let path = '$metadata'
16
+ if (query._target.kind === 'service') {
17
+ return path
18
+ }
19
+
20
+ const {
21
+ _target: { _isSingleton: isSingleton },
22
+ _propertyAccess: propertyAccess
23
+ } = query
24
+
25
+ path += '#'
26
+
27
+ // REVISIT: subselect is treated as empty array
28
+ const ref =
29
+ query.SELECT?.from?.ref ?? query.UPDATE?.entity?.ref ?? query.INSERT?.into?.ref ?? query.DELETE?.from?.ref ?? []
30
+
31
+ const isNavToDraftAdmin = _isNavToDraftAdmin(ref)
32
+
33
+ if (ref.length > 1) {
34
+ // prepend for relative path
35
+ path = '../'.repeat(ref.length - 1) + path
36
+ }
37
+ const lastRef = ref.at(-1)
38
+ let entityName = isNavToDraftAdmin ? ref[0].id ?? ref[0] : query._target.name ?? edmName
39
+ const serviceName = query._target._service?.name
40
+
41
+ if (query._target._isContained) {
42
+ let cur = query._target
43
+ let refIndex = ref.length - 1
44
+
45
+ entityName = lastRef.id ?? lastRef
46
+
47
+ while (cur._isContained) {
48
+ cur = cur.elements.up_._target
49
+ refIndex--
50
+ const curName = ref[refIndex].id ?? ref[refIndex]
51
+ const where = ref[refIndex].where
52
+
53
+ if (where) {
54
+ let keys
55
+ if (where.length > 3) {
56
+ // multiple keys should contain key name
57
+ const _keys = where2obj(where)
58
+ keys = Object.keys(_keys).map(k => {
59
+ return k + '=' + _keys[k]
60
+ })
61
+ } else {
62
+ // single keys can just contain value
63
+ keys = [where.at(-1).val]
64
+ }
65
+ entityName = curName + '(' + keys.join(',') + ')' + '/' + entityName
66
+ } else {
67
+ // REVISIT: is this correct?
68
+ entityName = curName + '/' + entityName
69
+ }
70
+ }
71
+ }
72
+
73
+ if (serviceName) {
74
+ entityName = entityName.replace(serviceName + '.', '').replace(/\./g, '_')
75
+ }
76
+
77
+ path += entityName
78
+
79
+ if (propertyAccess) {
80
+ path = '../' + path
81
+
82
+ const lastValidRef = _lastValidRef(ref)
83
+ if (lastValidRef.where) {
84
+ let keys
85
+ const isSibling = lastRef === 'SiblingEntity'
86
+ if (lastValidRef.where.length > 3) {
87
+ // multiple keys should contain key name
88
+ const _keys = where2obj(lastValidRef.where)
89
+ keys = Object.keys(_keys).map(k => {
90
+ if (k === 'IsActiveEntity' && isSibling) return k + '=' + !_keys[k]
91
+ return k + '=' + _keys[k]
92
+ })
93
+ } else {
94
+ // single keys can just contain value
95
+ keys = [lastValidRef.where.at(-1).val]
96
+ }
97
+
98
+ path += '(' + keys.join(',') + ')'
99
+ } else if (!isSingleton) {
100
+ // use keys from result if not in query
101
+ const _keys = Object.keys(query._target.keys)
102
+ let keyString
103
+ if (_keys.length === 1) {
104
+ keyString = result[_keys[0]]
105
+ } else {
106
+ keyString = _keys.map(k => k.name + '=' + result[k.name]).join(',')
107
+ }
108
+ path += '(' + keyString + ')'
109
+ }
110
+
111
+ if (isNavToDraftAdmin) {
112
+ path += '/' + lastRef
113
+ }
114
+
115
+ path += '/' + propertyAccess
116
+ } else if (isNavToDraftAdmin) {
117
+ const lastValidRef = _lastValidRef(ref)
118
+ if (lastValidRef.where) {
119
+ let keys
120
+ const isSibling = lastRef === 'SiblingEntity'
121
+ if (lastValidRef.where.length > 3) {
122
+ // multiple keys should contain key name
123
+ const _keys = where2obj(lastValidRef.where)
124
+ keys = Object.keys(_keys).map(k => {
125
+ if (k === 'IsActiveEntity' && isSibling) return k + '=' + !_keys[k]
126
+ return k + '=' + _keys[k]
127
+ })
128
+ } else {
129
+ // single keys can just contain value
130
+ keys = [lastValidRef.where.at(-1).val]
131
+ }
132
+
133
+ path += '(' + keys.join(',') + ')'
134
+ }
135
+ path += '/' + lastRef
136
+ }
137
+
138
+ if ((!isCollection && !isSingleton && !propertyAccess) || (isNavToDraftAdmin && !propertyAccess)) {
139
+ path += '/$entity'
140
+ }
141
+
142
+ return path
143
+ }
144
+
145
+ /**
146
+ * TODO
147
+ *
148
+ * @param {*} query
149
+ * @param {*} [options]
150
+ * @param {*} [options.result]
151
+ * @param {*} [options.isCollection]
152
+ * @param {*} [options.edmName]
153
+ * @returns
154
+ */
155
+ module.exports = function getODataMetadata(query, options = {}) {
156
+ if (!query._target) return
157
+
158
+ const context = _odataContext(query, options)
159
+
160
+ return { context }
161
+ }
@@ -0,0 +1,89 @@
1
+ const cds = require('../../_runtime/cds')
2
+
3
+ const getTemplate = require('../../_runtime/common/utils/template')
4
+ const templateProcessor = require('../../_runtime/common/utils/templateProcessor')
5
+
6
+ const _getParent = (model, name) => {
7
+ const target = model.definitions[name]
8
+
9
+ if (target && target.elements) {
10
+ for (const elementName in target.elements) {
11
+ const element = target.elements[elementName]
12
+ if (element._anchor && element._anchor._isContained) return element._anchor
13
+ }
14
+ }
15
+
16
+ return null
17
+ }
18
+
19
+ const _addEtags = (row, key) => {
20
+ if (!row[key]) return
21
+ row.$etag = row[key].startsWith('W/') ? row[key] : `W/"${row[key]}"`
22
+ }
23
+
24
+ const _processorFn = () => elementInfo => {
25
+ const { row, plain } = elementInfo
26
+ if (typeof row !== 'object') return
27
+ for (const category of plain.categories) {
28
+ const { row, key } = elementInfo
29
+ switch (category) {
30
+ case '@odata.etag':
31
+ _addEtags(row, key)
32
+ break
33
+ case '@cds.api.ignore':
34
+ delete row[key]
35
+ break
36
+ case 'binary':
37
+ if (Buffer.isBuffer(row[key])) {
38
+ // if the result object gets serialize to json, the buffer shall become a base64 string
39
+ row[key].toJSON = function () {
40
+ return this.toString('base64')
41
+ }
42
+ }
43
+ break
44
+ case 'array':
45
+ row[key] ??= []
46
+ break
47
+ // no default
48
+ }
49
+ }
50
+ }
51
+
52
+ const _pick = element => {
53
+ const categories = []
54
+ if (element['@odata.etag']) categories.push('@odata.etag')
55
+ if (element['@cds.api.ignore']) categories.push('@cds.api.ignore')
56
+ if (element._type === 'cds.Binary') categories.push('binary')
57
+ if (element.items) categories.push('array')
58
+ if (categories.length) return { categories }
59
+ }
60
+
61
+ module.exports = function postProcess(target, service, result, isMinimal) {
62
+ if (!result) return
63
+
64
+ let { model } = service
65
+ if (service.isExtensible) model = cds.context?.model || model
66
+
67
+ if (!model.definitions[target.name]) {
68
+ if (model.definitions[target.items?.type]) target = target.items
69
+ else return
70
+ }
71
+
72
+ const cacheKey = isMinimal ? 'postProcessMinimal' : 'postProcess'
73
+ const parent = _getParent(model, target.name)
74
+ const options = { pick: _pick, ignore: isMinimal ? el => el.isAssociation : undefined }
75
+ const template = getTemplate(cacheKey, service, target, options, parent)
76
+
77
+ if (template.elements.size === 0) return
78
+
79
+ // normalize result to rows
80
+ result = result.value != null && Object.keys(result).filter(k => !k.match(/^\W/)).length === 1 ? result.value : result
81
+
82
+ if (typeof result === 'object' && result != null) {
83
+ const rows = Array.isArray(result) ? result : [result]
84
+
85
+ // process each row
86
+ const processFn = _processorFn()
87
+ for (const row of rows) templateProcessor({ processFn, row, template })
88
+ }
89
+ }
@@ -1,23 +1,140 @@
1
1
  const cds = require('../../_runtime/cds')
2
- const { Request } = cds
3
-
4
- const readAfterWrite = async (req, srv, query) => {
5
- // gracefully set location and no body if no read auth or not readable capability
6
- let result
7
- try {
8
- const _req = new Request({ query, event: 'READ', _: req._, params: req.params })
9
- result = await srv.dispatch(_req)
10
- // NEW/PATCH must not include DraftAdministrativeData_DraftUUID for plain v4 usage, however required for odata-v2
11
- if (result && req.target._isDraftEnabled && req.headers?.['x-cds-odata-version'] !== 'v2') {
12
- delete result.DraftAdministrativeData_DraftUUID
2
+ const { SELECT } = cds.ql
3
+
4
+ const { DRAFT_COLUMNS_MAP } = require('../../_runtime/common/constants/draft')
5
+
6
+ const _keysOf = (row, target) => {
7
+ const keyElements = Object.values(target.keys || {}).filter(v => !v.virtual)
8
+ // > singleton
9
+ if (!keyElements.length) return
10
+ const keys = {}
11
+ for (const key of keyElements) {
12
+ if (key._isAssociationStrict) continue
13
+ if (row[key.name] === undefined) continue // key is not in data, so ignore it
14
+ keys[key.name] = key.elements ? { val: JSON.stringify(row[key.name]) } : row[key.name]
15
+ }
16
+ return keys
17
+ }
18
+
19
+ const _getSimpleSelectCQN = (target, data, subject) => {
20
+ let cqn
21
+
22
+ const keys = _keysOf(data, target)
23
+ if (subject?.ref.length > 1) {
24
+ cqn = SELECT.one(subject)
25
+ if (keys) cqn.where(keys)
26
+ } else if (!keys) {
27
+ //> singleton
28
+ cqn = SELECT.one(target)
29
+ } else {
30
+ cqn = SELECT.one(target, keys)
31
+ }
32
+
33
+ if (target.query && target.query.SELECT && target.query.SELECT.orderBy) {
34
+ cqn.SELECT.orderBy = target.query.SELECT.orderBy
35
+ }
36
+
37
+ return cqn
38
+ }
39
+
40
+ const _mergeExpandCQNs = cqns => {
41
+ const cols = cqns[0].SELECT.columns
42
+ for (const cqn of cqns.slice(1)) {
43
+ for (const col of cqn.SELECT.columns) {
44
+ if (!col.expand) continue
45
+ const idx = cols.findIndex(ele => {
46
+ if (!col.ref) return
47
+ if (ele.ref) return ele.ref[0] === col.ref[0]
48
+ if (ele.as) return ele.as === col.ref[0]
49
+ })
50
+ if (idx === -1) {
51
+ cols.push(col)
52
+ } else {
53
+ const colExists = cols[idx]
54
+ if (colExists.as && colExists.val === null) {
55
+ cols[idx] = col
56
+ continue
57
+ }
58
+ if (col.as && col.val === null) continue
59
+ const mergedExpandCQN = _mergeExpandCQNs([
60
+ { SELECT: { columns: colExists.expand } },
61
+ { SELECT: { columns: col.expand } }
62
+ ])
63
+ colExists.expand = mergedExpandCQN.SELECT.columns
64
+ }
13
65
  }
14
- } catch (e) {
15
- // read was not possible because of access restrictions => ignore
16
- if (!(Number(e.code) in { 401: 1, 403: 1, 404: 1, 405: 1 })) throw e
17
- result = null
18
66
  }
67
+ return cqns[0]
68
+ }
19
69
 
20
- return result
70
+ const _getExpandColumn = (data, element) => {
71
+ const key = element.name
72
+ if (!(key in data)) return
73
+ data = data[key]
74
+ if ((Array.isArray(data) && data.length === 0) || data == null) {
75
+ // performance tweak, keep in mind it is only for compositions
76
+ return { val: null, as: key }
77
+ }
78
+ const cqn = Array.isArray(data)
79
+ ? _mergeExpandCQNs(data.map(data => _getSelect({ target: element._target, data }, true)))
80
+ : _getSelect({ target: element._target, data }, true)
81
+ return { ref: [key], expand: cqn.SELECT.columns }
21
82
  }
22
83
 
23
- module.exports = readAfterWrite
84
+ const _getColumns = (target, data, prefix = []) => {
85
+ const columns = []
86
+ for (const each in target.elements) {
87
+ if (target.elements[each]['@cds.api.ignore']) continue
88
+ if (each in DRAFT_COLUMNS_MAP) continue
89
+ if (!cds.env.features.stream_compat && target.elements[each].type === 'cds.LargeBinary') continue
90
+ const element = target.elements[each]
91
+ if (element.elements && data[each]) {
92
+ prefix.push(element.name)
93
+ columns.push(..._getColumns(element, data[each], prefix))
94
+ prefix.pop()
95
+ } else if (element.isComposition && !prefix.length) {
96
+ const col = _getExpandColumn(data, element, prefix)
97
+ if (col) columns.push(col)
98
+ } else if (!element.isAssociation) {
99
+ columns.push({ ref: [...prefix, each] })
100
+ }
101
+ }
102
+ return columns
103
+ }
104
+
105
+ /*
106
+ * recursively builds a select cqn (depth determined by req.data)
107
+ */
108
+ const _getSelect = (cdsReq, deep = false) => {
109
+ const { target, data, subject } = cdsReq
110
+ const cqn = _getSimpleSelectCQN(target, data, subject)
111
+ if (deep) cqn.columns(..._getColumns(target, data))
112
+ return cqn
113
+ }
114
+
115
+ module.exports = (adapter, middleware) => {
116
+ const { service } = adapter
117
+
118
+ const _getQuery =
119
+ middleware === 'create'
120
+ ? cdsReq => _getSelect(cdsReq, cdsReq.event === 'CREATE')
121
+ : cdsReq => SELECT.one(cdsReq.subject)
122
+
123
+ return async function readAfterWrite(cdsReq) {
124
+ try {
125
+ const query = _getQuery(cdsReq)
126
+ const result = await service.dispatch(adapter.request4({ query, params: cdsReq.params }))
127
+
128
+ // REVISIT: really needed? -> disable and run odata v2 tests
129
+ // NEW/PATCH must not include DraftAdministrativeData_DraftUUID for plain v4 usage, however required for odata-v2
130
+ if (result && cdsReq.target._isDraftEnabled && cdsReq.headers?.['x-cds-odata-version'] !== 'v2') {
131
+ delete result.DraftAdministrativeData_DraftUUID
132
+ }
133
+
134
+ return result
135
+ } catch (e) {
136
+ // if read was not possible because of access restrictions then ignore else throw
137
+ if (!(Number(e.code) in { 401: 1, 403: 1, 404: 1, 405: 1 })) throw e
138
+ }
139
+ }
140
+ }