@sap/cds 5.6.3 → 5.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/CHANGELOG.md +135 -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 +3 -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 +8 -3
  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 -69
  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 +29 -1
  73. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +7 -1
  74. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +9 -5
  75. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +2 -2
  76. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +13 -10
  77. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -7
  78. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -8
  79. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +54 -76
  80. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
  81. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
  82. package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
  83. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
  84. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
  85. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
  86. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -2
  87. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
  88. package/libx/_runtime/cds-services/services/Service.js +0 -6
  89. package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
  90. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -7
  91. package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
  92. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
  93. package/libx/_runtime/cds-services/util/assert.js +1 -262
  94. package/libx/_runtime/cds.js +6 -9
  95. package/libx/_runtime/common/aspects/entity.js +1 -1
  96. package/libx/_runtime/common/composition/delete.js +4 -2
  97. package/libx/_runtime/common/composition/update.js +22 -38
  98. package/libx/_runtime/common/composition/utils.js +3 -7
  99. package/libx/_runtime/common/error/standardError.js +11 -0
  100. package/libx/_runtime/common/generic/auth.js +63 -33
  101. package/libx/_runtime/common/generic/crud.js +11 -23
  102. package/libx/_runtime/common/generic/input.js +20 -0
  103. package/libx/_runtime/common/generic/paging.js +2 -2
  104. package/libx/_runtime/common/generic/put.js +4 -10
  105. package/libx/_runtime/common/generic/sorting.js +12 -30
  106. package/libx/_runtime/common/i18n/messages.properties +2 -0
  107. package/libx/_runtime/common/perf/index.js +24 -0
  108. package/libx/_runtime/common/utils/cqn.js +58 -1
  109. package/libx/_runtime/common/utils/cqn2cqn4sql.js +298 -121
  110. package/libx/_runtime/common/utils/csn.js +38 -56
  111. package/libx/_runtime/common/utils/entityFromCqn.js +6 -6
  112. package/libx/_runtime/common/utils/resolveView.js +4 -5
  113. package/libx/_runtime/common/utils/rewriteAsterisks.js +46 -5
  114. package/libx/_runtime/common/utils/search2cqn4sql.js +21 -9
  115. package/libx/_runtime/common/utils/structured.js +35 -25
  116. package/libx/_runtime/db/Service.js +0 -6
  117. package/libx/_runtime/db/expand/expand-v2.js +130 -0
  118. package/libx/_runtime/db/expand/expandCQNToJoin.js +38 -52
  119. package/libx/_runtime/db/expand/index.js +3 -1
  120. package/libx/_runtime/db/generic/arrayed.js +3 -1
  121. package/libx/_runtime/db/generic/input.js +52 -10
  122. package/libx/_runtime/db/generic/integrity.js +367 -26
  123. package/libx/_runtime/db/generic/virtual.js +51 -13
  124. package/libx/_runtime/db/query/read.js +12 -8
  125. package/libx/_runtime/db/query/update.js +9 -3
  126. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +8 -9
  127. package/libx/_runtime/{common → db}/utils/propagateForeignKeys.js +11 -14
  128. package/libx/_runtime/fiori/generic/activate.js +1 -0
  129. package/libx/_runtime/fiori/generic/before.js +2 -1
  130. package/libx/_runtime/fiori/generic/edit.js +1 -0
  131. package/libx/_runtime/fiori/generic/patch.js +1 -1
  132. package/libx/_runtime/fiori/generic/read.js +128 -57
  133. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +0 -4
  134. package/libx/_runtime/fiori/uiflex/index.js +1 -1
  135. package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +6 -4
  136. package/libx/_runtime/fiori/utils/delete.js +7 -1
  137. package/libx/_runtime/hana/Service.js +1 -8
  138. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
  139. package/libx/_runtime/hana/execute.js +10 -4
  140. package/libx/_runtime/hana/pool.js +55 -45
  141. package/libx/_runtime/hana/search.js +7 -6
  142. package/libx/_runtime/hana/search2cqn4sql.js +8 -5
  143. package/libx/_runtime/hana/searchToContains.js +3 -1
  144. package/libx/_runtime/index.js +5 -5
  145. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
  146. package/libx/_runtime/messaging/Outbox.js +53 -0
  147. package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
  148. package/libx/_runtime/messaging/common-utils/connections.js +14 -9
  149. package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
  150. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
  151. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
  152. package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
  153. package/libx/_runtime/messaging/file-based.js +5 -5
  154. package/libx/_runtime/messaging/message-queuing-utils/options-messaging.js +1 -0
  155. package/libx/_runtime/messaging/message-queuing.js +2 -3
  156. package/libx/_runtime/messaging/outbox/OutboxRunner.js +75 -0
  157. package/libx/_runtime/messaging/outbox/utils.js +192 -0
  158. package/libx/_runtime/messaging/service.js +16 -30
  159. package/libx/_runtime/remote/Service.js +15 -0
  160. package/libx/_runtime/remote/utils/client.js +15 -3
  161. package/libx/_runtime/remote/utils/{dataConversion.js → data.js} +12 -2
  162. package/libx/_runtime/sqlite/Service.js +7 -10
  163. package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +19 -0
  164. package/libx/_runtime/sqlite/execute.js +18 -12
  165. package/libx/_runtime/types/api.js +2 -1
  166. package/libx/gql/resolvers/parse/ast2cqn/columns.js +1 -1
  167. package/libx/odata/{odata2cqn/afterburner.js → afterburner.js} +28 -16
  168. package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
  169. package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +222 -130
  170. package/libx/odata/index.js +23 -14
  171. package/libx/odata/parser.js +1 -0
  172. package/libx/odata/utils.js +57 -0
  173. package/libx/rest/RestAdapter.js +2 -6
  174. package/libx/rest/utils/data.js +1 -6
  175. package/package.json +4 -3
  176. package/server.js +4 -5
  177. package/srv/audit-log.cds +87 -0
  178. package/{libx/_runtime/fiori/uiflex/extensibility/index.cds → srv/flex.cds} +0 -0
  179. package/srv/flex.js +1 -0
  180. package/srv/outbox.cds +11 -0
  181. package/srv/outbox.js +0 -0
  182. package/libx/_runtime/cds-services/adapter/perf/performance.js +0 -104
  183. package/libx/_runtime/cds-services/adapter/perf/performanceMeasurement.js +0 -33
  184. package/libx/odata/odata2cqn/index.js +0 -3
  185. package/libx/odata/odata2cqn/parser.js +0 -1
  186. package/libx/odata/readme.md +0 -1
  187. 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
 
@@ -368,10 +377,6 @@ const _readAndTransform = (tx, req, odataReq) => {
368
377
  }
369
378
 
370
379
  if (_isCollection(segments)) {
371
- if (odataReq.getUriInfo().getQueryOption(QueryOptions.COUNT)) {
372
- req.query.SELECT.count = true
373
- }
374
-
375
380
  return _readCollection(tx, req, odataReq)
376
381
  }
377
382
 
@@ -384,11 +389,11 @@ const _readAndTransform = (tx, req, odataReq) => {
384
389
  }
385
390
  }
386
391
 
387
- return _readStream(tx, req, segments)
392
+ return _readStream(tx, req)
388
393
  }
389
394
 
390
395
  if (isStreaming(segments)) {
391
- return _readStream(tx, req, segments)
396
+ return _readStream(tx, req)
392
397
  }
393
398
 
394
399
  if (req.target._isSingleton) {
@@ -418,43 +423,6 @@ const _removeKeysForParams = result => {
418
423
  return options
419
424
  }
420
425
 
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
426
  /**
459
427
  * The handler that will be registered with odata-v4.
460
428
  *
@@ -478,21 +446,11 @@ const read = service => {
478
446
  return next(e)
479
447
  }
480
448
 
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
449
  const changeset = odataReq.getAtomicityGroupId()
492
450
  const tx = changeset ? odataReq.getBatchApplicationData().txs[changeset] : service.tx(req)
493
451
  cds.context = tx
494
452
 
495
- let result, err, commit
453
+ let result, err
496
454
  let additional = {}
497
455
  try {
498
456
  // REVISIT: refactor _readAndTransform
@@ -509,13 +467,12 @@ const read = service => {
509
467
  // for passing into commit
510
468
  odataReq.getBatchApplicationData().results[changeset].push({ result, req })
511
469
  } else {
512
- commit = true
513
470
  await tx.commit(result)
514
471
  }
515
472
  } catch (e) {
516
473
  err = e
517
- if (!changeset && !commit) {
518
- // ignore rollback error, which should never happen
474
+ if (!changeset) {
475
+ // REVISIT: rollback needed if error occured before commit attempted -> how to distinguish?
519
476
  await tx.rollback(e).catch(() => {})
520
477
  } else if (changeset) {
521
478
  // 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
@@ -64,7 +64,10 @@ class ExpressionToCQN {
64
64
  const nav =
65
65
  pathSegments.length > 2 ? pathSegments.slice(0, pathSegments.length - 2).map(this._segmentFromMember) : []
66
66
  const navName = this._segmentFromMember(pathSegments[pathSegments.length - 2])
67
- const condition = this._segmentFromMember(pathSegments[pathSegments.length - 1])
67
+ let condition = this._segmentFromMember(pathSegments[pathSegments.length - 1])
68
+
69
+ // in case of functions, condition is an object
70
+ if (condition && !Array.isArray(condition)) condition = [condition]
68
71
 
69
72
  return pathSegments[pathSegments.length - 1].getKind() === 'ALL.EXPRESSION'
70
73
  ? ['not', 'exists', { ref: [...nav, { id: navName, where: ['not', { xpr: condition }] }] }]
@@ -84,6 +87,8 @@ class ExpressionToCQN {
84
87
  case ResourceKind.ALL_EXPRESSION:
85
88
  case ResourceKind.ANY_EXPRESSION:
86
89
  return segment.getExpression() ? this.parse(segment.getExpression()) : undefined
90
+ case ResourceKind.COUNT:
91
+ return '$count'
87
92
  default:
88
93
  throw getFeatureNotSupportedError(`Segment kind "${segment.getKind()}" in $filter query option`)
89
94
  }
@@ -95,6 +100,21 @@ class ExpressionToCQN {
95
100
  if (!segment) return []
96
101
 
97
102
  if (segment.getKind() === ResourceKind.NAVIGATION_TO_ONE) {
103
+ const name = this._segmentFromMember(segment)
104
+ const where =
105
+ nextSegments &&
106
+ nextSegments.length &&
107
+ nextSegments[nextSegments.length - 1].getKind() === ResourceKind.COUNT &&
108
+ segment.getKeyPredicates().reduce((prev, curr) => {
109
+ if (prev.length > 0) prev.push('and')
110
+ prev.push({ ref: [curr.getEdmRef().getName()] }, '=', { val: curr.getText() })
111
+ return prev
112
+ }, [])
113
+
114
+ return [where ? { id: name, where } : name, ...this._getMemberRecursively(nextSegments)]
115
+ }
116
+
117
+ if (segment.getKind() === ResourceKind.NAVIGATION_TO_MANY) {
98
118
  return [this._segmentFromMember(segment), ...this._getMemberRecursively(nextSegments)]
99
119
  }
100
120
 
@@ -102,6 +122,10 @@ class ExpressionToCQN {
102
122
  return [...this._getMemberRecursively(nextSegments)]
103
123
  }
104
124
 
125
+ if (segment.getKind() === ResourceKind.COUNT) {
126
+ return [this._segmentFromMember(segment), ...this._getMemberRecursively(nextSegments)]
127
+ }
128
+
105
129
  if (segment.getKind() === ResourceKind.COMPLEX_PROPERTY) {
106
130
  if (nextSegments.length) {
107
131
  return [this._segmentFromMember(segment), ...this._getMemberRecursively(nextSegments)]
@@ -130,6 +154,10 @@ class ExpressionToCQN {
130
154
  return entry
131
155
  }
132
156
  }
157
+ if (members.length > 1 && members[members.length - 1] === '$count') {
158
+ return { func: 'count', args: [{ ref: members.slice(0, members.length - 1) }], as: '$count' }
159
+ }
160
+
133
161
  return { ref: members }
134
162
  }
135
163
 
@@ -12,6 +12,8 @@ const ExpressionToCQN = require('./ExpressionToCQN')
12
12
  const { getColumns } = require('../../../services/utils/columns')
13
13
  const { addLimit, isSameArray } = require('./utils')
14
14
  const { findCsnTargetFor } = require('../../../../common/utils/csn')
15
+ const getError = require('../../../../common/error')
16
+
15
17
  /**
16
18
  * Check which element(s) of the entity has been expanded.
17
19
  *
@@ -28,7 +30,11 @@ const _getExpandItem = (isAll, expandItems, name) => {
28
30
 
29
31
  return expandItems.find(item => {
30
32
  const pathSegments = item.getPathSegments()
31
- return pathSegments[pathSegments.length - 1].getNavigationProperty().getName() === name
33
+ if (pathSegments[pathSegments.length - 1].getKind() === 'COUNT') {
34
+ throw getError(501, 'EXPAND_COUNT_UNSUPPORTED')
35
+ }
36
+ const navigation = pathSegments[pathSegments.length - 1].getNavigationProperty()
37
+ return navigation && navigation.getName() === name
32
38
  })
33
39
  }
34
40
 
@@ -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
 
@@ -184,6 +184,7 @@ const _extendCqnWithApply = (cqn, apply, entity) => {
184
184
 
185
185
  // REVISIT only execute on HANA?
186
186
  cqn.SELECT.columns = _groupByPathExpressionsToExpand(cqn, entity)
187
+ if (cqn.SELECT.count) cqn.SELECT.__countAggregated = true
187
188
  }
188
189
 
189
190
  const _containsSelectedColumn = (o, selectColumns) => {
@@ -340,6 +341,7 @@ const _handleApply = (apply, select) => {
340
341
  * @param {object} odataReq - OKRA's req
341
342
  * @private
342
343
  */
344
+ // eslint-disable-next-line complexity
343
345
  const readToCQN = (service, target, odataReq) => {
344
346
  const uriInfo = odataReq.getUriInfo()
345
347
  const segments = uriInfo.getPathSegments()
@@ -390,6 +392,8 @@ const readToCQN = (service, target, odataReq) => {
390
392
  const cqn = SELECT.from(isView ? _convertUrlPathToViewCqn(segments) : convertUrlPathToCqn(segments, service), select)
391
393
  addValidationQueryIfRequired(segments, isView, cqn, service, kind)
392
394
 
395
+ if (isCollectionOrToMany && queryOptions && queryOptions[QueryOptions.COUNT]) cqn.SELECT.count = true
396
+
393
397
  if (Object.keys(apply).length) {
394
398
  _extendCqnWithApply(cqn, apply, entity)
395
399
  }
@@ -400,7 +404,7 @@ const readToCQN = (service, target, odataReq) => {
400
404
  }
401
405
 
402
406
  if (isCollectionOrToMany) {
403
- _topSkip(queryOptions, getMaxPageSize(target), cqn)
407
+ _topSkip(queryOptions, target, cqn)
404
408
  _orderby(uriInfo, cqn)
405
409
  }
406
410
 
@@ -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') {