@sap/cds 6.8.4 → 7.0.1

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 (217) hide show
  1. package/CHANGELOG.md +66 -4
  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 +3 -3
  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 +2 -2
  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/protocols/odata-v4.js +9 -4
  45. package/lib/srv/srv-api.js +9 -3
  46. package/lib/srv/srv-dispatch.js +12 -9
  47. package/lib/srv/srv-models.js +4 -21
  48. package/lib/srv/srv-tx.js +15 -12
  49. package/lib/utils/cds-test.js +14 -9
  50. package/lib/utils/cds-utils.js +2 -12
  51. package/lib/utils/check-version.js +17 -0
  52. package/{bin/build → lib/utils}/csv-reader.js +23 -24
  53. package/libx/_runtime/auth/index.js +27 -23
  54. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +15 -72
  55. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  56. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +0 -2
  57. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +33 -63
  58. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +14 -18
  59. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +15 -5
  60. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +5 -4
  61. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +37 -40
  62. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +7 -1
  63. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +101 -38
  64. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/AbstractError.js +5 -1
  65. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +5 -8
  66. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +2 -1
  67. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +9 -8
  68. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  69. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +15 -11
  70. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +4 -0
  71. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +5 -2
  72. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -123
  73. package/libx/_runtime/cds-services/services/Service.js +79 -107
  74. package/libx/_runtime/cds-services/services/utils/columns.js +23 -19
  75. package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -1
  76. package/libx/_runtime/cds-services/services/utils/differ.js +7 -2
  77. package/libx/_runtime/cds-services/util/assert.js +65 -2
  78. package/libx/_runtime/common/composition/data.js +1 -0
  79. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  80. package/libx/_runtime/common/generic/auth/restrict.js +5 -10
  81. package/libx/_runtime/common/generic/auth/restrictions.js +40 -0
  82. package/libx/_runtime/common/generic/auth/utils.js +1 -2
  83. package/libx/_runtime/common/generic/crud.js +32 -16
  84. package/libx/_runtime/common/generic/etag.js +133 -104
  85. package/libx/_runtime/common/generic/input.js +6 -21
  86. package/libx/_runtime/common/generic/put.js +1 -1
  87. package/libx/_runtime/common/generic/stream.js +52 -0
  88. package/libx/_runtime/common/generic/temporal.js +25 -8
  89. package/libx/_runtime/common/i18n/messages.properties +0 -2
  90. package/libx/_runtime/common/utils/cqn.js +1 -1
  91. package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
  92. package/libx/_runtime/common/utils/csn.js +0 -51
  93. package/libx/_runtime/common/utils/etag.js +30 -0
  94. package/libx/_runtime/common/utils/keys.js +1 -1
  95. package/libx/_runtime/common/utils/normalizeTimestamp.js +25 -0
  96. package/libx/_runtime/common/utils/path.js +1 -1
  97. package/libx/_runtime/common/utils/resolveView.js +2 -1
  98. package/libx/_runtime/common/utils/rewriteAsterisks.js +6 -4
  99. package/libx/_runtime/common/utils/search2cqn4sql.js +12 -16
  100. package/libx/_runtime/common/utils/stream.js +140 -0
  101. package/libx/_runtime/common/utils/streamProp.js +29 -12
  102. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +0 -2
  103. package/libx/_runtime/db/generic/index.js +0 -2
  104. package/libx/_runtime/db/query/delete.js +2 -2
  105. package/libx/_runtime/db/query/insert.js +2 -2
  106. package/libx/_runtime/db/query/read.js +2 -2
  107. package/libx/_runtime/db/query/run.js +2 -2
  108. package/libx/_runtime/db/query/update.js +2 -2
  109. package/libx/_runtime/db/sql-builder/BaseBuilder.js +0 -6
  110. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +23 -12
  111. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +18 -6
  112. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -0
  113. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -7
  114. package/libx/_runtime/db/sql-builder/UpsertBuilder.js +1 -0
  115. package/libx/_runtime/db/utils/normalizeTimeData.js +7 -3
  116. package/libx/_runtime/fiori/draft.js +2 -0
  117. package/libx/_runtime/fiori/generic/activate.js +8 -9
  118. package/libx/_runtime/fiori/generic/before.js +30 -20
  119. package/libx/_runtime/fiori/generic/cancel.js +5 -3
  120. package/libx/_runtime/fiori/generic/delete.js +5 -3
  121. package/libx/_runtime/fiori/generic/edit.js +7 -7
  122. package/libx/_runtime/fiori/generic/index.js +10 -16
  123. package/libx/_runtime/fiori/generic/new.js +5 -3
  124. package/libx/_runtime/fiori/generic/patch.js +11 -8
  125. package/libx/_runtime/fiori/generic/prepare.js +13 -6
  126. package/libx/_runtime/fiori/generic/read.js +12 -6
  127. package/libx/_runtime/fiori/lean-draft.js +207 -152
  128. package/libx/_runtime/fiori/utils/delete.js +10 -5
  129. package/libx/_runtime/fiori/utils/req.js +17 -5
  130. package/libx/_runtime/fiori/utils/stream.js +36 -0
  131. package/libx/_runtime/hana/Service.js +12 -9
  132. package/libx/_runtime/hana/conversion.js +10 -15
  133. package/libx/_runtime/hana/driver.js +2 -0
  134. package/libx/_runtime/hana/execute.js +28 -6
  135. package/libx/_runtime/hana/pool.js +36 -122
  136. package/libx/_runtime/hana/search2cqn4sql.js +34 -36
  137. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -6
  138. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -1
  139. package/libx/_runtime/messaging/enterprise-messaging.js +10 -58
  140. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  141. package/libx/_runtime/remote/Service.js +20 -1
  142. package/libx/_runtime/remote/utils/client.js +3 -5
  143. package/libx/_runtime/sqlite/Service.js +4 -6
  144. package/libx/_runtime/sqlite/conversion.js +3 -13
  145. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +9 -6
  146. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +6 -1
  147. package/libx/_runtime/sqlite/execute.js +5 -16
  148. package/libx/odata/afterburner.js +22 -6
  149. package/libx/odata/grammar.pegjs +6 -1
  150. package/libx/odata/parser.js +1 -1
  151. package/libx/rest/RestAdapter.js +16 -9
  152. package/libx/rest/RestRequest.js +1 -1
  153. package/libx/rest/middleware/input.js +2 -1
  154. package/libx/rest/middleware/operation.js +1 -0
  155. package/libx/rest/middleware/parse.js +3 -2
  156. package/libx/rest/middleware/payload.js +9 -8
  157. package/libx/rest/middleware/read.js +1 -0
  158. package/package.json +9 -16
  159. package/server.js +1 -1
  160. package/app/fiori/preview.js +0 -270
  161. package/app/fiori/routes.js +0 -59
  162. package/bin/build/buildTaskEngine.js +0 -360
  163. package/bin/build/buildTaskFactory.js +0 -283
  164. package/bin/build/buildTaskHandler.js +0 -241
  165. package/bin/build/buildTaskProvider.js +0 -22
  166. package/bin/build/buildTaskProviderFactory.js +0 -175
  167. package/bin/build/cds.js +0 -5
  168. package/bin/build/constants.js +0 -66
  169. package/bin/build/index.js +0 -58
  170. package/bin/build/provider/buildTaskHandlerEdmx.js +0 -82
  171. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +0 -131
  172. package/bin/build/provider/buildTaskHandlerInternal.js +0 -254
  173. package/bin/build/provider/buildTaskProviderInternal.js +0 -383
  174. package/bin/build/provider/fiori/index.js +0 -171
  175. package/bin/build/provider/hana/2migration.js +0 -179
  176. package/bin/build/provider/hana/index.js +0 -505
  177. package/bin/build/provider/hana/migrationtable.js +0 -472
  178. package/bin/build/provider/hana/template/.hdiconfig-haas +0 -163
  179. package/bin/build/provider/hana/template/.hdiconfig-hanacloud +0 -137
  180. package/bin/build/provider/hana/template/.hdinamespace +0 -4
  181. package/bin/build/provider/hana/template/package.json +0 -12
  182. package/bin/build/provider/hana/template/undeploy.json +0 -5
  183. package/bin/build/provider/java/index.js +0 -111
  184. package/bin/build/provider/java-cf/index.js +0 -1
  185. package/bin/build/provider/mtx/index.js +0 -268
  186. package/bin/build/provider/mtx/resourcesTarBuilder.js +0 -95
  187. package/bin/build/provider/mtx-extension/index.js +0 -131
  188. package/bin/build/provider/mtx-sidecar/index.js +0 -137
  189. package/bin/build/provider/node-cf/index.js +0 -1
  190. package/bin/build/provider/nodejs/index.js +0 -192
  191. package/bin/build/util.js +0 -299
  192. package/bin/cds.js +0 -125
  193. package/bin/deploy/to-hana/cfUtil.js +0 -355
  194. package/bin/deploy/to-hana/gitUtil.js +0 -57
  195. package/bin/deploy/to-hana/hana.js +0 -306
  196. package/bin/deploy/to-hana/hdiDeployUtil.js +0 -153
  197. package/bin/deploy/to-hana/index.js +0 -16
  198. package/bin/deploy/to-hana/mtaUtil.js +0 -170
  199. package/bin/mtx/in-cds.js +0 -17
  200. package/bin/plugins.js +0 -32
  201. package/bin/run.js +0 -24
  202. package/bin/utils/log.js +0 -24
  203. package/bin/version.js +0 -178
  204. package/libx/_runtime/audit/Service.js +0 -222
  205. package/libx/_runtime/audit/generic/personal/access.js +0 -61
  206. package/libx/_runtime/audit/generic/personal/index.js +0 -56
  207. package/libx/_runtime/audit/generic/personal/modification.js +0 -132
  208. package/libx/_runtime/audit/generic/personal/utils.js +0 -186
  209. package/libx/_runtime/audit/utils/log.js +0 -23
  210. package/libx/_runtime/audit/utils/v2.js +0 -176
  211. package/libx/_runtime/db/data-conversion/timestamp.js +0 -9
  212. package/libx/_runtime/db/generic/integrity.js +0 -455
  213. package/srv/audit-log.cds +0 -87
  214. package/srv/mtx.cds +0 -2
  215. package/srv/mtx.js +0 -8
  216. /package/lib/{core → linked}/classes.js +0 -0
  217. /package/lib/{core → linked}/entities.js +0 -0
@@ -7,6 +7,7 @@ const ResourceKind = odata.uri.UriResource.ResourceKind
7
7
  const EdmPrimitiveTypeKind = odata.edm.EdmPrimitiveTypeKind
8
8
  const { getFeatureNotSupportedError } = require('../../../util/errors')
9
9
  const { getSegmentKeyValue } = require('./utils')
10
+ const normalizeTimestamp = require('../../../../common/utils/normalizeTimestamp')
10
11
 
11
12
  const _binaryOperatorToCQN = new Map([
12
13
  [BinaryOperatorKind.EQ, '='],
@@ -46,10 +47,10 @@ class ExpressionToCQN {
46
47
  return { val: parseFloat(value) }
47
48
  case EdmPrimitiveTypeKind.DateTimeOffset: {
48
49
  try {
49
- let val = new Date(value).toISOString()
50
- // cut off ms if cds.DateTime
51
- if (expression._cdsType === 'cds.DateTime') val = val.replace(/\.\d\d\dZ$/, 'Z')
52
- return { val }
50
+ if (expression._cdsType === 'cds.DateTime')
51
+ // cut off ms if cds.DateTime
52
+ return { val: new Date(value).toISOString().replace(/\.\d\d\dZ$/, 'Z') }
53
+ return { val: normalizeTimestamp(value) }
53
54
  } catch (e) {
54
55
  throw Object.assign(new Error(`The type 'Edm.DateTimeOffset' is not compatible with '${value}'`), {
55
56
  status: 400
@@ -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
  }
@@ -2,12 +2,9 @@
2
2
 
3
3
  const UriSyntaxError = require('../errors/UriSyntaxError')
4
4
 
5
- const IDENTIFIER =
6
- '(?:(?:_|\\p{Letter}|\\p{Letter_Number})' +
7
- '(?:_|\\p{Letter}|\\p{Letter_Number}|\\p{Decimal_Number}' +
8
- '|\\p{Nonspacing_Mark}|\\p{Spacing_Mark}|\\p{Connector_Punctuation}|\\p{Format}){0,127})'
5
+ const IDENTIFIER = '([_\\p{L}\\p{Nl}][_\\p{L}\\p{Nl}\\p{Nd}\\p{Mn}\\p{Mc}\\p{Pc}\\p{Cf}]{0,127})'
9
6
  const IDENTIFIER_REGEXP = new RegExp('^' + IDENTIFIER, 'u')
10
- const QUALIFIED_NAME_REGEXP = new RegExp('^' + IDENTIFIER + '(?:\\.' + IDENTIFIER + ')+', 'u')
7
+ const QUALIFIED_NAME_REGEXP = new RegExp('^' + IDENTIFIER + '(\\.' + IDENTIFIER + ')+', 'u')
11
8
  const PARAMETER_ALIAS_NAME_REGEXP = new RegExp('^@' + IDENTIFIER, 'u')
12
9
 
13
10
  const BOOLEAN_VALUE_REGEXP = new RegExp('^(?:true|false)', 'i')
@@ -696,9 +693,9 @@ class UriTokenizer {
696
693
  * @private
697
694
  */
698
695
  _nextWithRegularExpression (regexp) {
699
- const parsed = regexp.exec(this._parseString.substring(this._index))
700
- if (!parsed) return false
701
- this._index += parsed[0].length
696
+ const matched = this._parseString.substring(this._index).match(regexp)
697
+ if (!matched) return false
698
+ this._index += matched[0].length
702
699
  return true
703
700
  }
704
701
 
@@ -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