@sap/cds 6.8.4 → 7.0.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 (214) hide show
  1. package/CHANGELOG.md +58 -5
  2. package/README.md +0 -1
  3. package/bin/cds-serve.js +50 -3
  4. package/bin/deploy/to-hana.js +1 -0
  5. package/bin/serve.js +16 -20
  6. package/lib/auth/basic-auth.js +6 -4
  7. package/lib/auth/index.js +4 -3
  8. package/lib/auth/jwt-auth.js +2 -5
  9. package/lib/compile/cds-compile.js +34 -89
  10. package/lib/compile/cdsc.js +11 -0
  11. package/lib/compile/etc/properties.js +2 -2
  12. package/lib/compile/for/lean_drafts.js +36 -69
  13. package/lib/compile/for/nodejs.js +2 -1
  14. package/lib/compile/load.js +1 -1
  15. package/lib/compile/minify.js +2 -0
  16. package/lib/compile/to/csn.js +74 -0
  17. package/{bin/build/provider/hana/2tabledata.js → lib/compile/to/hdbtabledata.js} +4 -6
  18. package/lib/compile/to/json.js +1 -1
  19. package/lib/compile/to/sql.js +8 -6
  20. package/lib/dbs/cds-deploy.js +174 -114
  21. package/lib/env/cds-env.js +64 -79
  22. package/lib/env/cds-requires.js +11 -28
  23. package/lib/env/defaults.js +13 -3
  24. package/lib/env/plugins.js +1 -12
  25. package/lib/env/presets.js +25 -21
  26. package/lib/index.js +121 -147
  27. package/lib/{core/reflect.js → linked/models.js} +2 -2
  28. package/lib/{core/infer.js → linked/queries.js} +2 -0
  29. package/lib/{core/index.js → linked/types.js} +2 -1
  30. package/lib/log/cds-error.js +13 -7
  31. package/lib/log/format/cf.js +1 -1
  32. package/lib/plugins.js +49 -0
  33. package/lib/ql/Query.js +0 -9
  34. package/lib/ql/STREAM.js +0 -1
  35. package/lib/req/context.js +2 -7
  36. package/lib/req/request.js +6 -2
  37. package/lib/req/response.js +23 -10
  38. package/lib/srv/middlewares/ctx-model.js +1 -1
  39. package/lib/srv/middlewares/errors.js +1 -1
  40. package/lib/srv/protocols/_legacy.js +1 -0
  41. package/lib/srv/protocols/graphql.js +7 -16
  42. package/lib/srv/protocols/index.js +59 -45
  43. package/lib/srv/protocols/odata-v2-proxy.js +2 -70
  44. package/lib/srv/srv-api.js +9 -3
  45. package/lib/srv/srv-dispatch.js +12 -9
  46. package/lib/srv/srv-models.js +4 -21
  47. package/lib/srv/srv-tx.js +15 -12
  48. package/lib/utils/cds-test.js +14 -9
  49. package/lib/utils/cds-utils.js +2 -12
  50. package/lib/utils/check-version.js +17 -0
  51. package/{bin/build → lib/utils}/csv-reader.js +23 -24
  52. package/libx/_runtime/auth/index.js +27 -23
  53. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +15 -72
  54. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  55. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +0 -2
  56. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +33 -63
  57. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +14 -18
  58. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +15 -5
  59. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +5 -4
  60. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +37 -40
  61. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +7 -1
  62. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +101 -38
  63. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/AbstractError.js +5 -1
  64. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +2 -1
  65. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +9 -8
  66. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  67. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +15 -11
  68. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +4 -0
  69. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +5 -2
  70. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -123
  71. package/libx/_runtime/cds-services/services/Service.js +79 -107
  72. package/libx/_runtime/cds-services/services/utils/columns.js +23 -19
  73. package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -1
  74. package/libx/_runtime/cds-services/services/utils/differ.js +7 -2
  75. package/libx/_runtime/cds-services/util/assert.js +65 -2
  76. package/libx/_runtime/common/composition/data.js +1 -0
  77. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  78. package/libx/_runtime/common/generic/auth/restrict.js +5 -10
  79. package/libx/_runtime/common/generic/auth/restrictions.js +40 -0
  80. package/libx/_runtime/common/generic/auth/utils.js +1 -2
  81. package/libx/_runtime/common/generic/crud.js +32 -16
  82. package/libx/_runtime/common/generic/etag.js +133 -104
  83. package/libx/_runtime/common/generic/input.js +6 -21
  84. package/libx/_runtime/common/generic/put.js +1 -1
  85. package/libx/_runtime/common/generic/stream.js +52 -0
  86. package/libx/_runtime/common/generic/temporal.js +25 -8
  87. package/libx/_runtime/common/i18n/messages.properties +0 -2
  88. package/libx/_runtime/common/utils/cqn.js +1 -1
  89. package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
  90. package/libx/_runtime/common/utils/csn.js +0 -51
  91. package/libx/_runtime/common/utils/etag.js +30 -0
  92. package/libx/_runtime/common/utils/keys.js +1 -1
  93. package/libx/_runtime/common/utils/normalizeTimestamp.js +25 -0
  94. package/libx/_runtime/common/utils/path.js +1 -1
  95. package/libx/_runtime/common/utils/resolveView.js +2 -1
  96. package/libx/_runtime/common/utils/rewriteAsterisks.js +6 -4
  97. package/libx/_runtime/common/utils/search2cqn4sql.js +12 -16
  98. package/libx/_runtime/common/utils/stream.js +140 -0
  99. package/libx/_runtime/common/utils/streamProp.js +29 -12
  100. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +0 -2
  101. package/libx/_runtime/db/generic/index.js +0 -2
  102. package/libx/_runtime/db/query/delete.js +2 -2
  103. package/libx/_runtime/db/query/insert.js +2 -2
  104. package/libx/_runtime/db/query/read.js +2 -2
  105. package/libx/_runtime/db/query/run.js +2 -2
  106. package/libx/_runtime/db/query/update.js +2 -2
  107. package/libx/_runtime/db/sql-builder/BaseBuilder.js +0 -6
  108. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +23 -12
  109. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +18 -6
  110. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -0
  111. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -7
  112. package/libx/_runtime/db/sql-builder/UpsertBuilder.js +1 -0
  113. package/libx/_runtime/db/utils/normalizeTimeData.js +7 -3
  114. package/libx/_runtime/fiori/draft.js +2 -0
  115. package/libx/_runtime/fiori/generic/activate.js +8 -9
  116. package/libx/_runtime/fiori/generic/before.js +30 -20
  117. package/libx/_runtime/fiori/generic/cancel.js +5 -3
  118. package/libx/_runtime/fiori/generic/delete.js +5 -3
  119. package/libx/_runtime/fiori/generic/edit.js +7 -7
  120. package/libx/_runtime/fiori/generic/index.js +10 -16
  121. package/libx/_runtime/fiori/generic/new.js +5 -3
  122. package/libx/_runtime/fiori/generic/patch.js +11 -8
  123. package/libx/_runtime/fiori/generic/prepare.js +13 -6
  124. package/libx/_runtime/fiori/generic/read.js +12 -6
  125. package/libx/_runtime/fiori/lean-draft.js +207 -152
  126. package/libx/_runtime/fiori/utils/delete.js +10 -5
  127. package/libx/_runtime/fiori/utils/req.js +17 -5
  128. package/libx/_runtime/fiori/utils/stream.js +36 -0
  129. package/libx/_runtime/hana/Service.js +12 -9
  130. package/libx/_runtime/hana/conversion.js +10 -15
  131. package/libx/_runtime/hana/driver.js +2 -0
  132. package/libx/_runtime/hana/execute.js +28 -6
  133. package/libx/_runtime/hana/pool.js +36 -122
  134. package/libx/_runtime/hana/search2cqn4sql.js +34 -36
  135. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -6
  136. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -1
  137. package/libx/_runtime/messaging/enterprise-messaging.js +10 -58
  138. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  139. package/libx/_runtime/remote/Service.js +20 -1
  140. package/libx/_runtime/remote/utils/client.js +3 -5
  141. package/libx/_runtime/sqlite/Service.js +4 -6
  142. package/libx/_runtime/sqlite/conversion.js +3 -13
  143. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +9 -6
  144. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +6 -1
  145. package/libx/_runtime/sqlite/execute.js +5 -16
  146. package/libx/odata/afterburner.js +22 -6
  147. package/libx/odata/grammar.pegjs +6 -1
  148. package/libx/odata/parser.js +1 -1
  149. package/libx/rest/RestAdapter.js +16 -9
  150. package/libx/rest/RestRequest.js +1 -1
  151. package/libx/rest/middleware/input.js +2 -1
  152. package/libx/rest/middleware/operation.js +1 -0
  153. package/libx/rest/middleware/parse.js +3 -2
  154. package/libx/rest/middleware/payload.js +9 -8
  155. package/libx/rest/middleware/read.js +1 -0
  156. package/package.json +9 -16
  157. package/app/fiori/preview.js +0 -270
  158. package/app/fiori/routes.js +0 -59
  159. package/bin/build/buildTaskEngine.js +0 -360
  160. package/bin/build/buildTaskFactory.js +0 -283
  161. package/bin/build/buildTaskHandler.js +0 -241
  162. package/bin/build/buildTaskProvider.js +0 -22
  163. package/bin/build/buildTaskProviderFactory.js +0 -175
  164. package/bin/build/cds.js +0 -5
  165. package/bin/build/constants.js +0 -66
  166. package/bin/build/index.js +0 -58
  167. package/bin/build/provider/buildTaskHandlerEdmx.js +0 -82
  168. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +0 -131
  169. package/bin/build/provider/buildTaskHandlerInternal.js +0 -254
  170. package/bin/build/provider/buildTaskProviderInternal.js +0 -383
  171. package/bin/build/provider/fiori/index.js +0 -171
  172. package/bin/build/provider/hana/2migration.js +0 -179
  173. package/bin/build/provider/hana/index.js +0 -505
  174. package/bin/build/provider/hana/migrationtable.js +0 -472
  175. package/bin/build/provider/hana/template/.hdiconfig-haas +0 -163
  176. package/bin/build/provider/hana/template/.hdiconfig-hanacloud +0 -137
  177. package/bin/build/provider/hana/template/.hdinamespace +0 -4
  178. package/bin/build/provider/hana/template/package.json +0 -12
  179. package/bin/build/provider/hana/template/undeploy.json +0 -5
  180. package/bin/build/provider/java/index.js +0 -111
  181. package/bin/build/provider/java-cf/index.js +0 -1
  182. package/bin/build/provider/mtx/index.js +0 -268
  183. package/bin/build/provider/mtx/resourcesTarBuilder.js +0 -95
  184. package/bin/build/provider/mtx-extension/index.js +0 -131
  185. package/bin/build/provider/mtx-sidecar/index.js +0 -137
  186. package/bin/build/provider/node-cf/index.js +0 -1
  187. package/bin/build/provider/nodejs/index.js +0 -192
  188. package/bin/build/util.js +0 -299
  189. package/bin/cds.js +0 -125
  190. package/bin/deploy/to-hana/cfUtil.js +0 -355
  191. package/bin/deploy/to-hana/gitUtil.js +0 -57
  192. package/bin/deploy/to-hana/hana.js +0 -306
  193. package/bin/deploy/to-hana/hdiDeployUtil.js +0 -153
  194. package/bin/deploy/to-hana/index.js +0 -16
  195. package/bin/deploy/to-hana/mtaUtil.js +0 -170
  196. package/bin/mtx/in-cds.js +0 -17
  197. package/bin/plugins.js +0 -32
  198. package/bin/run.js +0 -24
  199. package/bin/utils/log.js +0 -24
  200. package/bin/version.js +0 -178
  201. package/libx/_runtime/audit/Service.js +0 -222
  202. package/libx/_runtime/audit/generic/personal/access.js +0 -61
  203. package/libx/_runtime/audit/generic/personal/index.js +0 -56
  204. package/libx/_runtime/audit/generic/personal/modification.js +0 -132
  205. package/libx/_runtime/audit/generic/personal/utils.js +0 -186
  206. package/libx/_runtime/audit/utils/log.js +0 -23
  207. package/libx/_runtime/audit/utils/v2.js +0 -176
  208. package/libx/_runtime/db/data-conversion/timestamp.js +0 -9
  209. package/libx/_runtime/db/generic/integrity.js +0 -455
  210. package/srv/audit-log.cds +0 -87
  211. package/srv/mtx.cds +0 -2
  212. package/srv/mtx.js +0 -8
  213. /package/lib/{core → linked}/classes.js +0 -0
  214. /package/lib/{core → linked}/entities.js +0 -0
@@ -6,7 +6,13 @@ const orderByToCQN = require('./orderByToCQN')
6
6
  const selectToCQN = require('./selectToCQN')
7
7
  const searchToCQN = require('./searchToCQN')
8
8
  const applyToCQN = require('./applyToCQN')
9
- const { convertUrlPathToCqn, getAllKeys, isPathSupported, getSegmentKeyValue } = require('./utils')
9
+ const {
10
+ convertUrlPathToCqn,
11
+ convertUrlPathToViewCqn,
12
+ getAllKeys,
13
+ isPathSupported,
14
+ getPropertyParam
15
+ } = require('./utils')
10
16
 
11
17
  const QueryOptions = require('../okra/odata-server').QueryOptions
12
18
  const {
@@ -33,11 +39,12 @@ const SUPPORTED_SEGMENT_KINDS = {
33
39
  }
34
40
 
35
41
  const expandToCQN = require('./expandToCQN')
36
- const { resolveStructuredName } = require('../utils/handlerUtils')
37
42
  const { isStreaming } = require('../utils/stream')
38
43
 
39
44
  const { getPageSize } = require('../../../../common/generic/paging')
40
45
  const { isAsteriskColumn } = require('../../../../common/utils/rewriteAsterisks')
46
+ const { handleStreamProperties } = require('../../../../common/utils/streamProp')
47
+ const { isNewStream } = require('../../../../common/utils/stream')
41
48
 
42
49
  const { ensureUnlocalized } = require('../../../../fiori/utils/handler')
43
50
 
@@ -145,18 +152,6 @@ const _topSkip = (queryOptions, target, cqn) => {
145
152
  }
146
153
  }
147
154
 
148
- const _getPropertyParam = pathSegments => {
149
- const index = pathSegments[pathSegments.length - 1].getKind() === VALUE ? 2 : 1
150
- const prop = pathSegments[pathSegments.length - index].getProperty()
151
- const name = prop && prop.getName()
152
- return (
153
- name &&
154
- (pathSegments.length > 1
155
- ? { ref: resolveStructuredName(pathSegments, pathSegments.length - 2, [name]) }
156
- : { ref: [name] })
157
- )
158
- }
159
-
160
155
  const _isCollectionOrToMany = kind => {
161
156
  return kind === ENTITY_COLLECTION || kind === NAVIGATION_TO_MANY
162
157
  }
@@ -249,29 +244,6 @@ const _addKeysToSelectIfNoStreaming = (entity, select, streaming) => {
249
244
  }
250
245
  }
251
246
 
252
- const _convertUrlPathToViewCqn = segments => {
253
- const args = segments[0].getKeyPredicates().reduce((prev, curr) => {
254
- const { keyName, val } = getSegmentKeyValue(curr)
255
- prev[keyName.replace(/\//g, '_')] = { val }
256
- return prev
257
- }, {})
258
-
259
- // REVISIT: Replace .getFullQualifiedName().toString() with findCsnTargetFor as done in convertUrlPathToCqn
260
- return {
261
- ref: [
262
- {
263
- id: segments[0]
264
- .getEntitySet()
265
- .getEntityType()
266
- .getFullQualifiedName()
267
- .toString()
268
- .replace(/Parameters$/, ''),
269
- args
270
- }
271
- ]
272
- }
273
- }
274
-
275
247
  const _expandRecursive = (ref, entity, expands = []) => {
276
248
  if (ref.length > 1) {
277
249
  let innerExpandElement = expands.find(e => e.ref[0] === ref[0])
@@ -344,12 +316,35 @@ const readToCQN = (service, target, odataReq) => {
344
316
  const segments = uriInfo.getPathSegments()
345
317
  isPathSupported(SUPPORTED_SEGMENT_KINDS, segments)
346
318
 
319
+ if (isNewStream()) {
320
+ let propertyParam
321
+ if (isStreaming(segments)) {
322
+ propertyParam = getPropertyParam(segments)
323
+ } else if (segments[segments.length - 1]._isStreamByDollarValue) {
324
+ // REVISIT: Issue with multiple streaming properties ? Also in read.js
325
+ for (const k in target.elements) {
326
+ if (target.elements[k]['@Core.MediaType']) {
327
+ propertyParam = { ref: [k] }
328
+ break
329
+ }
330
+ }
331
+ }
332
+
333
+ if (propertyParam) {
334
+ const isView = target.params && Object.keys(target.params).length > 0
335
+ return STREAM.from(isView ? convertUrlPathToViewCqn(segments) : convertUrlPathToCqn(segments, service)).column(
336
+ propertyParam.ref[0]
337
+ )
338
+ }
339
+ }
340
+
347
341
  const queryOptions = odataReq.getQueryOptions()
348
342
  const entity = service.model.definitions[ensureUnlocalized(target.name)]
349
- const propertyParam = _getPropertyParam(segments)
343
+ const propertyParam = getPropertyParam(segments)
350
344
  const apply = _apply(uriInfo, queryOptions, entity, service.model)
351
345
  const select = _select(queryOptions, entity)
352
346
  const expand = expandToCQN(uriInfo.getQueryOption(QueryOptions.EXPAND), uriInfo.getFinalEdmType(), service)
347
+ const streaming = isStreaming(segments)
353
348
 
354
349
  if (Object.keys(apply).length) {
355
350
  _handleApply(apply, select)
@@ -359,7 +354,7 @@ const readToCQN = (service, target, odataReq) => {
359
354
  select.push(propertyParam)
360
355
 
361
356
  // add keys if no streaming, TODO: what if streaming via to-one
362
- _addKeysToSelectIfNoStreaming(entity, select, isStreaming(segments))
357
+ _addKeysToSelectIfNoStreaming(entity, select, streaming)
363
358
  }
364
359
 
365
360
  if (select.length === 0) {
@@ -378,7 +373,9 @@ const readToCQN = (service, target, odataReq) => {
378
373
  _checkViewWithParamCall(isView, segments, kind, target.name)
379
374
 
380
375
  // keep target as input because of localized view
381
- const cqn = SELECT.from(isView ? _convertUrlPathToViewCqn(segments) : convertUrlPathToCqn(segments, service), select)
376
+ const cqn = SELECT.from(isView ? convertUrlPathToViewCqn(segments) : convertUrlPathToCqn(segments, service), select)
377
+ if (!cqn.SELECT.columns) cqn.SELECT.columns = ['*']
378
+ if (!streaming) handleStreamProperties(target, cqn, service.model, true)
382
379
 
383
380
  const isCount =
384
381
  isCollectionOrToMany &&
@@ -3,7 +3,8 @@ const { UPDATE } = cds.ql
3
3
 
4
4
  const { getFeatureNotSupportedError } = require('../../../util/errors')
5
5
  const { isStreaming } = require('../utils/stream')
6
- const { convertUrlPathToCqn } = require('./utils')
6
+ const { convertUrlPathToCqn, getPropertyParam } = require('./utils')
7
+ const { isNewStream } = require('../../../../common/utils/stream')
7
8
 
8
9
  const { ENTITY, NAVIGATION_TO_ONE, PRIMITIVE_PROPERTY, SINGLETON } =
9
10
  require('../okra/odata-server').uri.UriResource.ResourceKind
@@ -28,6 +29,11 @@ const updateToCQN = (service, data, odataReq) => {
28
29
  const segment = segments[segments.length - 1]
29
30
  const streaming = isStreaming(segments)
30
31
 
32
+ if (isNewStream() && streaming) {
33
+ const col = getPropertyParam(segments).ref[0]
34
+ return STREAM.into(convertUrlPathToCqn(segments, service)).data(data[col]).column(col)
35
+ }
36
+
31
37
  if (SUPPORTED_KINDS[segment.getKind()] || streaming) {
32
38
  return UPDATE(convertUrlPathToCqn(segments, service)).data(data)
33
39
  }
@@ -1,19 +1,7 @@
1
1
  const { getFeatureNotSupportedError } = require('../../../util/errors')
2
-
2
+ const { resolveStructuredName } = require('../utils/handlerUtils')
3
3
  const { findCsnTargetFor } = require('../../../../common/utils/csn')
4
4
 
5
- const CDL_KEYWORDS = new Set(require('@sap/cds-compiler').to.cdl.keywords)
6
-
7
- // TODO: Which EDM Types are missing?
8
- const notToBeConvertedForCompiler = new Set([
9
- 'Edm.Boolean',
10
- 'Edm.Int16',
11
- 'Edm.Int32',
12
- 'Edm.Int64',
13
- 'Edm.Decimal',
14
- 'Edm.Double'
15
- ])
16
-
17
5
  const addLimit = (item, rows, offset) => {
18
6
  // ignore 0 offset -> truthy check
19
7
  if (rows != null || offset) {
@@ -29,21 +17,49 @@ const addLimit = (item, rows, offset) => {
29
17
  }
30
18
  }
31
19
 
32
- const convertKeyPredicatesToStringExpr = keyPredicates => {
20
+ const _isNum = type => {
21
+ const nums = {
22
+ 'Edm.Byte': 1,
23
+ 'Edm.Int16': 1,
24
+ 'Edm.Int32': 1,
25
+ 'Edm.Double': 1
26
+ }
27
+
28
+ return type.toString() in nums
29
+ }
30
+
31
+ const _isBigNum = type => {
32
+ const bigNums = {
33
+ 'Edm.Int64': 1,
34
+ 'Edm.Decimal': 1
35
+ }
36
+
37
+ return type.toString() in bigNums
38
+ }
39
+
40
+ const _getValue = (value, type) => {
41
+ if (_isNum(type)) return Number(value)
42
+ if (_isBigNum(type)) {
43
+ const num = Number(value)
44
+ return Number.isSafeInteger(num) ? num : value
45
+ }
46
+ if (type === 'Edm.Boolean') return value === 'true'
47
+ return value
48
+ }
49
+
50
+ const convertKeyPredicatesToWhere = keyPredicates => {
51
+ const where = []
33
52
  if (keyPredicates.length) {
34
- return `[${keyPredicates
35
- .map(kp => {
36
- const keyName = kp.getEdmRef().getName().replace(/\//g, '.')
37
- let keyValue = kp.getText().replace(/'/g, "''")
38
- if (!notToBeConvertedForCompiler.has(kp.getEdmRef().getProperty().getType().toString())) {
39
- keyValue = `'${keyValue}'`
40
- }
41
- return `${CDL_KEYWORDS.has(keyName) ? `![${keyName}]` : keyName}=${keyValue}`
42
- })
43
- .join(' and ')}]`
53
+ keyPredicates.forEach(kp => {
54
+ if (where.length) where.push('and')
55
+ const keyName = kp.getEdmRef().getName().replace(/\//g, '.')
56
+ const type = kp.getEdmRef().getProperty().getType()
57
+ let keyValue = _getValue(kp.getText(), type.toString())
58
+ where.push({ ref: [keyName] }, '=', { val: keyValue })
59
+ })
44
60
  }
45
61
 
46
- return ''
62
+ return where
47
63
  }
48
64
 
49
65
  const convertUrlPathToCqn = (segments, service) => {
@@ -52,29 +68,74 @@ const convertUrlPathToCqn = (segments, service) => {
52
68
  segment =>
53
69
  segment.getKind() !== 'COUNT' && segment.getKind() !== 'PRIMITIVE.PROPERTY' && segment.getKind() !== 'VALUE'
54
70
  )
55
- .reduce((expr, segment, i) => {
71
+ .reduce((expr, segment) => {
56
72
  if (segment.getKind() === 'ENTITY' || segment.getKind() === 'ENTITY.COLLECTION') {
57
73
  const entity = segment.getEntitySet().getEntityType().getFullQualifiedName()
58
- const keys = convertKeyPredicatesToStringExpr(segment.getKeyPredicates())
59
-
60
- return `${findCsnTargetFor(entity.name, service.model, service.namespace).name}${keys}`
74
+ const where = convertKeyPredicatesToWhere(segment.getKeyPredicates())
75
+ let ref
76
+ if (where.length) {
77
+ ref = { ref: [{ id: findCsnTargetFor(entity.name, service.model, service.namespace).name, where }] }
78
+ } else {
79
+ ref = { ref: [findCsnTargetFor(entity.name, service.model, service.namespace).name] }
80
+ }
81
+ return ref
61
82
  }
62
83
 
63
84
  if (segment.getKind() === 'SINGLETON') {
64
85
  const singleton = segment.getSingleton().getEntityType().getFullQualifiedName()
65
-
66
- return `${findCsnTargetFor(singleton.name, service.model, service.namespace).name}`
86
+ return { ref: [findCsnTargetFor(singleton.name, service.model, service.namespace).name] }
67
87
  }
68
88
 
69
89
  if (segment.getKind() === 'COMPLEX.PROPERTY') {
70
- const complex = segment.getProperty().getName()
71
- return `${expr}${i === 1 ? ':' : '.'}${complex}`
90
+ expr.ref.push(segment.getProperty().getName())
91
+ return expr
92
+ }
93
+
94
+ const where = convertKeyPredicatesToWhere(segment.getKeyPredicates())
95
+ let ref
96
+ if (where.length) {
97
+ ref = { id: segment.getNavigationProperty().getName(), where }
98
+ } else {
99
+ ref = segment.getNavigationProperty().getName()
72
100
  }
73
101
 
74
- const navigation = segment.getNavigationProperty().getName()
75
- const keys = convertKeyPredicatesToStringExpr(segment.getKeyPredicates())
76
- return `${expr}${i === 1 ? ':' : '.'}${navigation}${keys}`
77
- }, '')
102
+ if (!expr.ref) expr.ref = []
103
+ expr.ref.push(ref)
104
+ return expr
105
+ }, {})
106
+ }
107
+
108
+ const convertUrlPathToViewCqn = segments => {
109
+ const args = segments[0].getKeyPredicates().reduce((prev, curr) => {
110
+ const { keyName, val } = getSegmentKeyValue(curr)
111
+ prev[keyName.replace(/\//g, '_')] = { val }
112
+ return prev
113
+ }, {})
114
+
115
+ // REVISIT: Replace .getFullQualifiedName().toString() with findCsnTargetFor as done in convertUrlPathToCqn
116
+ return {
117
+ ref: [
118
+ {
119
+ id: segments[0]
120
+ .getEntitySet()
121
+ .getEntityType()
122
+ .getFullQualifiedName()
123
+ .toString()
124
+ .replace(/Parameters$/, ''),
125
+ args
126
+ }
127
+ ]
128
+ }
129
+ }
130
+
131
+ const getPropertyParam = segments => {
132
+ const index = segments[segments.length - 1].getKind() === 1 ? 2 : 1
133
+ const prop = segments[segments.length - index].getProperty()
134
+ const name = prop && prop.getName()
135
+ return (
136
+ name &&
137
+ (segments.length > 1 ? { ref: resolveStructuredName(segments, segments.length - 2, [name]) } : { ref: [name] })
138
+ )
78
139
  }
79
140
 
80
141
  const isSameArray = (arr1, arr2) => {
@@ -143,8 +204,10 @@ const getSegmentKeyValue = segmentParam => {
143
204
  module.exports = {
144
205
  addLimit,
145
206
  convertUrlPathToCqn,
207
+ convertUrlPathToViewCqn,
146
208
  isSameArray,
147
209
  getAllKeys,
148
210
  isPathSupported,
149
- getSegmentKeyValue
211
+ getSegmentKeyValue,
212
+ getPropertyParam
150
213
  }
@@ -35,7 +35,11 @@ class AbstractError extends Error {
35
35
  setRootCause (rootCause) {
36
36
  if (rootCause) {
37
37
  if (!(rootCause instanceof Error)) {
38
- throw new Error("Parameter 'rootCause' must be an instance of Error")
38
+ if (typeof rootCause === 'object' && rootCause.message) {
39
+ rootCause = Object.assign(new Error(rootCause.message), rootCause)
40
+ } else {
41
+ throw new Error("Parameter 'rootCause' must be an instance of Error")
42
+ }
39
43
  }
40
44
  this._rootCause = rootCause
41
45
  }
@@ -336,7 +336,8 @@ class ValueConverter {
336
336
  * output is requested in the formatParams parameter of the constructor of this class.
337
337
  */
338
338
  convertDecimal (value, precision, scale) {
339
- this._valueValidator.validateDecimal(value, precision, scale)
339
+ // now done in runtime's assert framework
340
+ // this._valueValidator.validateDecimal(value, precision, scale)
340
341
 
341
342
  const bigValue = new big(value)
342
343
 
@@ -201,18 +201,19 @@ class ResourceJsonDeserializer {
201
201
  // because the first parameter of a bound action is the binding parameter
202
202
  // which is not part of the payload data.
203
203
  if (edmAction.isBound()) parameters.shift()
204
-
204
+
205
+ if (parameters.length > 0) data = JSON.parse(value)
205
206
  for (const [paramName, edmParam] of parameters) {
206
- if ((value === null || value === undefined) && !edmParam.isNullable()) {
207
- throw new DeserializationError(`Parameter '${paramName}' is not nullable but payload is null`)
208
- } else if (!data) {
209
- data = JSON.parse(value)
210
- if (typeof data !== 'object') {
211
- throw new DeserializationError('Value for action parameters must be an object.')
212
- }
207
+ if (!data && typeof data !== 'object') {
208
+ throw new DeserializationError('Value for action parameters must be an object.')
213
209
  }
214
210
 
215
211
  let paramValue = data[paramName]
212
+
213
+ if ((value === null || value === undefined || paramValue == null) && !edmParam.isNullable()) {
214
+ throw new DeserializationError(`Parameter '${paramName}' is not nullable`)
215
+ }
216
+
216
217
  if (paramValue === undefined) {
217
218
  // OData JSON Format Version 4.0 Plus Errata 03 - 17 Action Invocation:
218
219
  // "Any parameter values not specified in the JSON object are assumed to have the null value."
@@ -21,7 +21,7 @@ function OkraAdapter(srv, model = srv.model) {
21
21
  //
22
22
  // REVISIT: Move to ExtensibilityService
23
23
  //
24
- if (cds.mtx || cds.requires.extensibility || cds.requires.toggles)
24
+ if (cds.requires.extensibility || cds.requires.toggles)
25
25
  module.exports = srv => {
26
26
  const id = `${++unique} - ${srv.path}` // REVISIT: this is to allow running multiple express apps serving same endpoints, as done by some questionable tests
27
27
  return function ODataAdapter(req, res) {
@@ -3,13 +3,15 @@ const {
3
3
  } = require('../okra/odata-server')
4
4
 
5
5
  const { findCsnTargetFor } = require('../../../../common/utils/csn')
6
- const { isStreaming } = require('./stream')
7
6
  const { convertStructured } = require('../../../../../common/utils/ucsn')
8
7
  const { deepCopy } = require('../../../../common/utils/copy')
9
8
  const { getSegmentKeyValue } = require('../odata-to-cqn/utils')
10
9
 
11
- const _isFunctionInvocation = req =>
12
- req.getUriInfo().getLastSegment().getFunction() || req.getUriInfo().getLastSegment().getFunctionImport()
10
+ const _isFunctionInvocation = odataReq => {
11
+ return (
12
+ odataReq.getUriInfo().getLastSegment().getFunction() || odataReq.getUriInfo().getLastSegment().getFunctionImport()
13
+ )
14
+ }
13
15
 
14
16
  const _addStructuredProperties = ([structName, property, ...nestedProperties], paramData, value) => {
15
17
  paramData[structName] = paramData[structName] || {}
@@ -129,14 +131,14 @@ function _entityOrTypeName(navSourceSegment) {
129
131
  .getFullQualifiedName()
130
132
  }
131
133
 
132
- const _addForeignKeys = (service, req, data) => {
133
- const pathSegments = req.getUriInfo().getPathSegments()
134
+ const _addForeignKeys = (service, odataReq, data) => {
135
+ const pathSegments = odataReq.getUriInfo().getPathSegments()
134
136
  // retrieve keys/values from the path segment representing the navigation source
135
137
  const navSourceSegment = pathSegments[pathSegments.length - 2]
136
138
  const navSourceKeyValues = _getParamData(navSourceSegment.getKeyPredicates())
137
139
 
138
140
  // retrieve relevant foreign key properties of the target entity, including the corresponding source key properties
139
- const navProperty = req.getUriInfo().getLastSegment().getNavigationProperty()
141
+ const navProperty = odataReq.getUriInfo().getLastSegment().getNavigationProperty()
140
142
 
141
143
  // REVISIT: cannot be removed yet because of navigation of draft to non draft would add IsActiveEntity to .data
142
144
  if (navProperty.getPartner() && navProperty.getPartner().getReferentialConstraints().size) {
@@ -179,10 +181,10 @@ const _getFunctionParameters = (lastSegment, keyValues, service, target) => {
179
181
  return paramValues
180
182
  }
181
183
 
182
- const _getCopiedData = (odataReq, streaming, lastSegment, service, target) => {
184
+ const _getCopiedData = (odataReq, lastSegment, service, target) => {
183
185
  let data = odataReq.getBody() || {}
184
186
 
185
- if (streaming || lastSegment.getKind() === 'PRIMITIVE.PROPERTY') {
187
+ if (lastSegment.getKind() === 'PRIMITIVE.PROPERTY') {
186
188
  return { [lastSegment.getProperty().getName()]: data }
187
189
  }
188
190
 
@@ -219,8 +221,10 @@ const _getCopiedData = (odataReq, streaming, lastSegment, service, target) => {
219
221
  const getData = (component, odataReq, service, target) => {
220
222
  const segments = odataReq.getUriInfo().getPathSegments()
221
223
  const lastSegment = odataReq.getUriInfo().getLastSegment()
222
- const streaming = isStreaming(segments)
223
- const keyPredicates = streaming ? segments[segments.length - 2].getKeyPredicates() : lastSegment.getKeyPredicates()
224
+ const keyPredicates =
225
+ lastSegment.getKind() === 'PRIMITIVE.PROPERTY'
226
+ ? segments[segments.length - 2].getKeyPredicates()
227
+ : lastSegment.getKeyPredicates()
224
228
  const keyValues = _getParamData(keyPredicates)
225
229
 
226
230
  if (component === DATA_READ_HANDLER && _isFunctionInvocation(odataReq)) {
@@ -235,7 +239,7 @@ const getData = (component, odataReq, service, target) => {
235
239
  }
236
240
 
237
241
  // copy so that original payload is preserved
238
- const data = _getCopiedData(odataReq, streaming, lastSegment, service, target)
242
+ const data = _getCopiedData(odataReq, lastSegment, service, target)
239
243
 
240
244
  // Only to be done for post via navigation
241
245
  if (
@@ -8,6 +8,7 @@ const {
8
8
  const { getDeepSelect, getSimpleSelectCQN } = require('./handlerUtils')
9
9
  const { hasDeepUpdate } = require('../../../../common/composition/update')
10
10
  const { WRITE_EVENTS, CDS_EVENTS } = require('../../../../common/constants/events')
11
+ const { addEtagColumns } = require('../../../../common/utils/etag')
11
12
 
12
13
  const setLocationHeader = (req, { model }) => {
13
14
  const { odataRes } = req._
@@ -67,6 +68,9 @@ const readAfterWrite = async (req, srv, { operation, isBefore } = { isBefore: fa
67
68
  Object.defineProperty(query.SELECT, '_4odata', { value: true })
68
69
  _ensureKeysAreSelected(query)
69
70
 
71
+ // automatically add etag columns if not already there
72
+ addEtagColumns(query.SELECT.columns, req.target)
73
+
70
74
  // gracefully set location and no body if no read auth or not readable capability
71
75
  let result
72
76
  try {
@@ -7,6 +7,7 @@ const getTemplate = require('../../../../common/utils/template')
7
7
  const templateProcessor = require('../../../../common/utils/templateProcessor')
8
8
  const { omitValue, applyOmitValuesPreference } = require('./omitValues')
9
9
  const { setLocationHeader } = require('./readAfterWrite')
10
+ const normalizeTimestamp = require('../../../../common/utils/normalizeTimestamp')
10
11
 
11
12
  const METADATA = {
12
13
  $context: '*@odata.context',
@@ -210,6 +211,7 @@ const _processCategory = (req, category, elementInfo, options, previousResult) =
210
211
 
211
212
  case '@cds.Boolean':
212
213
  if (row[key] != null) row[key] = !!row[key]
214
+ break
213
215
 
214
216
  // no default
215
217
  }
@@ -273,6 +275,7 @@ const _pick = options => (element, target) => {
273
275
 
274
276
  if (element['@odata.etag']) categories.push('@odata.etag')
275
277
  if (element._type === 'cds.Decimal') categories.push('@cds.Decimal')
278
+ if (element._type === 'cds.Timestamp') categories.push('@cds.Timestamp')
276
279
  if (cds.db?.cqn2sql && element._type === 'cds.Boolean') categories.push('@cds.Boolean') // REVISIT: violates modularization -> do we still need that?
277
280
 
278
281
  categories.push(..._assocs(element, target))
@@ -373,8 +376,8 @@ const postProcessMinimal = (req, service, result) => {
373
376
  setLocationHeader(req, service)
374
377
 
375
378
  const etag = target._etag && target._etag.name
376
- if (timestamp && etag && etag in result) {
377
- return { '*@odata.etag': result[etag] === '$now' ? new Date(timestamp).toISOString() : result[etag] }
379
+ if (etag && etag in result) {
380
+ return { '*@odata.etag': result[etag] === '$now' ? normalizeTimestamp(timestamp) : result[etag] }
378
381
  }
379
382
 
380
383
  return null
@@ -1,15 +1,3 @@
1
- const cds = require('../../../../cds')
2
- const LOG = cds.log('odata')
3
-
4
- const { SELECT } = cds.ql
5
-
6
- const { isPathToDraft } = require('../../../../common/utils/cqn')
7
- const { deepCopyArray } = require('../../../../common/utils/copy')
8
- const { cqn2cqn4sql } = require('../../../../common/utils/cqn2cqn4sql')
9
- const { ensureDraftsSuffix } = require('../../../../fiori/utils/handler')
10
- const { removeIsActiveEntityRecursively, isActiveEntityRequested } = require('../../../../fiori/utils/where')
11
- const { getTransition } = require('../../../../common/utils/resolveView')
12
-
13
1
  const isStreaming = segments => {
14
2
  const lastSegment = segments[segments.length - 1]
15
3
  return (
@@ -19,116 +7,6 @@ const isStreaming = segments => {
19
7
  )
20
8
  }
21
9
 
22
- const _adaptSubSelectsDraft = select => {
23
- if (select.SELECT.from.ref) {
24
- const index = select.SELECT.from.ref.length - 1
25
- select.SELECT.from.ref[index] = ensureDraftsSuffix(select.SELECT.from.ref[index])
26
- }
27
-
28
- if (select.SELECT.where) {
29
- for (let i = 0; i < select.SELECT.where.length; i++) {
30
- const element = select.SELECT.where[i]
31
- if (element.SELECT) {
32
- _adaptSubSelectsDraft(element)
33
- } else if (element.xpr) {
34
- _adaptSubSelectsDraft({ SELECT: { from: {}, where: element.xpr } })
35
- }
36
- }
37
- }
38
- }
39
-
40
- const adaptStreamCQN = (cqn, isDraft = false) => {
41
- if (isDraft || !isActiveEntityRequested(cqn.SELECT.where)) {
42
- _adaptSubSelectsDraft(cqn)
43
- } else {
44
- cqn.SELECT.where = removeIsActiveEntityRecursively(cqn.SELECT.where)
45
- }
46
- }
47
-
48
- // eslint-disable-next-line complexity
49
- const getStreamProperties = (req, model) => {
50
- // new odata parser sets streaming property in SELECT.from
51
- const ref = (req.query.SELECT.columns && req.query.SELECT.columns[0].ref) || req.query.SELECT.from.ref
52
- const propertyName = ref[ref.length - 1]
53
- let mediaTypeProperty
54
- for (let key in req.target.elements) {
55
- const val = req.target.elements[key]
56
- if (val['@Core.MediaType'] && val.name === propertyName) {
57
- mediaTypeProperty = val
58
- break
59
- }
60
- }
61
-
62
- let contentType, contentDispositionFilename
63
- const columns = []
64
- if (typeof mediaTypeProperty['@Core.MediaType'] === 'object') {
65
- let contentTypeProperty = mediaTypeProperty['@Core.MediaType']['=']
66
- if (!req.target.elements[contentTypeProperty]) {
67
- LOG._warn &&
68
- LOG.warn(
69
- `@Core.MediaType in entity "${req.target.name}" points to property "${contentTypeProperty}" which was renamed or is not part of the projection. You must update the annotation value.`
70
- )
71
- const mapping = getTransition(req.target, cds.db).mapping
72
- const key = [...mapping.entries()].find(({ 1: val }) => val.ref[0] === contentTypeProperty)
73
- contentTypeProperty = key && key.length && key[0]
74
- }
75
- if (!req.target.elements[contentTypeProperty]) {
76
- LOG._warn && LOG.warn(`MediaType ${contentTypeProperty} not found in entity "${req.target.name}".`)
77
- } else {
78
- columns.push({ ref: [contentTypeProperty], as: 'contentType' })
79
- }
80
- } else {
81
- contentType = mediaTypeProperty['@Core.MediaType']
82
- }
83
- if (mediaTypeProperty['@Core.ContentDisposition.Filename']) {
84
- if (typeof mediaTypeProperty['@Core.ContentDisposition.Filename'] === 'object') {
85
- let contentDispositionProperty = mediaTypeProperty['@Core.ContentDisposition.Filename']['=']
86
- if (!req.target.elements[contentDispositionProperty]) {
87
- LOG._warn &&
88
- LOG.warn(
89
- `@Core.ContentDisposition.Filename in entity "${req.target.name}" points to property "${contentDispositionProperty}" which was renamed or is not part of the projection. You must update the annotation value.`
90
- )
91
- const mapping = getTransition(req.target, cds.db).mapping
92
- const key = [...mapping.entries()].find(({ 1: val }) => val.ref[0] === contentDispositionProperty)
93
- contentDispositionProperty = key && key.length && key[0]
94
- }
95
- if (!req.target.elements[contentDispositionProperty]) {
96
- LOG._warn &&
97
- LOG.warn(`ContentDisposition ${contentDispositionProperty} not found in entity "${req.target.name}".`)
98
- } else {
99
- columns.push({ ref: [contentDispositionProperty], as: 'contentDispositionFilename' })
100
- }
101
- } else {
102
- contentDispositionFilename = mediaTypeProperty['@Core.ContentDisposition.Filename']
103
- }
104
- }
105
- const contentDispositionType = mediaTypeProperty['@Core.ContentDisposition.Type']
106
-
107
- if (columns.length && cds.db && !req.target._hasPersistenceSkip) {
108
- // used cloned path
109
- let select = SELECT.one.from({ ref: deepCopyArray(req.query.SELECT.from.ref) }).columns(columns)
110
-
111
- const pathToDraft = isPathToDraft(select.SELECT.from.ref, model)
112
- if (req.target._isDraftEnabled && pathToDraft) {
113
- select = cqn2cqn4sql(select, model)
114
- adaptStreamCQN(select, pathToDraft)
115
- }
116
-
117
- return cds
118
- .tx(req)
119
- .run(select)
120
- .then(res => ({
121
- contentType: (res && res.contentType) || contentType,
122
- contentDispositionFilename: (res && res.contentDispositionFilename) || contentDispositionFilename,
123
- contentDispositionType
124
- }))
125
- }
126
-
127
- return Promise.resolve({ contentType, contentDispositionFilename, contentDispositionType })
128
- }
129
-
130
10
  module.exports = {
131
- isStreaming,
132
- getStreamProperties,
133
- adaptStreamCQN
11
+ isStreaming
134
12
  }