@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
@@ -218,14 +218,6 @@ class ODataRequest extends cds.Request {
218
218
  }
219
219
  })
220
220
 
221
- if (this._.req.performanceMeasurement) {
222
- this.performanceMeasurement = this._.req.performanceMeasurement
223
- }
224
-
225
- if (this._.req.dynatrace) {
226
- this.dynatrace = this._.req.dynatrace
227
- }
228
-
229
221
  /*
230
222
  * req.isConcurrentResource
231
223
  */
@@ -89,7 +89,7 @@ const action = service => {
89
89
  const tx = changeset ? odataReq.getBatchApplicationData().txs[changeset] : service.tx(req)
90
90
  cds.context = tx
91
91
 
92
- let result, err, commit
92
+ let result, err
93
93
  try {
94
94
  result = await tx.dispatch(req)
95
95
 
@@ -107,13 +107,12 @@ const action = service => {
107
107
  // for passing into commit
108
108
  odataReq.getBatchApplicationData().results[changeset].push({ result, req })
109
109
  } else {
110
- commit = true
111
110
  await tx.commit(result)
112
111
  }
113
112
  } catch (e) {
114
113
  err = e
115
- if (!changeset && !commit) {
116
- // ignore rollback error, which should never happen
114
+ if (!changeset) {
115
+ // REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
117
116
  await tx.rollback(e).catch(() => {})
118
117
  } else if (changeset) {
119
118
  // for passing into rollback
@@ -32,11 +32,13 @@ const create = service => {
32
32
  const tx = changeset ? odataReq.getBatchApplicationData().txs[changeset] : service.tx(req)
33
33
  cds.context = tx
34
34
 
35
- let result, err, commit
35
+ let result, err
36
36
  try {
37
37
  result = await tx.dispatch(req)
38
38
 
39
- if (!isReturnMinimal(req)) {
39
+ if (isReturnMinimal(req)) {
40
+ postProcessMinimal(req, result)
41
+ } else {
40
42
  // REVISIT: find better solution
41
43
  if (req._.readAfterWrite) {
42
44
  const dataInDb = await readAfterWrite(req, service)
@@ -44,15 +46,12 @@ const create = service => {
44
46
  }
45
47
 
46
48
  postProcess(req, odataRes, service, result)
47
- } else {
48
- postProcessMinimal(req, result)
49
49
  }
50
50
 
51
51
  if (changeset) {
52
52
  // for passing into commit
53
53
  odataReq.getBatchApplicationData().results[changeset].push({ result, req })
54
54
  } else {
55
- commit = true
56
55
  await tx.commit(result)
57
56
  }
58
57
 
@@ -61,8 +60,8 @@ const create = service => {
61
60
  }
62
61
  } catch (e) {
63
62
  err = e
64
- if (!changeset && !commit) {
65
- // ignore rollback error, which should never happen
63
+ if (!changeset) {
64
+ // REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
66
65
  await tx.rollback(e).catch(() => {})
67
66
  } else if (changeset) {
68
67
  // for passing into rollback
@@ -28,7 +28,7 @@ const del = service => {
28
28
  const tx = changeset ? odataReq.getBatchApplicationData().txs[changeset] : service.tx(req)
29
29
  cds.context = tx
30
30
 
31
- let err, commit
31
+ let err
32
32
  try {
33
33
  await tx.dispatch(req)
34
34
  const result = null
@@ -37,13 +37,12 @@ const del = service => {
37
37
  // for passing into commit
38
38
  odataReq.getBatchApplicationData().results[changeset].push({ result, req })
39
39
  } else {
40
- commit = true
41
40
  await tx.commit(result)
42
41
  }
43
42
  } catch (e) {
44
43
  err = e
45
- if (!changeset && !commit) {
46
- // ignore rollback error, which should never happen
44
+ if (!changeset) {
45
+ // REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
47
46
  await tx.rollback(e).catch(() => {})
48
47
  } else if (changeset) {
49
48
  // for passing into rollback
@@ -1,4 +1,5 @@
1
1
  const cds = require('../../../../cds')
2
+ const { isStandardError } = require('../../../../common/error/standardError')
2
3
 
3
4
  const { StatusCodes: HttpStatusCodes } = require('../okra/odata-commons/http/HttpStatusCode')
4
5
 
@@ -24,16 +25,6 @@ const ERROR_TO_HTTP_CODE = {
24
25
 
25
26
  const { normalizeError } = require('../../../../common/error/frontend')
26
27
 
27
- const _isStandardError = err => {
28
- return (
29
- err instanceof TypeError ||
30
- err instanceof ReferenceError ||
31
- err instanceof SyntaxError ||
32
- err instanceof RangeError ||
33
- err instanceof URIError
34
- )
35
- }
36
-
37
28
  const _beautifyMessage = msg => (msg.endsWith('.') ? msg : msg + '.')
38
29
 
39
30
  const _buildRootCauseMessage = (message, rootCause) => {
@@ -79,7 +70,7 @@ const _betterOkraError = err => {
79
70
  const getErrorHandler = (crashOnError = true, srv) => {
80
71
  return (odataReq, odataRes, next, err) => {
81
72
  // REVISIT: crashOnError
82
- if (_isStandardError(err) && crashOnError) {
73
+ if (isStandardError(err) && crashOnError) {
83
74
  err.__crashOnError = true
84
75
  throw err
85
76
  }
@@ -1,7 +1,10 @@
1
1
  const cds = require('../../../../cds')
2
+ const LOG = cds.log('odata')
2
3
 
3
4
  const { toODataResult } = require('../utils/result')
4
5
 
6
+ const { normalizeError } = require('../../../../common/error/frontend')
7
+
5
8
  let _mps
6
9
 
7
10
  const _get4Tenant = async (tenant, locale, service) => {
@@ -33,13 +36,14 @@ const _get4Toggles = async (tenant, locale, service, req) => {
33
36
  */
34
37
  const metadata = service => {
35
38
  return async (odataReq, odataRes, next) => {
36
- try {
37
- const req = odataReq.getIncomingRequest()
39
+ const req = odataReq.getIncomingRequest()
40
+
41
+ const tenant = req.user && req.user.tenant
38
42
 
39
- const tenant = req.tenant
40
- // REVISIT: can we take locale from user, or is there some odata special wrt metadata?
41
- const locale = odataRes.getContract().getLocale()
43
+ // REVISIT: can we take locale from user, or is there some odata special wrt metadata?
44
+ const locale = odataRes.getContract().getLocale()
42
45
 
46
+ try {
43
47
  let edmx
44
48
 
45
49
  if (tenant) {
@@ -60,7 +64,13 @@ const metadata = service => {
60
64
 
61
65
  return next(null, toODataResult(edmx))
62
66
  } catch (e) {
63
- return next(e)
67
+ if (LOG._error) {
68
+ e.message = 'Unable to get EDMX for tenant ' + tenant + ' due to error: ' + e.message
69
+ LOG.error(e)
70
+ }
71
+ // return 503 to client
72
+ const { error, statusCode } = normalizeError(Object.assign(e, { statusCode: 503 }), req)
73
+ return next(Object.assign(error, { statusCode }))
64
74
  }
65
75
  }
66
76
  }
@@ -1,7 +1,8 @@
1
1
  const cds = require('../../../../cds')
2
+
2
3
  const { SELECT } = cds.ql
4
+
3
5
  const ODataRequest = require('../ODataRequest')
4
- const { rewriteExpandAsterisk } = require('../../../../common/utils/rewriteAsterisks')
5
6
 
6
7
  const {
7
8
  QueryOptions,
@@ -13,15 +14,14 @@ const {
13
14
  }
14
15
  } = require('../okra/odata-server')
15
16
 
16
- const getError = require('../../../../common/error')
17
- const { getSapMessages } = require('../../../../common/error/frontend')
18
17
  const { isCustomOperation, skipToken } = require('../utils/request')
19
18
  const { actionAndFunctionQueries, getActionOrFunctionReturnType } = require('../utils/handlerUtils')
20
19
  const { validateResourcePath } = require('../utils/request')
21
20
  const { toODataResult, postProcess } = require('../utils/result')
22
21
  const { isStreaming, getStreamProperties } = require('../utils/stream')
23
22
  const { resolveStructuredName } = require('../utils/handlerUtils')
24
- const { ensureNoDraftsSuffix } = require('../../../../common/utils/draft')
23
+ const getError = require('../../../../common/error')
24
+ const { getSapMessages } = require('../../../../common/error/frontend')
25
25
 
26
26
  /**
27
27
  * Checks whether a bound function or function import is invoked.
@@ -32,6 +32,10 @@ const { ensureNoDraftsSuffix } = require('../../../../common/utils/draft')
32
32
  */
33
33
  const _isFunction = segments => [BOUND_FUNCTION, FUNCTION_IMPORT].includes(segments[segments.length - 1].getKind())
34
34
 
35
+ const _selectOrExpandInQueryOptions = queryOptions => {
36
+ return queryOptions && (queryOptions.$select || queryOptions.$expand)
37
+ }
38
+
35
39
  /**
36
40
  * Invoke a function.
37
41
  *
@@ -49,7 +53,12 @@ const _invokeFunction = async (tx, req, odataReq) => {
49
53
  tx.model.definitions
50
54
  )
51
55
 
52
- if (functionReturnType && functionReturnType.kind === 'entity' && odataReq.getQueryOptions()) {
56
+ // if $select or $expand is present, do it
57
+ if (
58
+ functionReturnType &&
59
+ functionReturnType.kind === 'entity' &&
60
+ _selectOrExpandInQueryOptions(odataReq.getQueryOptions())
61
+ ) {
53
62
  await actionAndFunctionQueries(req, odataReq, result, tx, functionReturnType)
54
63
  }
55
64
 
@@ -283,13 +292,14 @@ const _readCollection = async (tx, req, odataReq) => {
283
292
  *
284
293
  * @param {object} tx
285
294
  * @param {object} req
286
- * @param {Array} segments
287
295
  * @returns {Promise}
288
296
  * @private
289
297
  */
290
- const _readStream = async (tx, req, segments) => {
298
+ const _readStream = async (tx, req) => {
291
299
  req.query._streaming = true
292
300
 
301
+ const { contentType, contentDisposition } = await getStreamProperties(req, tx.model)
302
+
293
303
  let result = await tx.dispatch(req)
294
304
 
295
305
  // REVISIT: compat, should actually be treated as object
@@ -313,13 +323,12 @@ const _readStream = async (tx, req, segments) => {
313
323
  })
314
324
  }
315
325
 
316
- const { contentType, contentDisposition } = await getStreamProperties(segments, tx, req)
317
-
318
326
  const headers = req._.odataReq.getHeaders()
327
+
319
328
  if (
329
+ contentType &&
320
330
  headers &&
321
331
  headers.accept &&
322
- contentType &&
323
332
  !headers.accept.includes('*/*') &&
324
333
  !headers.accept.includes(contentType) &&
325
334
  !headers.accept.includes(contentType.split('/')[0] + '/*')
@@ -328,9 +337,9 @@ const _readStream = async (tx, req, segments) => {
328
337
  }
329
338
 
330
339
  if (contentType) streamObj['*@odata.mediaContentType'] = contentType
331
- if (contentDisposition) {
340
+ if (contentDisposition)
332
341
  req._.odataRes.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(contentDisposition)}"`)
333
- }
342
+
334
343
  return streamObj
335
344
  }
336
345
 
@@ -384,11 +393,11 @@ const _readAndTransform = (tx, req, odataReq) => {
384
393
  }
385
394
  }
386
395
 
387
- return _readStream(tx, req, segments)
396
+ return _readStream(tx, req)
388
397
  }
389
398
 
390
399
  if (isStreaming(segments)) {
391
- return _readStream(tx, req, segments)
400
+ return _readStream(tx, req)
392
401
  }
393
402
 
394
403
  if (req.target._isSingleton) {
@@ -418,43 +427,6 @@ const _removeKeysForParams = result => {
418
427
  return options
419
428
  }
420
429
 
421
- const _getTarget = (ref, target, definitions) => {
422
- if (cds.env.effective.odata.proxies) {
423
- const target_ = target.elements[ref[0]]
424
-
425
- if (ref.length === 1) {
426
- return definitions[ensureNoDraftsSuffix(target_.target)]
427
- }
428
-
429
- return _getTarget(ref.slice(1), target_, definitions)
430
- }
431
-
432
- const target_ = target.elements[ref.join('_')]
433
- return definitions[ensureNoDraftsSuffix(target_.target)]
434
- }
435
-
436
- const _getRestrictedExpand = (columns, target, definitions) => {
437
- if (!columns || !target || columns === '*') return
438
-
439
- const annotation = target['@Capabilities.ExpandRestrictions.NonExpandableProperties']
440
- const restrictions = annotation && annotation.map(element => element['='])
441
-
442
- rewriteExpandAsterisk(columns, target)
443
-
444
- for (const col of columns) {
445
- if (col.expand) {
446
- if (restrictions && restrictions.length !== 0) {
447
- const ref = col.ref.join('_')
448
- const ref_ = restrictions.find(element => element.replace(/\./g, '_') === ref)
449
- if (ref_) return ref_
450
- }
451
-
452
- const restricted = _getRestrictedExpand(col.expand, _getTarget(col.ref, target, definitions), definitions)
453
- if (restricted) return restricted
454
- }
455
- }
456
- }
457
-
458
430
  /**
459
431
  * The handler that will be registered with odata-v4.
460
432
  *
@@ -478,21 +450,11 @@ const read = service => {
478
450
  return next(e)
479
451
  }
480
452
 
481
- // REVISIT: this should be in common/generic/auth.js with the rest of the access control stuff
482
- const restricted = _getRestrictedExpand(
483
- req.query.SELECT && req.query.SELECT.columns,
484
- req.target,
485
- service.model.definitions
486
- )
487
- if (restricted) {
488
- return next(getError(400, 'EXPAND_IS_RESTRICTED', [restricted]))
489
- }
490
-
491
453
  const changeset = odataReq.getAtomicityGroupId()
492
454
  const tx = changeset ? odataReq.getBatchApplicationData().txs[changeset] : service.tx(req)
493
455
  cds.context = tx
494
456
 
495
- let result, err, commit
457
+ let result, err
496
458
  let additional = {}
497
459
  try {
498
460
  // REVISIT: refactor _readAndTransform
@@ -509,13 +471,12 @@ const read = service => {
509
471
  // for passing into commit
510
472
  odataReq.getBatchApplicationData().results[changeset].push({ result, req })
511
473
  } else {
512
- commit = true
513
474
  await tx.commit(result)
514
475
  }
515
476
  } catch (e) {
516
477
  err = e
517
- if (!changeset && !commit) {
518
- // ignore rollback error, which should never happen
478
+ if (!changeset) {
479
+ // REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
519
480
  await tx.rollback(e).catch(() => {})
520
481
  } else if (changeset) {
521
482
  // for passing into rollback
@@ -2,8 +2,6 @@ const cds = require('../../../../cds')
2
2
 
3
3
  const { UNAUTHORIZED, FORBIDDEN, getRequiresAsArray } = require('../../../../common/utils/auth')
4
4
 
5
- const measurePerformance = require('../../perf/performance')
6
-
7
5
  module.exports = srv => {
8
6
  const requires = getRequiresAsArray(srv.definition)
9
7
 
@@ -55,11 +53,6 @@ module.exports = srv => {
55
53
  odataReq.setApplicationData({ req })
56
54
  }
57
55
 
58
- // in case of batch request with sap-statistics=true also measure performance of batched requests
59
- if (odataReq.getBatchApplicationData()) {
60
- measurePerformance(req, res)
61
- }
62
-
63
56
  next()
64
57
  }
65
58
  }
@@ -14,13 +14,6 @@ const { toODataResult, postProcess, postProcessMinimal } = require('../utils/res
14
14
  const { hasOmitValuesPreference } = require('../utils/omitValues')
15
15
  const { mergeJson } = require('../../../services/utils/compareJson')
16
16
 
17
- /*
18
- const { isStreaming } = require('../utils/stream')
19
- const { findCsnTargetFor } = require('../../../../common/utils/csn')
20
- const { isActiveEntityRequested, removeIsActiveEntityRecursively } = require('../../../../fiori/utils/where')
21
- const { ensureDraftsSuffix } = require('../../../../fiori/utils/handler')
22
- */
23
-
24
17
  const _isUpsertAllowed = target => {
25
18
  return !(cds.env.runtime && cds.env.runtime.allow_upsert === false) && !(target && target._isDraftEnabled)
26
19
  }
@@ -121,58 +114,6 @@ const _readAfterWriteAndVirtuals = async (req, service, result) => {
121
114
  const _shouldReadPreviousResult = req =>
122
115
  req.event === 'UPDATE' && !isReturnMinimal(req) && hasOmitValuesPreference(req.headers.prefer, 'defaults')
123
116
 
124
- /*
125
- const _getEntity = (segments, model) => {
126
- let entityName, namespace
127
- const previous = segments[segments.length - 2]
128
- if (previous.getKind() === 'ENTITY') {
129
- entityName = previous.getEntitySet().getName()
130
- namespace = previous.getEdmType().getFullQualifiedName().namespace
131
- } else if (previous.getKind() === 'NAVIGATION.TO.ONE') {
132
- entityName = previous.getTarget().getName()
133
- namespace = previous.getTarget().getEntityType().getFullQualifiedName().namespace
134
- }
135
-
136
- if (entityName) {
137
- return findCsnTargetFor(entityName, model, namespace)
138
- }
139
- }
140
-
141
- const _getMediaType = entity => {
142
- if (entity._hasPersistenceSkip) return
143
-
144
- return Object.values(entity.elements).find(ele => ele['@Core.IsMediaType'])
145
- }
146
-
147
- const _getMediaTypeCQN = (mediaType, contentType, entity, req) => {
148
- const where = req.query.UPDATE.entity.ref[0].where
149
- const isActive = isActiveEntityRequested(where)
150
- const data = {}
151
- data[mediaType.name] = contentType
152
- const cqn = UPDATE(entity).set(data)
153
- cqn.UPDATE.where = removeIsActiveEntityRecursively(where)
154
- if (!isActive) {
155
- cqn.UPDATE.entity = ensureDraftsSuffix(entity.name)
156
- }
157
-
158
- return cqn
159
- }
160
-
161
- const _handleMediaType = async (odataReq, model, tx, req) => {
162
- const segments = odataReq.getUriInfo().getPathSegments()
163
- const contentType = odataReq._inRequest.headers['content-type']
164
- if (isStreaming(segments) && contentType) {
165
- const entity = _getEntity(segments, model)
166
- if (entity && !entity['@cds.persistence.skip']) {
167
- const mediaType = _getMediaType(entity)
168
- if (mediaType) {
169
- await tx.run(_getMediaTypeCQN(mediaType, contentType, entity, req))
170
- }
171
- }
172
- }
173
- }
174
- */
175
-
176
117
  /**
177
118
  * The handler that will be registered with odata-v4.
178
119
  *
@@ -199,11 +140,8 @@ const update = service => {
199
140
  // putting a property?
200
141
  const primitive = odataReq.getUriInfo().getLastSegment().getKind() === 'PRIMITIVE.PROPERTY'
201
142
 
202
- let result, err, commit
143
+ let result, err
203
144
  try {
204
- // // REVISIT: should be handled somewhere else
205
- // await _handleMediaType(odataReq, service.model, tx, req)
206
-
207
145
  let previousResult
208
146
  if (_shouldReadPreviousResult(req)) {
209
147
  previousResult = await _readAfterWriteAndVirtuals(req, service, result)
@@ -227,7 +165,6 @@ const update = service => {
227
165
  // for passing into commit
228
166
  odataReq.getBatchApplicationData().results[changeset].push({ result, req })
229
167
  } else {
230
- commit = true
231
168
  await tx.commit(result)
232
169
  }
233
170
 
@@ -236,8 +173,8 @@ const update = service => {
236
173
  }
237
174
  } catch (e) {
238
175
  err = e
239
- if (!changeset && !commit) {
240
- // ignore rollback error, which should never happen
176
+ if (!changeset) {
177
+ // REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
241
178
  await tx.rollback(e).catch(() => {})
242
179
  } else if (changeset) {
243
180
  // for passing into rollback
@@ -59,6 +59,8 @@ class ExpressionToCQN {
59
59
  }
60
60
 
61
61
  _lambda(pathSegments) {
62
+ // we don't care about the variable name
63
+ if (pathSegments[0].getKind() === 'EXPRESSION.VARIABLE') pathSegments = pathSegments.slice(1)
62
64
  const nav =
63
65
  pathSegments.length > 2 ? pathSegments.slice(0, pathSegments.length - 2).map(this._segmentFromMember) : []
64
66
  const navName = this._segmentFromMember(pathSegments[pathSegments.length - 2])
@@ -82,6 +84,8 @@ class ExpressionToCQN {
82
84
  case ResourceKind.ALL_EXPRESSION:
83
85
  case ResourceKind.ANY_EXPRESSION:
84
86
  return segment.getExpression() ? this.parse(segment.getExpression()) : undefined
87
+ case ResourceKind.COUNT:
88
+ return '$count'
85
89
  default:
86
90
  throw getFeatureNotSupportedError(`Segment kind "${segment.getKind()}" in $filter query option`)
87
91
  }
@@ -93,6 +97,20 @@ class ExpressionToCQN {
93
97
  if (!segment) return []
94
98
 
95
99
  if (segment.getKind() === ResourceKind.NAVIGATION_TO_ONE) {
100
+ const name = this._segmentFromMember(segment)
101
+ const where =
102
+ nextSegments &&
103
+ nextSegments[nextSegments.length - 1].getKind() === ResourceKind.COUNT &&
104
+ segment.getKeyPredicates().reduce((prev, curr) => {
105
+ if (prev.length > 0) prev.push('and')
106
+ prev.push({ ref: [curr.getEdmRef().getName()] }, '=', { val: curr.getText() })
107
+ return prev
108
+ }, [])
109
+
110
+ return [where ? { id: name, where } : name, ...this._getMemberRecursively(nextSegments)]
111
+ }
112
+
113
+ if (segment.getKind() === ResourceKind.NAVIGATION_TO_MANY) {
96
114
  return [this._segmentFromMember(segment), ...this._getMemberRecursively(nextSegments)]
97
115
  }
98
116
 
@@ -100,6 +118,10 @@ class ExpressionToCQN {
100
118
  return [...this._getMemberRecursively(nextSegments)]
101
119
  }
102
120
 
121
+ if (segment.getKind() === ResourceKind.COUNT) {
122
+ return [this._segmentFromMember(segment), ...this._getMemberRecursively(nextSegments)]
123
+ }
124
+
103
125
  if (segment.getKind() === ResourceKind.COMPLEX_PROPERTY) {
104
126
  if (nextSegments.length) {
105
127
  return [this._segmentFromMember(segment), ...this._getMemberRecursively(nextSegments)]
@@ -128,6 +150,10 @@ class ExpressionToCQN {
128
150
  return entry
129
151
  }
130
152
  }
153
+ if (members.length > 1 && members[members.length - 1] === '$count') {
154
+ return { func: 'count', args: [{ ref: members.slice(0, members.length - 1) }], as: '$count' }
155
+ }
156
+
131
157
  return { ref: members }
132
158
  }
133
159
 
@@ -14,7 +14,7 @@ const { _expand } = require('../utils/handlerUtils')
14
14
  const { resolveStructuredName } = require('../utils/handlerUtils')
15
15
  const { isStreaming } = require('../utils/stream')
16
16
  const { convertUrlPathToCqn, getAllKeys } = require('./utils')
17
- const { getMaxPageSize } = require('../../../../common/utils/page')
17
+ const { getMaxPageSize, getDefaultPageSize } = require('../../../../common/utils/page')
18
18
  const { isAsteriskColumn } = require('../../../../common/utils/rewriteAsterisks')
19
19
 
20
20
  const {
@@ -137,11 +137,11 @@ const _apply = (uriInfo, queryOptions, entity, model) => {
137
137
  return {}
138
138
  }
139
139
 
140
- const _topSkip = (queryOptions, maxPageSize, cqn) => {
140
+ const _topSkip = (queryOptions, target, cqn) => {
141
141
  if (queryOptions && (queryOptions.$top || queryOptions.$skip || queryOptions.$skiptoken)) {
142
- const top = queryOptions.$top ? parseInt(queryOptions.$top) : Number.MAX_SAFE_INTEGER
142
+ const top = queryOptions.$top ? parseInt(queryOptions.$top) : getDefaultPageSize(target)
143
143
  const skip = parseInt(queryOptions.$skip || 0) + parseInt(queryOptions.$skiptoken || 0)
144
- cqn.limit(Math.min(top, maxPageSize), skip)
144
+ cqn.limit(Math.min(top, getMaxPageSize(target)), skip)
145
145
  }
146
146
  }
147
147
 
@@ -400,7 +400,7 @@ const readToCQN = (service, target, odataReq) => {
400
400
  }
401
401
 
402
402
  if (isCollectionOrToMany) {
403
- _topSkip(queryOptions, getMaxPageSize(target), cqn)
403
+ _topSkip(queryOptions, target, cqn)
404
404
  _orderby(uriInfo, cqn)
405
405
  }
406
406
 
@@ -55,13 +55,13 @@ const convertUrlPathToCqn = (segments, service) => {
55
55
  const entity = segment.getEntitySet().getEntityType().getFullQualifiedName()
56
56
  const keys = convertKeyPredicatesToStringExpr(segment.getKeyPredicates())
57
57
 
58
- return `${findCsnTargetFor(entity.name, service.model, service.name).name}${keys}`
58
+ return `${findCsnTargetFor(entity.name, service.model, service.namespace).name}${keys}`
59
59
  }
60
60
 
61
61
  if (segment.getKind() === 'SINGLETON') {
62
62
  const singleton = segment.getSingleton().getEntityType().getFullQualifiedName()
63
63
 
64
- return `${findCsnTargetFor(singleton.name, service.model, service.name).name}`
64
+ return `${findCsnTargetFor(singleton.name, service.model, service.namespace).name}`
65
65
  }
66
66
 
67
67
  if (segment.getKind() === 'COMPLEX.PROPERTY') {