@sap/cds 5.6.2 → 5.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/_i18n/i18n_fr.properties +4 -4
  3. package/apis/cds.d.ts +7 -10
  4. package/apis/connect.d.ts +3 -3
  5. package/apis/core.d.ts +2 -4
  6. package/apis/models.d.ts +2 -3
  7. package/apis/ql.d.ts +0 -1
  8. package/apis/services.d.ts +7 -3
  9. package/bin/build/buildTaskFactory.js +16 -10
  10. package/bin/build/buildTaskProviderFactory.js +3 -3
  11. package/bin/build/constants.js +2 -1
  12. package/bin/build/provider/buildTaskProviderInternal.js +14 -14
  13. package/bin/build/provider/hana/2migration.js +2 -3
  14. package/bin/build/provider/hana/index.js +34 -0
  15. package/bin/build/provider/hana/migrationtable.js +90 -22
  16. package/bin/build/provider/hana/template/undeploy.json +5 -0
  17. package/bin/build/provider/node-cf/index.js +9 -2
  18. package/bin/serve.js +16 -18
  19. package/lib/compile/cdsc.js +15 -5
  20. package/lib/compile/etc/_localized.js +4 -4
  21. package/lib/compile/extend.js +8 -0
  22. package/lib/compile/index.js +3 -1
  23. package/lib/compile/minify.js +61 -0
  24. package/lib/compile/resolve.js +4 -1
  25. package/lib/compile/to/gql.js +9 -0
  26. package/lib/compile/to/sql.js +26 -30
  27. package/lib/connect/index.js +1 -1
  28. package/lib/core/entities.js +0 -3
  29. package/lib/core/infer.js +1 -0
  30. package/lib/core/reflect.js +0 -34
  31. package/lib/deploy.js +25 -17
  32. package/lib/env/defaults.js +3 -1
  33. package/lib/env/index.js +13 -4
  34. package/lib/env/presets.js +38 -0
  35. package/lib/env/requires.js +16 -11
  36. package/lib/index.js +13 -11
  37. package/lib/log/format/kibana.js +4 -2
  38. package/lib/log/index.js +2 -2
  39. package/lib/ql/Whereable.js +1 -0
  40. package/lib/req/cds-context.js +79 -0
  41. package/lib/req/context.js +5 -77
  42. package/lib/req/request.js +1 -1
  43. package/lib/serve/Service-api.js +8 -4
  44. package/lib/serve/Service-dispatch.js +0 -7
  45. package/lib/serve/Service-methods.js +6 -8
  46. package/lib/serve/Transaction.js +35 -30
  47. package/lib/serve/adapters.js +1 -4
  48. package/lib/utils/axios.js +1 -1
  49. package/libx/_runtime/audit/Service.js +44 -20
  50. package/libx/_runtime/audit/generic/personal/access.js +16 -11
  51. package/libx/_runtime/audit/generic/personal/modification.js +5 -5
  52. package/libx/_runtime/audit/generic/personal/utils.js +46 -37
  53. package/libx/_runtime/{common/auth → auth}/index.js +21 -7
  54. package/libx/_runtime/{common/auth → auth}/strategies/JWT.js +2 -2
  55. package/libx/_runtime/{common/auth → auth}/strategies/basic.js +2 -2
  56. package/libx/_runtime/{common/auth → auth}/strategies/dummy.js +1 -1
  57. package/libx/_runtime/{common/auth → auth}/strategies/mock.js +2 -2
  58. package/libx/_runtime/{common/auth → auth}/strategies/utils/uaa.js +1 -1
  59. package/libx/_runtime/{common/auth → auth}/strategies/utils/xssec.js +0 -0
  60. package/libx/_runtime/{common/auth → auth}/strategies/xsuaa.js +2 -2
  61. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +7 -2
  62. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +0 -7
  63. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +0 -8
  64. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +3 -4
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +6 -7
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +3 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -11
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +16 -6
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -65
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +0 -7
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +3 -66
  72. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +26 -0
  73. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +5 -5
  74. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +2 -2
  75. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +13 -10
  76. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -7
  77. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -8
  78. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +54 -76
  79. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
  80. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
  81. package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
  82. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
  83. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
  84. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
  85. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -2
  86. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
  87. package/libx/_runtime/cds-services/services/Service.js +0 -6
  88. package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
  89. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -7
  90. package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
  91. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
  92. package/libx/_runtime/cds-services/util/assert.js +1 -262
  93. package/libx/_runtime/cds.js +6 -9
  94. package/libx/_runtime/common/aspects/entity.js +1 -1
  95. package/libx/_runtime/common/composition/delete.js +4 -2
  96. package/libx/_runtime/common/composition/update.js +22 -35
  97. package/libx/_runtime/common/composition/utils.js +3 -7
  98. package/libx/_runtime/common/error/standardError.js +11 -0
  99. package/libx/_runtime/common/generic/auth.js +63 -33
  100. package/libx/_runtime/common/generic/crud.js +11 -23
  101. package/libx/_runtime/common/generic/input.js +20 -0
  102. package/libx/_runtime/common/generic/paging.js +2 -2
  103. package/libx/_runtime/common/generic/put.js +4 -10
  104. package/libx/_runtime/common/generic/sorting.js +12 -30
  105. package/libx/_runtime/common/perf/index.js +24 -0
  106. package/libx/_runtime/common/utils/cqn.js +58 -1
  107. package/libx/_runtime/common/utils/cqn2cqn4sql.js +297 -121
  108. package/libx/_runtime/common/utils/csn.js +38 -56
  109. package/libx/_runtime/common/utils/entityFromCqn.js +6 -6
  110. package/libx/_runtime/common/utils/resolveView.js +4 -5
  111. package/libx/_runtime/common/utils/rewriteAsterisks.js +46 -5
  112. package/libx/_runtime/common/utils/search2cqn4sql.js +21 -9
  113. package/libx/_runtime/common/utils/structured.js +35 -25
  114. package/libx/_runtime/db/Service.js +0 -6
  115. package/libx/_runtime/db/expand/expand-v2.js +130 -0
  116. package/libx/_runtime/db/expand/expandCQNToJoin.js +38 -52
  117. package/libx/_runtime/db/expand/index.js +3 -1
  118. package/libx/_runtime/db/generic/arrayed.js +3 -1
  119. package/libx/_runtime/db/generic/input.js +52 -10
  120. package/libx/_runtime/db/generic/integrity.js +367 -26
  121. package/libx/_runtime/db/generic/virtual.js +51 -13
  122. package/libx/_runtime/db/query/update.js +9 -3
  123. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +8 -9
  124. package/libx/_runtime/{common → db}/utils/propagateForeignKeys.js +11 -14
  125. package/libx/_runtime/fiori/generic/activate.js +1 -0
  126. package/libx/_runtime/fiori/generic/before.js +2 -1
  127. package/libx/_runtime/fiori/generic/edit.js +1 -0
  128. package/libx/_runtime/fiori/generic/patch.js +1 -1
  129. package/libx/_runtime/fiori/generic/read.js +155 -57
  130. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +0 -4
  131. package/libx/_runtime/fiori/uiflex/index.js +1 -1
  132. package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +6 -4
  133. package/libx/_runtime/fiori/utils/delete.js +7 -1
  134. package/libx/_runtime/hana/Service.js +1 -8
  135. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
  136. package/libx/_runtime/hana/execute.js +10 -4
  137. package/libx/_runtime/hana/pool.js +55 -45
  138. package/libx/_runtime/hana/search.js +7 -6
  139. package/libx/_runtime/hana/search2cqn4sql.js +8 -5
  140. package/libx/_runtime/hana/searchToContains.js +3 -1
  141. package/libx/_runtime/index.js +5 -5
  142. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
  143. package/libx/_runtime/messaging/Outbox.js +53 -0
  144. package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
  145. package/libx/_runtime/messaging/common-utils/connections.js +14 -9
  146. package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
  147. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
  148. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
  149. package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
  150. package/libx/_runtime/messaging/file-based.js +5 -5
  151. package/libx/_runtime/messaging/message-queuing.js +2 -3
  152. package/libx/_runtime/messaging/outbox/OutboxRunner.js +75 -0
  153. package/libx/_runtime/messaging/outbox/utils.js +192 -0
  154. package/libx/_runtime/messaging/service.js +16 -30
  155. package/libx/_runtime/remote/Service.js +15 -0
  156. package/libx/_runtime/remote/utils/client.js +15 -3
  157. package/libx/_runtime/remote/utils/{dataConversion.js → data.js} +12 -2
  158. package/libx/_runtime/sqlite/Service.js +7 -10
  159. package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +19 -0
  160. package/libx/_runtime/sqlite/execute.js +18 -12
  161. package/libx/_runtime/types/api.js +2 -1
  162. package/libx/gql/resolvers/parse/ast2cqn/columns.js +1 -1
  163. package/libx/odata/{odata2cqn/afterburner.js → afterburner.js} +28 -16
  164. package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
  165. package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +182 -118
  166. package/libx/odata/index.js +18 -15
  167. package/libx/odata/parser.js +1 -0
  168. package/libx/odata/utils.js +57 -0
  169. package/libx/rest/RestAdapter.js +2 -6
  170. package/libx/rest/utils/data.js +1 -6
  171. package/package.json +4 -3
  172. package/server.js +4 -5
  173. package/srv/audit-log.cds +87 -0
  174. package/{libx/_runtime/fiori/uiflex/extensibility/index.cds → srv/flex.cds} +0 -0
  175. package/srv/flex.js +1 -0
  176. package/srv/outbox.cds +11 -0
  177. package/srv/outbox.js +0 -0
  178. package/libx/_runtime/cds-services/adapter/perf/performance.js +0 -104
  179. package/libx/_runtime/cds-services/adapter/perf/performanceMeasurement.js +0 -33
  180. package/libx/odata/odata2cqn/index.js +0 -3
  181. package/libx/odata/odata2cqn/parser.js +0 -1
  182. package/libx/odata/readme.md +0 -1
  183. package/libx/odata/utils/index.js +0 -64
@@ -25,7 +25,7 @@ const FORMAT_REGEXP = new RegExp(
25
25
  )
26
26
 
27
27
  const parseNonNegativeInteger = value => {
28
- let tokenizer = new UriTokenizer(value)
28
+ const tokenizer = new UriTokenizer(value)
29
29
  if (tokenizer.next(TokenKind.UnsignedIntegerValue) && tokenizer.next(TokenKind.EOF)) {
30
30
  const result = Number.parseInt(value, 10)
31
31
  if (Number.isSafeInteger(result)) return result
@@ -66,19 +66,22 @@ queryOptionParserMap.set(QueryOptions.FORMAT, value => {
66
66
  throw new UriSyntaxError(UriSyntaxError.Message.WRONG_OPTION_VALUE, QueryOptions.FORMAT)
67
67
  })
68
68
  queryOptionParserMap.set(QueryOptions.SEARCH, value => {
69
- let tokenizer = new UriTokenizer(value)
69
+ const trimmedValue = value.trim()
70
+ if (!trimmedValue) return
71
+
72
+ const tokenizer = new UriTokenizer(trimmedValue)
70
73
  const searchOption = new SearchParser().parse(tokenizer)
71
74
  tokenizer.requireNext(TokenKind.EOF)
72
75
  return searchOption
73
76
  })
74
77
  queryOptionParserMap.set(QueryOptions.FILTER, (value, edm, referringType, crossjoinEntitySets, aliases) => {
75
- let tokenizer = new UriTokenizer(value)
78
+ const tokenizer = new UriTokenizer(value)
76
79
  const filterOption = new FilterParser(edm).parse(tokenizer, referringType, crossjoinEntitySets, aliases)
77
80
  tokenizer.requireNext(TokenKind.EOF)
78
81
  return filterOption
79
82
  })
80
83
  queryOptionParserMap.set(QueryOptions.ORDERBY, (value, edm, referringType, crossjoinEntitySets, aliases) => {
81
- let tokenizer = new UriTokenizer(value)
84
+ const tokenizer = new UriTokenizer(value)
82
85
  const orderByOption = new OrderByParser(edm).parse(tokenizer, referringType, crossjoinEntitySets, aliases)
83
86
  tokenizer.requireNext(TokenKind.EOF)
84
87
  return orderByOption
@@ -86,14 +89,14 @@ queryOptionParserMap.set(QueryOptions.ORDERBY, (value, edm, referringType, cross
86
89
  queryOptionParserMap.set(
87
90
  QueryOptions.SELECT,
88
91
  (value, edm, referringType, crossjoinEntitySets, aliases, isCollection) => {
89
- let tokenizer = new UriTokenizer(value)
92
+ const tokenizer = new UriTokenizer(value)
90
93
  const selectOption = new SelectParser(edm).parse(tokenizer, referringType, isCollection)
91
94
  tokenizer.requireNext(TokenKind.EOF)
92
95
  return selectOption
93
96
  }
94
97
  )
95
98
  queryOptionParserMap.set(QueryOptions.EXPAND, (value, edm, referringType, crossjoinEntitySets, aliases) => {
96
- let tokenizer = new UriTokenizer(value)
99
+ const tokenizer = new UriTokenizer(value)
97
100
  const expandOption = new ExpandParser(edm).parse(tokenizer, referringType, crossjoinEntitySets, aliases)
98
101
  tokenizer.requireNext(TokenKind.EOF)
99
102
  return expandOption
@@ -170,7 +173,7 @@ class UriParser {
170
173
 
171
174
  // $apply must be parsed first.
172
175
  if (queryOptions[QueryOptions.APPLY] !== undefined) {
173
- let tokenizer = new UriTokenizer(queryOptions[QueryOptions.APPLY])
176
+ const tokenizer = new UriTokenizer(queryOptions[QueryOptions.APPLY])
174
177
  referringType = new TransientStructuredType(referringType)
175
178
  const parseApplyQueryOptionPm = parseQueryOptionsPm
176
179
  ? parseQueryOptionsPm.createChild('Parse query option $apply').start()
@@ -219,7 +222,7 @@ class UriParser {
219
222
  */
220
223
  _parseRelativeUri (uriPathSegments, aliases) {
221
224
  let currentUriSegment = uriPathSegments[0]
222
- let tokenizer = new UriTokenizer(currentUriSegment)
225
+ const tokenizer = new UriTokenizer(currentUriSegment)
223
226
 
224
227
  if (tokenizer.next(TokenKind.EOF)) {
225
228
  uriPathSegments.shift()
@@ -257,7 +260,7 @@ class UriParser {
257
260
  // Type casts are explicitly not supported (although the parser can parse them)
258
261
  FeatureSupport.failUnsupported(FeatureSupport.features.TypeCast, uriPathSegment, 0)
259
262
 
260
- let tokenizer = new UriTokenizer(uriPathSegment)
263
+ const tokenizer = new UriTokenizer(uriPathSegment)
261
264
  tokenizer.requireNext(TokenKind.QualifiedName)
262
265
  const qualifiedName = tokenizer.getText()
263
266
  const type = this._edm.getEntityType(FullQualifiedName.createFromNameSpaceAndName(qualifiedName))
@@ -275,7 +278,7 @@ class UriParser {
275
278
  */
276
279
  _parseResourcePath (uriPathSegments, aliases) {
277
280
  let currentUriSegment = uriPathSegments[0]
278
- let tokenizer = new UriTokenizer(currentUriSegment)
281
+ const tokenizer = new UriTokenizer(currentUriSegment)
279
282
 
280
283
  if (tokenizer.next(TokenKind.ALL)) {
281
284
  FeatureSupport.failUnsupported(FeatureSupport.features.All)
@@ -34,13 +34,6 @@ class ConditionalRequestControlCommand extends Command {
34
34
  execute (next) {
35
35
  // Validate that the ETag validation has been called by the data handler.
36
36
  if (this._request.isConditional()) {
37
- if (!this._request.validateEtagHasBeenCalled()) {
38
- throw new InternalServerError(
39
- 'Error in conditional request processing. ' +
40
- ' The function validateEtag(etag) has to be called by the application.'
41
- )
42
- }
43
-
44
37
  // Do not set statusCode directly because of internal response cache.
45
38
  if (this._request.getETAGValidationStatus() === StatusCodes.NOT_MODIFIED) {
46
39
  this._response.setStatusCode(StatusCodes.NOT_MODIFIED)
@@ -34,14 +34,6 @@ class ConditionalRequestValidator {
34
34
  ) {
35
35
  throw new PreconditionFailedError()
36
36
  }
37
- } else if (
38
- method !== HttpMethods.GET ||
39
- (ifMatch && ifMatch.trim() !== '*' && ifMatch.trim() !== '"*"') ||
40
- ifNoneMatch
41
- ) {
42
- // A GET request with an If-Match header value of '*' (or '"*"', despite not in RFC 7232) is allowed,
43
- // but otherwise concurrency-control headers are not expected here.
44
- throw new ConflictError('The requested resource is not concurrent')
45
37
  }
46
38
  }
47
39
  }
@@ -1,9 +1,10 @@
1
1
  const cds = require('../../../../cds')
2
+ const LOG = cds.log('odata')
3
+
2
4
  const { SELECT } = cds.ql
3
- const { isActiveEntityRequested } = require('../../../../fiori/utils/where')
4
- const { ensureDraftsSuffix } = require('../../../../fiori/utils/handler')
5
- const { cqn2cqn4sql } = require('../../../../common/utils/cqn2cqn4sql')
6
- const { findCsnTargetFor } = require('../../../../common/utils/csn')
5
+
6
+ const { targetFromPath, isPathToDraft } = require('../../../../common/utils/cqn')
7
+ const { deepCopyArray } = require('../../../../common/utils/copy')
7
8
 
8
9
  const isStreaming = segments => {
9
10
  const lastSegment = segments[segments.length - 1]
@@ -14,87 +15,64 @@ const isStreaming = segments => {
14
15
  )
15
16
  }
16
17
 
17
- const _getProperties = async (properties, req) => {
18
- const isActiveRequested = isActiveEntityRequested(req.query.SELECT.from.ref[0].where)
19
-
20
- // REVISIT DRAFT HANDLING: cqn2cqn4sql should not happen here, but adaptStreamCQN relies on exists clause
21
- const cqn = cqn2cqn4sql(SELECT.one(req.query.SELECT.from), req._model).columns(properties)
22
-
23
- // REVISIT: renaming of media type property (e.g., mimeType as MimeType in AFC) not reflected in @Core.MediaType
24
- if (req.target.query && req.target.query._target && !req.target.drafts) {
25
- cqn.SELECT.from.ref[0] = req.target.query._target.name
26
- }
27
-
28
- if (!isActiveRequested) {
29
- cqn.SELECT.from.ref[0] = ensureDraftsSuffix(cqn.SELECT.from.ref[0])
30
- }
31
-
32
- try {
33
- return await cds.tx(req).run(cqn)
34
- } catch (e) {
35
- // REVISIT: why ignore?
36
- }
37
- }
18
+ const getStreamProperties = (req, model) => {
19
+ const mediaTypeProperty = Object.values(req.target.elements).find(val => val['@Core.MediaType'])
38
20
 
39
- const _getDynamicProperties = (contentType, contentDisposition) => {
40
- const properties = {}
41
- if (contentType && typeof contentType === 'object') {
42
- properties.contentType = Object.values(contentType)[0]
21
+ let contentType, contentDisposition
22
+ const columns = []
23
+ if (typeof mediaTypeProperty['@Core.MediaType'] === 'object') {
24
+ let contentTypeProperty = mediaTypeProperty['@Core.MediaType']['=']
25
+ if (!req.target.elements[contentTypeProperty]) {
26
+ LOG._warn &&
27
+ LOG.warn(
28
+ `@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.`
29
+ )
30
+ contentTypeProperty = (Object.values(req.target.elements).find(val => val['@Core.IsMediaType']) || {}).name
31
+ }
32
+ if (!req.target.elements[contentTypeProperty]) {
33
+ LOG._warn && LOG.warn(`No @Core.IsMediaType found in entity "${req.target.name}".`)
34
+ } else {
35
+ columns.push({ ref: [contentTypeProperty], as: 'contentType' })
36
+ }
37
+ } else {
38
+ contentType = mediaTypeProperty['@Core.MediaType']
43
39
  }
44
- if (contentDisposition) {
45
- properties.contentDisposition = Object.values(contentDisposition)[0]
40
+ if (mediaTypeProperty['@Core.ContentDisposition.Filename']) {
41
+ if (typeof mediaTypeProperty['@Core.ContentDisposition.Filename'] === 'object') {
42
+ const contentDispositionProperty = mediaTypeProperty['@Core.ContentDisposition.Filename']['=']
43
+ if (!req.target.elements[contentDispositionProperty]) {
44
+ LOG._warn &&
45
+ LOG.warn(
46
+ `@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.`
47
+ )
48
+ } else {
49
+ columns.push({ ref: [contentDispositionProperty], as: 'contentDisposition' })
50
+ }
51
+ } else {
52
+ contentDisposition = mediaTypeProperty['@Core.ContentDisposition.Filename']
53
+ }
46
54
  }
47
55
 
48
- return properties
49
- }
50
-
51
- const getStreamProperties = async (segments, srv, req) => {
52
- // REVISIT: we need to read directly from db, which might not be there!
53
- if (!cds.db) return {}
56
+ if (columns.length && cds.db && !req.target._hasPersistenceSkip) {
57
+ // used cloned path
58
+ const select = SELECT.one.from({ ref: deepCopyArray(req.query.SELECT.from.ref) }).columns(columns)
54
59
 
55
- let contentType, entityName, namespace, contentDisposition
56
- const previous = segments[segments.length - 2]
57
- if (previous.getKind() === 'ENTITY') {
58
- entityName = previous.getEntitySet().getName()
59
- namespace = previous.getEdmType().getFullQualifiedName().namespace
60
- } else if (previous.getKind() === 'NAVIGATION.TO.ONE' && previous.getTarget()) {
61
- entityName = previous.getTarget().getName()
62
- namespace = previous.getTarget().getEntityType().getFullQualifiedName().namespace
63
- }
60
+ if (req.target._isDraftEnabled && isPathToDraft(select.SELECT.from.ref, model))
61
+ select.SELECT.from.ref[0].id = select.SELECT.from.ref[0].id + '_drafts'
64
62
 
65
- if (entityName) {
66
- const entityDefinition = findCsnTargetFor(entityName, srv.model, namespace)
67
- if (entityDefinition._hasPersistenceSkip) return {}
63
+ // new parser has media property as last ref element -> remove
64
+ if (targetFromPath(select.SELECT.from.ref, model).kind === 'element') select.SELECT.from.ref.pop()
68
65
 
69
- const mediaProperty = Object.values(entityDefinition.elements).find(ele => ele['@Core.MediaType'])
70
- contentType = mediaProperty['@Core.MediaType']
71
- // @Core.ContentDisposition.Filename is correct, but we released with @Core.ContentDisposition, so we should stay compatible
72
- contentDisposition = mediaProperty['@Core.ContentDisposition.Filename'] || mediaProperty['@Core.ContentDisposition']
73
- // contentDisposition can be only dynamic
74
- if (typeof contentType === 'object' || contentDisposition) {
75
- const properties = _getDynamicProperties(contentType, contentDisposition)
76
- const result = await _getProperties(Object.values(properties), req)
77
- if (properties.contentDisposition && result[properties.contentDisposition] !== undefined) {
78
- contentDisposition = result[properties.contentDisposition]
79
- }
80
- // REVISIT: renaming of media type property (e.g., mimeType as MimeType in AFC) not reflected in @Core.MediaType
81
- if (properties.contentType) {
82
- if (result[properties.contentType] !== undefined) contentType = result[properties.contentType]
83
- else {
84
- let ct
85
- for (const k in entityDefinition.elements) {
86
- if (entityDefinition.elements[k]['@Core.IsMediaType']) {
87
- ct = k
88
- break
89
- }
90
- }
91
- contentType = result[ct]
92
- }
93
- }
94
- }
66
+ return cds
67
+ .tx(req)
68
+ .run(select)
69
+ .then(res => ({
70
+ contentType: (res && res.contentType) || contentType,
71
+ contentDisposition: (res && res.contentDisposition) || contentDisposition
72
+ }))
95
73
  }
96
74
 
97
- return { contentType, contentDisposition }
75
+ return Promise.resolve({ contentType, contentDisposition })
98
76
  }
99
77
 
100
78
  module.exports = {
@@ -58,13 +58,6 @@ class RestRequest extends cds.Request {
58
58
  return cds.tx(this).run(...args)
59
59
  }
60
60
  })
61
-
62
- if (this._.req.performanceMeasurement) {
63
- this.performanceMeasurement = this._.req.performanceMeasurement
64
- }
65
- if (this._.req.dynatrace) {
66
- this.dynatrace = this._.req.dynatrace
67
- }
68
61
  }
69
62
  }
70
63
 
@@ -50,7 +50,7 @@ module.exports = service => {
50
50
  const tx = service.tx({ user: restReq.user, req: restReq, _model: service.model })
51
51
  cds.context = tx
52
52
 
53
- let result, err, commit, location
53
+ let result, err, location
54
54
  try {
55
55
  const reqs = data.map(d => new RestRequest(parsed, d, restReq, restRes, service))
56
56
  result = await Promise.all(reqs.map(req => tx.dispatch(req)))
@@ -64,14 +64,11 @@ module.exports = service => {
64
64
  location = _locationHeader(target, service.name, result)
65
65
  }
66
66
 
67
- commit = true
68
67
  await tx.commit(result)
69
68
  } catch (e) {
70
69
  err = e
71
- if (!commit) {
72
- // ignore rollback error, which should never happen
73
- await tx.rollback(e).catch(() => {})
74
- }
70
+ // REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
71
+ await tx.rollback(e).catch(() => {})
75
72
  } finally {
76
73
  if (err) next(err)
77
74
  else {
@@ -18,18 +18,15 @@ module.exports = service => {
18
18
 
19
19
  const req = new RestRequest(parsed, data, restReq, restRes, service)
20
20
 
21
- let err, commit
21
+ let err
22
22
  try {
23
23
  await tx.dispatch(req)
24
24
 
25
- commit = true
26
25
  await tx.commit(null)
27
26
  } catch (e) {
28
27
  err = e
29
- if (!commit) {
30
- // ignore rollback error, which should never happen
31
- await tx.rollback(e).catch(() => {})
32
- }
28
+ // REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
29
+ await tx.rollback(e).catch(() => {})
33
30
  } finally {
34
31
  if (err) next(err)
35
32
  else {
@@ -32,7 +32,7 @@ module.exports = service => {
32
32
 
33
33
  const req = new RestRequest(parsed, data, restReq, restRes, service)
34
34
 
35
- let result, err, commit, status, body
35
+ let result, err, status, body
36
36
  try {
37
37
  result = await tx.dispatch(req)
38
38
 
@@ -44,14 +44,11 @@ module.exports = service => {
44
44
  body = _convertCustomOperationReturnValue(operation.returns, result)
45
45
  }
46
46
 
47
- commit = true
48
47
  await tx.commit(result)
49
48
  } catch (e) {
50
49
  err = e
51
- if (!commit) {
52
- // ignore rollback error, which should never happen
53
- await tx.rollback(e).catch(() => {})
54
- }
50
+ // REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
51
+ await tx.rollback(e).catch(() => {})
55
52
  } finally {
56
53
  if (err) next(err)
57
54
  else {
@@ -22,7 +22,7 @@ module.exports = service => {
22
22
  const tx = service.tx({ user: restReq.user, req: restReq, _model: service.model })
23
23
  cds.context = tx
24
24
 
25
- let result, err, commit
25
+ let result, err
26
26
  try {
27
27
  const req = new RestRequest(parsed, data, restReq, restRes, service)
28
28
 
@@ -34,14 +34,11 @@ module.exports = service => {
34
34
 
35
35
  bufferToBase64(result, segments[0])
36
36
 
37
- commit = true
38
37
  await tx.commit(result)
39
38
  } catch (e) {
40
39
  err = e
41
- if (!commit) {
42
- // ignore rollback error, which should never happen
43
- await tx.rollback(e).catch(() => {})
44
- }
40
+ // REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
41
+ await tx.rollback(e).catch(() => {})
45
42
  } finally {
46
43
  if (err) next(err)
47
44
  else restRes.send(toRestResult(result))
@@ -52,7 +52,7 @@ const update = service => {
52
52
  const tx = service.tx({ user: restReq.user, req: restReq, _model: service.model })
53
53
  cds.context = tx
54
54
 
55
- let result, err, commit, status
55
+ let result, err, status
56
56
  try {
57
57
  // try UPDATE and, on 404 error, try CREATE
58
58
  result = await _updateThenCreate(parsed, restReq, restRes, tx)
@@ -62,14 +62,11 @@ const update = service => {
62
62
 
63
63
  bufferToBase64(result, target)
64
64
 
65
- commit = true
66
65
  await tx.commit(result)
67
66
  } catch (e) {
68
67
  err = e
69
- if (!commit) {
70
- // ignore rollback error, which should never happen
71
- await tx.rollback(e).catch(() => {})
72
- }
68
+ // REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
69
+ await tx.rollback(e).catch(() => {})
73
70
  } finally {
74
71
  if (err) next(err)
75
72
  else {
@@ -4,7 +4,7 @@ const { INSERT, SELECT, UPDATE, DELETE } = cds.ql
4
4
 
5
5
  const { createCqlString } = require('./utils')
6
6
  const { getColumns } = require('../../../services/utils/columns')
7
- const { getMaxPageSize } = require('../../../../common/utils/page')
7
+ const { getMaxPageSize, getDefaultPageSize } = require('../../../../common/utils/page')
8
8
 
9
9
  const _readToCQN = ({ isCollection, segments }, target, restReq) => {
10
10
  const key = Object.keys(target.keys)[0]
@@ -20,7 +20,7 @@ const _readToCQN = ({ isCollection, segments }, target, restReq) => {
20
20
  if (!isCollection) cqn.SELECT.one = true
21
21
 
22
22
  if (isCollection && (restReq.query.$top || restReq.query.$skip)) {
23
- const top = restReq.query.$top ? parseInt(restReq.query.$top) : Number.MAX_SAFE_INTEGER
23
+ const top = restReq.query.$top ? parseInt(restReq.query.$top) : getDefaultPageSize(target)
24
24
  cqn.limit(Math.min(top, getMaxPageSize(target)), parseInt(restReq.query.$skip) || 0)
25
25
  }
26
26
 
@@ -48,7 +48,7 @@ const _parseEntityOrOperation = part => {
48
48
 
49
49
  const _findEntityOrCustomOperation = (customOperation, service, name) => {
50
50
  const thing =
51
- service.entities[name] || service.operations[name] || findCsnTargetFor(name, service.model, service.name)
51
+ service.entities[name] || service.operations[name] || findCsnTargetFor(name, service.model, service.namespace)
52
52
  if (!thing) {
53
53
  throw getError(404, 'INVALID_RESOURCE', [name])
54
54
  }
@@ -152,13 +152,15 @@ const _parseCreateOrRead2 = (event, parsed, service, parts) => {
152
152
  }
153
153
 
154
154
  parsed.segments.push(
155
- _validateEntity(service.entities[parts[0]] || findCsnTargetFor(parts[0], service.model, service.name)),
155
+ _validateEntity(service.entities[parts[0]] || findCsnTargetFor(parts[0], service.model, service.namespace)),
156
156
  parts[1]
157
157
  )
158
158
  }
159
159
 
160
160
  const _parseCreateOrRead3 = (service, parts, customOperation, parsed) => {
161
- const entity = _validateEntity(service.entities[parts[0]] || findCsnTargetFor(parts[0], service.model, service.name))
161
+ const entity = _validateEntity(
162
+ service.entities[parts[0]] || findCsnTargetFor(parts[0], service.model, service.namespace)
163
+ )
162
164
  const key = parts[1]
163
165
  const { name, params } = _parseEntityOrOperation(parts[2])
164
166
  const operation = _validateCustomOperation(entity, name, customOperation)
@@ -209,7 +211,9 @@ const parseUpdateOrDeleteUrl = (event, service, req) => {
209
211
  throw getError(400, 'INVALID_PUT')
210
212
  }
211
213
 
212
- const entity = _validateEntity(service.entities[parts[0]] || findCsnTargetFor(parts[0], service.model, service.name))
214
+ const entity = _validateEntity(
215
+ service.entities[parts[0]] || findCsnTargetFor(parts[0], service.model, service.namespace)
216
+ )
213
217
  const segments = [entity]
214
218
 
215
219
  if (parts[1]) {
@@ -22,12 +22,6 @@ class ApplicationService extends cds.Service {
22
22
  this._calculateDiff = this._differ.calculate
23
23
  }
24
24
 
25
- set model(csn) {
26
- const m = csn && 'definitions' in csn ? cds.linked(cds.compile.for.odata(csn)) : csn
27
- cds.alpha_localized(m) // with compiler v2 we always need to localized the csn
28
- super.model = m
29
- }
30
-
31
25
  init() {
32
26
  /*
33
27
  * .before handlers (all with _initial === true)
@@ -116,11 +116,15 @@ const getSearchableColumns = entity => {
116
116
  /**
117
117
  * @returns {import('../../../types/api').ColumnRefs}
118
118
  */
119
- const computeColumnsToBeSearched = (cqn, entity = { _searchableColumns: [] }) => {
119
+ const computeColumnsToBeSearched = (cqn, entity = { _searchableColumns: [] }, alias) => {
120
120
  // if there is a group by clause, only columns in it may be searched
121
121
  let toBeSearched = [...entity._searchableColumns]
122
122
  if (cqn.SELECT.groupBy) toBeSearched = toBeSearched.filter(tbs => cqn.SELECT.groupBy.some(gb => gb.ref[0] === tbs))
123
- toBeSearched = toBeSearched.map(c => ({ ref: [c] }))
123
+ toBeSearched = toBeSearched.map(c => {
124
+ const col = { ref: [c] }
125
+ if (alias) col.ref.unshift(alias)
126
+ return col
127
+ })
124
128
 
125
129
  // add aggregations
126
130
  cqn.SELECT.columns &&
@@ -141,7 +145,10 @@ const computeColumnsToBeSearched = (cqn, entity = { _searchableColumns: [] }) =>
141
145
  if (columnRef) {
142
146
  const columnName = columnRef[columnRef.length - 1]
143
147
  const csnColumn = entity.elements[columnName]
144
- if (!csnColumn) toBeSearched.push({ ref: [columnName] })
148
+ if (csnColumn) return
149
+ const col = { ref: [columnName] }
150
+ if (alias) col.ref.unshift(alias)
151
+ toBeSearched.push(col)
145
152
  }
146
153
  })
147
154
 
@@ -131,14 +131,11 @@ const _addToBeDeletedEntriesToResult = (results, entity, keys, newValues, oldVal
131
131
  }
132
132
  }
133
133
 
134
- const _normalizeToArray = value => (Array.isArray(value) ? value : [value])
134
+ const _normalizeToArray = value => (Array.isArray(value) ? value : value === null ? [] : [value])
135
135
 
136
136
  const _addKeysToEntryIfNotExists = (keys, newEntry) => {
137
- for (const key of keys) {
138
- if (!(key in newEntry)) {
139
- newEntry[key] = undefined
140
- }
141
- }
137
+ if (!newEntry) return
138
+ for (const key of keys) if (!(key in newEntry)) newEntry[key] = undefined
142
139
  }
143
140
 
144
141
  const _isUnManaged = element => {
@@ -268,7 +265,7 @@ const compareJson = (newValue, oldValue, entity) => {
268
265
  return Array.isArray(newValue) ? result : result[0]
269
266
  }
270
267
 
271
- const _isObject = item => item && typeof item === 'object' && !Array.isArray(item)
268
+ const _isObject = item => item && typeof item === 'object' && !Array.isArray(item) && !Buffer.isBuffer(item)
272
269
 
273
270
  const _mergeArrays = (entity, oldValue, newValue) => {
274
271
  const merged = []
@@ -58,10 +58,13 @@ module.exports = class {
58
58
 
59
59
  async _diffUpdate(req, providedData) {
60
60
  if (cds.db) {
61
+ // REVISIT: remove try catch with cds^6
61
62
  try {
62
63
  await this._addPartialPersistentState(req)
63
64
  } catch (e) {
64
- LOG._warn && LOG.warn('Unable to calculate diff due to error: ' + e.message, e)
65
+ // NOTE: unofficial compat flag!
66
+ if (cds.env.features.throw_diff_error !== false) throw e
67
+ else LOG._error && LOG.error('Unable to calculate diff due to error: ' + e.message, e)
65
68
  }
66
69
  }
67
70
  const newQuery = cqn2cqn4sql(req.query, this._srv.model)
@@ -1,9 +1,7 @@
1
1
  const cds = require('../../../cds')
2
-
3
2
  const { SELECT } = cds.ql
4
3
 
5
- const { checkReferenceIntegrity } = require('../../util/assert')
6
- const { processDeep, processDeepAsync } = require('../../util/dataProcessUtils')
4
+ const { processDeep } = require('../../util/dataProcessUtils')
7
5
 
8
6
  const { DRAFT_COLUMNS } = require('../../../common/constants/draft')
9
7
 
@@ -145,43 +143,6 @@ const flattenDeepToOneAssociations = (req, csn) => {
145
143
  )
146
144
  }
147
145
 
148
- const checkIntegrityWrapper = (req, csn, run) => async (data, entity) => {
149
- const errors = await checkReferenceIntegrity(entity, data, req, csn, run)
150
- if (errors && errors.length !== 0) {
151
- for (const err of errors) {
152
- req.error(err)
153
- }
154
- }
155
- }
156
-
157
- const _isUncheckableInsert = query => {
158
- return query.INSERT && (query.INSERT.rows || query.INSERT.values || query.INSERT.as)
159
- }
160
-
161
- // REVISIT: lower to db layer, where it's used
162
- const checkIntegrityUtil = async (req, csn, run) => {
163
- if (!run) return
164
-
165
- // REVISIT
166
- if (typeof req.query === 'string' || req.target._unresolved) return
167
-
168
- // FIXME: doesn't work for uncheckable inserts
169
- if (_isUncheckableInsert(req.query)) return
170
-
171
- // REVISIT: integrity check needs context.data
172
- if (Object.keys(req.data).length === 0) {
173
- // REVISIT: We may need to double-check re req.data being undefined or empty
174
- if (req.query.DELETE) {
175
- req.data = req._beforeDeleteData || {}
176
- } else if (req.context && req.context.data && Object.keys(req.context.data).length > 0) {
177
- req.data = req.context.data
178
- }
179
- }
180
- if (Object.keys(req.data).length === 0) return
181
-
182
- await processDeepAsync(checkIntegrityWrapper(req, csn, run), req.data, req.target, false, true)
183
- }
184
-
185
146
  /*
186
147
  * merge CQNs
187
148
  */
@@ -235,6 +196,5 @@ const getDeepSelect = req => {
235
196
  module.exports = {
236
197
  getDeepSelect,
237
198
  allKeysAreProvided,
238
- checkIntegrityUtil,
239
199
  flattenDeepToOneAssociations
240
200
  }