@sap/cds 7.7.3 → 7.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/lib/auth/ias-auth.js +5 -3
  3. package/lib/auth/jwt-auth.js +4 -2
  4. package/lib/compile/cdsc.js +0 -10
  5. package/lib/compile/for/java.js +9 -5
  6. package/lib/compile/for/lean_drafts.js +1 -1
  7. package/lib/compile/to/edm.js +2 -1
  8. package/lib/compile/to/sql.js +0 -21
  9. package/lib/compile/to/srvinfo.js +13 -4
  10. package/lib/dbs/cds-deploy.js +7 -7
  11. package/lib/env/cds-requires.js +6 -0
  12. package/lib/index.js +4 -3
  13. package/lib/linked/classes.js +151 -88
  14. package/lib/linked/entities.js +28 -23
  15. package/lib/linked/models.js +57 -36
  16. package/lib/linked/types.js +42 -104
  17. package/lib/ql/Whereable.js +3 -3
  18. package/lib/req/context.js +9 -5
  19. package/lib/srv/protocols/hcql.js +2 -1
  20. package/lib/srv/protocols/http.js +7 -7
  21. package/lib/srv/protocols/index.js +31 -13
  22. package/lib/srv/protocols/odata-v4.js +79 -58
  23. package/lib/srv/srv-api.js +7 -6
  24. package/lib/srv/srv-dispatch.js +1 -12
  25. package/lib/srv/srv-tx.js +9 -13
  26. package/lib/utils/cds-utils.js +6 -5
  27. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +11 -8
  28. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +21 -12
  29. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +5 -3
  30. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -7
  31. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +5 -0
  32. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -1
  33. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +3 -7
  34. package/libx/_runtime/cds-services/services/utils/columns.js +6 -3
  35. package/libx/_runtime/cds.js +0 -13
  36. package/libx/_runtime/common/generic/input.js +3 -0
  37. package/libx/_runtime/common/generic/sorting.js +8 -6
  38. package/libx/_runtime/common/i18n/messages.properties +1 -0
  39. package/libx/_runtime/common/utils/cqn.js +5 -0
  40. package/libx/_runtime/common/utils/foreignKeyPropagations.js +7 -1
  41. package/libx/_runtime/common/utils/keys.js +2 -2
  42. package/libx/_runtime/common/utils/resolveView.js +2 -1
  43. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
  44. package/libx/_runtime/common/utils/stream.js +0 -10
  45. package/libx/_runtime/common/utils/template.js +20 -35
  46. package/libx/_runtime/db/Service.js +5 -1
  47. package/libx/_runtime/db/utils/columns.js +1 -1
  48. package/libx/_runtime/fiori/lean-draft.js +14 -2
  49. package/libx/_runtime/messaging/Outbox.js +7 -5
  50. package/libx/_runtime/messaging/kafka.js +266 -0
  51. package/libx/_runtime/messaging/service.js +7 -5
  52. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
  53. package/libx/common/assert/validation.js +1 -1
  54. package/libx/odata/index.js +8 -2
  55. package/libx/odata/middleware/batch.js +340 -0
  56. package/libx/odata/middleware/create.js +43 -46
  57. package/libx/odata/middleware/delete.js +27 -15
  58. package/libx/odata/middleware/error.js +6 -5
  59. package/libx/odata/middleware/metadata.js +16 -15
  60. package/libx/odata/middleware/operation.js +107 -59
  61. package/libx/odata/middleware/parse.js +15 -7
  62. package/libx/odata/middleware/read.js +150 -24
  63. package/libx/odata/middleware/service-document.js +17 -6
  64. package/libx/odata/middleware/stream.js +34 -17
  65. package/libx/odata/middleware/update.js +123 -87
  66. package/libx/odata/parse/afterburner.js +131 -28
  67. package/libx/odata/parse/cqn2odata.js +1 -1
  68. package/libx/odata/parse/grammar.peggy +4 -5
  69. package/libx/odata/parse/multipartToJson.js +163 -0
  70. package/libx/odata/parse/parser.js +1 -1
  71. package/libx/odata/utils/index.js +29 -47
  72. package/libx/odata/utils/path.js +72 -0
  73. package/libx/odata/utils/result.js +123 -20
  74. package/package.json +1 -1
  75. package/server.js +4 -0
@@ -1,73 +1,94 @@
1
1
  const cds = require('../../index'),
2
2
  { User } = cds,
3
3
  { decodeURI } = cds.utils
4
- const libx = require('../../../libx/_runtime')
5
- const LOG = cds.log('odata')
6
4
  const express = require('express') // eslint-disable-line cds/no-missing-dependencies
7
- const { isStream, stream } = require('../../../libx/odata/middleware/stream')
8
5
 
9
- module.exports = function ODataAdapter(srv) {
10
- const router = express.Router()
6
+ if (cds.env.features.odata_new_adapter) {
7
+ const { isStream, stream } = require('../../../libx/odata/middleware/stream')
8
+ const BaseProtocolAdapter = require('./http')
9
+ // REVISIT: odata_new_adapter shouldn't influence odata_new_parser and cds_assert, but we'd need to set marker in cds.context or so to remove this
10
+ cds.env.features.odata_new_parser = true
11
+ cds.env.features.cds_assert = true
11
12
 
12
- router.use((req, _, next) => {
13
- let u = req.user
14
- req.user = u instanceof User ? u : new User(u)
13
+ module.exports = class ODataAdapter extends BaseProtocolAdapter {
14
+ log(req) {
15
+ let u = req.user
16
+ req.user = u instanceof User ? u : new User(u)
15
17
 
16
- let url = decodeURI(req.originalUrl)
17
- LOG && LOG(req.method, url, req.body || '')
18
- if (/\$batch/.test(req.url))
19
- req.on('dispatch', req => {
20
- let path = decodeURI(req._path)
21
- LOG && LOG('>', req.event, path, req._query || '')
22
- if (LOG._debug && req.query) LOG.debug(req.query)
23
- })
18
+ // REVISIT: how to handle logging of batch subrequests? (note: service is missing from path)
19
+ let url = decodeURI(req.originalUrl)
20
+ this.logger.info(req.method, url, req.body || '')
21
+ if (/\$batch/.test(req.url))
22
+ req.on('dispatch', req => {
23
+ let path = decodeURI(req._path)
24
+ this.logger.info('>', req.event, path, req._query || '')
25
+ if (this.logger._debug && req.query) this.logger.debug(req.query)
26
+ })
27
+ }
24
28
 
25
- next()
26
- })
29
+ router4(srv) {
30
+ const router = super.router4(srv)
31
+ const jsonBodyParser = express.json()
27
32
 
28
- if (cds.env.features.odata_new_adapter) {
29
- // REVISIT: odata_new_adapter shouldn't influence odata_new_parser and cds_assert, but we'd need to set marker in cds.context or so to remove this
30
- cds.env.features.odata_new_parser = true
31
- cds.env.features.cds_assert = true
32
- // REVISIT: add middleware for negative cases?
33
- // service root
34
- router.use(/^\/$/, require('../../../libx/odata/middleware/service-document')(srv))
35
- router.use('/\\$metadata', require('../../../libx/odata/middleware/metadata')(srv))
36
- // parse
37
- router.use(require('../../../libx/odata/middleware/parse')(srv))
38
- // REVISIT do we want to build our own body parser logic?
39
- router.use((req, res, next) => {
40
- const method = req.method
41
- if (method !== 'PUT') {
42
- return express.json()(req, res, next)
43
- }
33
+ return (
34
+ router
35
+ // REVISIT: add middleware for negative cases?
36
+ // service root
37
+ .use(/^\/$/, require('../../../libx/odata/middleware/service-document')(srv))
38
+ .use('/\\$metadata', require('../../../libx/odata/middleware/metadata')(srv))
39
+ // parse
40
+ .use(require('../../../libx/odata/middleware/parse')(srv))
41
+ // REVISIT do we want to build our own body parser logic?
42
+ .use((req, res, next) => {
43
+ // REVISIT: body of batch subrequests are already deserialized
44
+ if (typeof req.body === 'object') return next()
45
+ if (req.method === 'PUT' && isStream(req._query)) {
46
+ req.body = { value: req }
47
+ return next(null, req, res)
48
+ }
49
+ // TODO: check if the raw body still exists, then we can remove deepCopy() in the handlers
50
+ return jsonBodyParser(req, res, next)
51
+ })
52
+ // batch
53
+ .post('/\\$batch', require('../../../libx/odata/middleware/batch')(srv, router))
54
+ // handle
55
+ // REVISIT: with old adapter, we return 405 for HEAD requests -> check OData spec
56
+ .head('*', (_, res) => res.sendStatus(405))
57
+ .post('*', require('../../../libx/odata/middleware/operation')(srv)) //> action
58
+ .get('*', require('../../../libx/odata/middleware/operation')(srv)) //> function
59
+ .post('*', require('../../../libx/odata/middleware/create')(srv))
60
+ .get('*', stream(srv))
61
+ .get('*', require('../../../libx/odata/middleware/read')(srv))
62
+ .put('*', require('../../../libx/odata/middleware/update')(srv))
63
+ .patch('*', require('../../../libx/odata/middleware/update')(srv))
64
+ .delete('*', require('../../../libx/odata/middleware/delete')(srv))
65
+ // error
66
+ .use(require('../../../libx/odata/middleware/error')(srv))
67
+ )
68
+ }
69
+ }
70
+ } else {
71
+ const libx = require('../../../libx/_runtime')
72
+ const LOG = cds.log('odata')
73
+ module.exports = function ODataAdapter(srv) {
74
+ const router = express.Router()
44
75
 
45
- const { _query: query } = req
46
- if (isStream(query)) {
47
- req.body = { value: req }
48
- return next(null, req, res)
49
- }
76
+ router.use((req, _, next) => {
77
+ let u = req.user
78
+ req.user = u instanceof User ? u : new User(u)
50
79
 
51
- // TODO: check if the raw body still exists, then we can remove deepCopy() in the handlers
52
- return express.json()(req, res, next)
53
- })
80
+ let url = decodeURI(req.originalUrl)
81
+ LOG && LOG(req.method, url, req.body || '')
82
+ if (/\$batch/.test(req.url))
83
+ req.on('dispatch', req => {
84
+ let path = decodeURI(req._path)
85
+ LOG && LOG('>', req.event, path, req._query || '')
86
+ if (LOG._debug && req.query) LOG.debug(req.query)
87
+ })
54
88
 
55
- // handle
56
- // REVISIT: with old adapter, we return 405 for HEAD requests -> check OData spec
57
- router.head('*', (_, res) => res.sendStatus(405))
58
- router.post('*', require('../../../libx/odata/middleware/operation')(srv)) //> action
59
- router.get('*', require('../../../libx/odata/middleware/operation')(srv)) //> function
60
- router.post('*', require('../../../libx/odata/middleware/create')(srv))
61
- router.get('*', stream(srv))
62
- router.get('*', require('../../../libx/odata/middleware/read')(srv))
63
- router.put('*', require('../../../libx/odata/middleware/update')(srv))
64
- router.patch('*', require('../../../libx/odata/middleware/update')(srv))
65
- router.delete('*', require('../../../libx/odata/middleware/delete')(srv))
66
- // error
67
- router.use(require('../../../libx/odata/middleware/error')(srv))
68
- } else {
89
+ next()
90
+ })
69
91
  router.use(libx.to.odata_v4(srv))
92
+ return router
70
93
  }
71
-
72
- return router
73
94
  }
@@ -66,11 +66,11 @@ class Service extends require('./srv-handlers') {
66
66
  */
67
67
  run (query, data) {
68
68
  if (typeof query === 'function') {
69
- const ctx = cds.context, fn = query
70
- if (!ctx?.tx) return this.tx(fn) // run fn with root tx
71
- if (!ctx.tx._done) return fn(this.tx(ctx)) // run fn with nested tx
72
- if (ctx.tx._done === 'rolled back') ctx.tx._throw_closed_error()
73
- else return this.tx(fn) // run fn with detached root tx
69
+ const fn = query; if (this.context) return fn(this) // if this is a tx -> run fn with this
70
+ const ctx = cds.context, tx = ctx?.tx // is there an (open) outer tx? ...
71
+ if (!tx || tx._done === 'committed') return this.tx(fn) // no -> run fn with root tx
72
+ if (tx._done !== 'rolled back') return fn(this.tx(ctx)) // yes -> run fn with nested tx
73
+ else throw this.tx._is_done (tx._done) // throw if outer tx was rolled back
74
74
  }
75
75
  const req = new Request ({ query, data })
76
76
  return this.dispatch (req)
@@ -101,10 +101,11 @@ class Service extends require('./srv-handlers') {
101
101
  || !this.isDatabaseService && !/\W/.test(this.name) && this.name
102
102
  || undefined
103
103
  }
104
- get operations() { return super.operations = _reflect (this, d => d.kind === 'action' || d.kind === 'function') }
105
104
  get entities() { return super.entities = _reflect (this, d => d.kind === 'entity') }
106
105
  get events() { return super.events = _reflect (this, d => d.kind === 'event') }
107
106
  get types() { return super.types = _reflect (this, d => !d.kind || d.kind === 'type') }
107
+ get actions() { return super.actions = _reflect (this, d => d.kind === 'action' || d.kind === 'function') }
108
+ get operations() { return super.operations = _reflect (this, d => d.kind === 'action' || d.kind === 'function') }
108
109
 
109
110
  /**
110
111
  * Flag to control whether this service is extensible.
@@ -12,18 +12,7 @@ const cds = require ('../index')
12
12
  exports.dispatch = async function dispatch (req) { //NOSONAR
13
13
 
14
14
  // Ensure we are in a proper transaction
15
- if (!this.context) {
16
- const ctx = cds.context
17
-
18
- if (!ctx?.tx) return this.tx(tx => tx.dispatch(req)) // with root tx
19
- else {
20
- if (!ctx.tx._done) return this.tx(ctx).dispatch(req) // with nested tx
21
- else if (ctx.tx._done === 'rolled back') // > reject
22
- ctx.tx._throw_closed_error()
23
- else return this.tx(tx => tx.dispatch(req)) // with detached root tx
24
- }
25
- }
26
-
15
+ if (!this.context) return this.run (tx => tx.dispatch(req))
27
16
  if (!req.tx) req.tx = this // `this` is a tx from now on...
28
17
 
29
18
  // Inform potential listeners // REVISIT: -> this should move into protocol adapters
package/lib/srv/srv-tx.js CHANGED
@@ -20,7 +20,7 @@ class NestedContext extends EventContext {
20
20
  * @returns { Promise<Transaction & import('./srv-api')> }
21
21
  * @param { EventContext } ctx
22
22
  */
23
- function srv_tx (ctx,fn) { const srv = this
23
+ module.exports = exports = function srv_tx (ctx,fn) { const srv = this
24
24
 
25
25
  if (srv.context) return srv // srv.tx().tx() -> idempotent
26
26
  if (!ctx) return RootTransaction.for (srv)
@@ -64,7 +64,6 @@ class Transaction {
64
64
  const proto = new.target.prototype
65
65
  tx.commit = proto.commit.bind(tx)
66
66
  tx.rollback = proto.rollback.bind(tx)
67
- tx._throw_closed_error = proto._throw_closed_error.bind(tx)
68
67
  if (srv.isExtensible) {
69
68
  const m = cds.context?.model
70
69
  if (m) tx.model = m
@@ -107,13 +106,6 @@ class Transaction {
107
106
  if (err) throw err
108
107
  }
109
108
 
110
- _throw_closed_error () {
111
- throw cds.error (
112
- `Transaction is ${this._done}, no subsequent .run allowed, without prior .begin`,
113
- { code: 'TRANSACTION_CLOSED' }
114
- )
115
- }
116
-
117
109
  }
118
110
 
119
111
 
@@ -200,12 +192,16 @@ const _begin = async function (req) {
200
192
  if (!req.query && req.method === 'BEGIN') // IMPORTANT: !req.query is to exclude batch requests
201
193
  return this.ready = this.__proto__.dispatch.call (this,req)
202
194
  // Protection against unintended tx.run() after root tx.commit/rollback()
203
- if (typeof this.ready === 'string' || !this.ready && this.context.tx._done)
204
- this.context.tx._throw_closed_error()
205
- else if (!this.ready) this.ready = this.begin()
195
+ if (this.ready === 'rolled back') throw exports._is_done (this.ready)
196
+ if (this.ready === 'committed') throw exports._is_done (this.ready)
197
+ if (!this.ready && this.context.tx._done) throw exports._is_done (this.context.tx._done)
198
+ if (!this.ready) this.ready = this.begin().then(()=>true)
206
199
  await this.ready
207
200
  delete this.dispatch
208
201
  return this.dispatch (req)
209
202
  }
210
203
 
211
- module.exports = srv_tx
204
+ exports._is_done = done => new cds.error (
205
+ `Transaction is ${done}, no subsequent .run allowed, without prior .begin`,
206
+ { code: 'TRANSACTION_CLOSED' }
207
+ )
@@ -183,15 +183,16 @@ exports.find = function find (base, patterns='*', filter=()=>true) {
183
183
  exports.deprecated = (fn, { kind = 'Method', old = fn.name+'()', use } = {}) => {
184
184
  const yellow = '\x1b[33m'
185
185
  const reset = '\x1b[0m'
186
+ // use cds.log in production for custom logger
187
+ const log = cds.env.production ? cds.log().warn: console.log
186
188
  if(typeof fn !== 'function') {
187
189
  if (cds.env.features.deprecated === 'off') return
188
-
189
190
  [kind,old,use] = [fn.kind || 'Configuration',fn.old,fn.use]
190
- console.log (
191
+ log (
191
192
  yellow,
192
193
  '\n------------------------------------------------------------------------------',
193
194
  '\nDEPRECATED:', old, '\n',
194
- '\n ', kind, old, 'is deprecated and will be removed in upcoming releases!',
195
+ '\n ', (kind ? `${kind} ${old}` : old), 'is deprecated and will be removed in upcoming releases!',
195
196
  use ? `\n => Please use ${use} instead.` : '', '\n',
196
197
  '\n------------------------------------------------------------------------------\n',
197
198
  reset
@@ -199,11 +200,11 @@ exports.deprecated = (fn, { kind = 'Method', old = fn.name+'()', use } = {}) =>
199
200
  } else return function() {
200
201
  if (cds.env.features.deprecated !== 'off' && !fn.warned) {
201
202
  let o={}; Error.captureStackTrace(o)
202
- console.warn (
203
+ log (
203
204
  yellow,
204
205
  '\n------------------------------------------------------------------------------',
205
206
  '\nDEPRECATED:', old, '\n',
206
- '\n ', kind, old, 'is deprecated and will be removed in upcoming releases!',
207
+ '\n ', (kind ? `${kind} ${old}` : old), 'is deprecated and will be removed in upcoming releases!',
207
208
  use ? `\n => Please use ${use} instead.` : '', '\n',
208
209
  o.stack.replace(/^Error\n\s*at.*\n/,'\n'), '\n',
209
210
  '\n------------------------------------------------------------------------------\n',
@@ -17,6 +17,15 @@ const { isCustomOperation } = require('./utils/request')
17
17
  const { isStreaming } = require('./utils/stream')
18
18
  const { handleStreamProperties } = require('../../../common/utils/streamProp')
19
19
 
20
+ // getter functions extracted to show deprecation warning only once
21
+ const _getAttr = attr => attr
22
+ const _getShared = (oReq, req, res) => {
23
+ if (oReq.context) oReq._shared = oReq.context._shared = oReq.context._shared || { req, res }
24
+ else oReq._shared = oReq._shared || { req, res }
25
+
26
+ return oReq._shared
27
+ }
28
+
20
29
  function _isCorrectCallToViewWithParams(csdlStructuredType) {
21
30
  return (
22
31
  csdlStructuredType.navigationProperties &&
@@ -215,12 +224,7 @@ class ODataRequest extends cds.Request {
215
224
  const that = this
216
225
  Object.defineProperty(this._, 'shared', {
217
226
  get() {
218
- cds._logDeprecation('req._.shared is deprecated and will be removed.')
219
-
220
- if (that.context) that._shared = that.context._shared = that.context._shared || { req, res }
221
- else that._shared = that._shared || { req, res }
222
-
223
- return that._shared
227
+ return cds.utils.deprecated(_getShared, { kind: 'Property', old: 'req._.shared' })(that, req, res)
224
228
  }
225
229
  })
226
230
 
@@ -228,8 +232,7 @@ class ODataRequest extends cds.Request {
228
232
  const attr = { identityZone: this.tenant }
229
233
  Object.defineProperty(this, 'attr', {
230
234
  get() {
231
- cds._logDeprecation('req.attr is deprecated and will be removed.')
232
- return attr
235
+ return cds.utils.deprecated(_getAttr, { kind: 'Property', old: 'req.attr' })(attr)
233
236
  }
234
237
  })
235
238
 
@@ -21,8 +21,7 @@ const { isStreaming } = require('../utils/stream')
21
21
  const { resolveStructuredName } = require('../utils/handlerUtils')
22
22
  const getError = require('../../../../common/error')
23
23
  const { getSapMessages } = require('../../../../common/error/frontend')
24
- const { getPageSize, commonGenericPaging } = require('../../../../common/generic/paging')
25
- const { handler: commonGenericSorting } = require('../../../../common/generic/sorting')
24
+ const { getPageSize } = require('../../../../common/generic/paging')
26
25
  const { getTransition } = require('../../../../common/utils/resolveView')
27
26
  const { Readable } = require('stream')
28
27
 
@@ -244,9 +243,6 @@ const _reliablePagingPossible = req => {
244
243
  */
245
244
  // eslint-disable-next-line complexity
246
245
  const _readCollection = async (tx, req, odataReq) => {
247
- commonGenericPaging(req)
248
- commonGenericSorting(req)
249
-
250
246
  const result = (await tx.dispatch(req)) || []
251
247
 
252
248
  if (req.http?.req?.headers?.accept?.match(/application\/pdf/)) {
@@ -271,9 +267,7 @@ const _readCollection = async (tx, req, odataReq) => {
271
267
  if (req.query[0].SELECT.count) result.$count = adjustedResult.$count || 0
272
268
  } else if (req.query.SELECT.count && !('$count' in result)) result.$count = 0
273
269
 
274
- const limit = Array.isArray(req.query)
275
- ? getPageSize(req.query[0]._target).max
276
- : req.query.SELECT.limit && req.query.SELECT.limit.rows && req.query.SELECT.limit.rows.val
270
+ const limit = Array.isArray(req.query) ? getPageSize(req.query[0]._target).max : req.query.SELECT.limit?.rows?.val
277
271
  const top = odataReq.getUriInfo().getQueryOption(QueryOptions.TOP) || parseInt(odataReq._queryOptions?.$top)
278
272
 
279
273
  if (limit && limit === result.length && limit !== top && !('$nextLink' in result)) {
@@ -554,10 +548,25 @@ const read = service => {
554
548
  // for passing into commit
555
549
  odataReq.getBatchApplicationData().results[changeset].push({ result, req })
556
550
  } else if (result.value && isStreaming(odataReq.getUriInfo().getPathSegments())) {
557
- // REVISIT: temp workaround for url streaming
558
- if (!(result.value instanceof Readable)) _ensureStream(result)
559
- result.value.on('end', () => tx.commit(result).catch(() => {}))
560
- result.value.once('error', err => tx.rollback(err).catch(() => {}))
551
+ if (odataRes._response.destroyed) {
552
+ err = new Error('Response is closed while streaming')
553
+ tx.rollback(err).catch(() => {})
554
+ } else {
555
+ // REVISIT: temp workaround for url streaming
556
+ if (!(result.value instanceof Readable)) _ensureStream(result)
557
+ result.value.on('end', () => tx.commit(result).catch(() => {}))
558
+ result.value.once('error', err => tx.rollback(err).catch(() => {}))
559
+ let finished = false
560
+ odataRes._response.on('finish', () => {
561
+ finished = true
562
+ })
563
+ odataRes._response.on('close', () => {
564
+ if (!finished) {
565
+ err = new Error('Response is closed while streaming')
566
+ tx.rollback(err).catch(() => {})
567
+ }
568
+ })
569
+ }
561
570
  } else {
562
571
  await tx.commit(result)
563
572
  }
@@ -1,5 +1,4 @@
1
1
  const cds = require('../../../cds')
2
- const OData = require('./OData')
3
2
 
4
3
  /**
5
4
  * This is the express handler for a specific OData endpoint.
@@ -10,8 +9,10 @@ module.exports = srv => {
10
9
  return okra.process.bind(okra)
11
10
  }
12
11
 
12
+ let OData
13
13
  function OkraAdapter(srv, model = srv.model) {
14
14
  const edm = cds.compile.to.edm(model, { service: srv.definition?.name || srv.name })
15
+ OData ??= require('./OData')
15
16
  return new OData(edm, model, srv.options).addCDSServiceToChannel(srv)
16
17
  }
17
18
 
@@ -19,7 +20,8 @@ function OkraAdapter(srv, model = srv.model) {
19
20
  //
20
21
  // REVISIT: Move to ExtensibilityService
21
22
  //
22
- if (cds.requires.extensibility || cds.requires.toggles)
23
+ if (cds.requires.extensibility || cds.requires.toggles) {
24
+ let unique = 0
23
25
  module.exports = srv => {
24
26
  const id = `${++unique} - ${srv.path}` // REVISIT: this is to allow running multiple express apps serving same endpoints, as done by some questionable tests
25
27
  return function ODataAdapter(req, res) {
@@ -36,4 +38,4 @@ if (cds.requires.extensibility || cds.requires.toggles)
36
38
  return okra.process(req, res)
37
39
  }
38
40
  }
39
- let unique = 0
41
+ }
@@ -1,12 +1,8 @@
1
1
  const cds = require('../../../../cds')
2
2
  const { isCustomOperation } = require('./request')
3
- const {
4
- uri: {
5
- UriResource: {
6
- ResourceKind: { COMPLEX_PROPERTY, PRIMITIVE_PROPERTY }
7
- }
8
- }
9
- } = require('../okra/odata-server')
3
+ // don't require okra when using new adapter -> define constants here
4
+ const COMPLEX_PROPERTY = 'COMPLEX.PROPERTY'
5
+ const PRIMITIVE_PROPERTY = 'PRIMITIVE.PROPERTY'
10
6
  const { DRAFT_COLUMNS_MAP } = require('../../../../common/constants/draft')
11
7
  const { SELECT } = cds.ql
12
8
 
@@ -262,6 +262,11 @@ const _isStructuredProperty = ({ returnType }) => {
262
262
  const _getContextUrl = options => {
263
263
  if (!options.returnType) return ''
264
264
  const contextUrlPrefix = _getContextUrlPrefix(options)
265
+
266
+ if (options.returnType.kind === 'service') {
267
+ return `${contextUrlPrefix}$metadata`
268
+ }
269
+
265
270
  const returnTypeUrl = _getReturnTypeUrl(options)
266
271
  const columnsStringified = options.propertyName || _isStructuredProperty(options) ? '' : _listColumns(options)
267
272
  const $entity = _isSingleEntity(options) ? '/$entity' : ''
@@ -51,13 +51,14 @@ const _ensureKeysAreSelected = query => {
51
51
  }
52
52
  }
53
53
 
54
- const readAfterWrite = async (req, srv, { operation, isBefore } = { isBefore: false }) => {
54
+ const readAfterWrite = async (req, srv, { operation, isBefore, columns } = { isBefore: false }) => {
55
55
  let query
56
56
 
57
57
  if (_isActionOrFunction(req)) {
58
58
  const { result, returnType } = operation
59
59
  query = getSimpleSelectCQN(returnType, result, _getOperationQueryColumns(req._queryOptions))
60
60
  if (_isDraftAction(req)) query.where({ IsActiveEntity: req.event === 'draftActivate' })
61
+ if (columns) query.SELECT.columns = columns
61
62
  } else if (req.event === 'NEW' || req.event === 'PATCH') {
62
63
  const { result } = operation
63
64
  if (req.query.UPDATE?.entity?.ref[0]?.where) {
@@ -1,10 +1,6 @@
1
- const {
2
- uri: {
3
- UriResource: {
4
- ResourceKind: { ENTITY, ENTITY_COLLECTION }
5
- }
6
- }
7
- } = require('../okra/odata-server')
1
+ // don't require okra when using new adapter -> define constants here
2
+ const ENTITY = 'ENTITY'
3
+ const ENTITY_COLLECTION = 'ENTITY.COLLECTION'
8
4
 
9
5
  const getError = require('../../../../common/error')
10
6
 
@@ -113,9 +113,12 @@ const _getSearchableColumns = entity => {
113
113
  const defaultSearchFilteredColumns = searchableColumns.filter(column => column[defaultSearchElementTerm])
114
114
 
115
115
  if (defaultSearchFilteredColumns.length > 0) {
116
- cds._logDeprecation(
117
- 'Annotation "@Search.defaultSearchElement" is deprecated and will be removed. Use "@cds.search" instead.'
118
- )
116
+ // Remove with cds 8
117
+ cds.utils.deprecated({
118
+ kind: 'Annotation',
119
+ old: '@Search.defaultSearchElement in entity ' + entity.name,
120
+ use: '@cds.search'
121
+ })
119
122
  return defaultSearchFilteredColumns.map(column => column.name)
120
123
  }
121
124
 
@@ -14,16 +14,3 @@ cds.extend(entity).with(require('./common/aspects/entity'))
14
14
  * cds.assert(data, definition, options?)
15
15
  */
16
16
  Object.defineProperty(cds, 'assert', { get: () => require('../common/assert') })
17
-
18
- /**
19
- * Logs a deprecation warning once per process
20
- *
21
- * @param {string} msg the deprecation warning to be logged
22
- */
23
- cds._logDeprecation = function (msg) {
24
- if (!cds._deprecations.has(msg)) {
25
- cds._deprecations.add(msg)
26
- cds.log().warn(msg)
27
- }
28
- }
29
- Object.defineProperty(cds, '_deprecations', { value: new Set() })
@@ -113,6 +113,7 @@ const _processCategory = (req, category, value, elementInfo, assertMap) => {
113
113
  const managed = `@cds.on.${event === 'CREATE' ? 'insert' : 'update'}`
114
114
  if (cds.env.features.preserve_computed !== false && req._?.event === 'draftActivate' && !element[managed]) return
115
115
 
116
+ // FIXME: req.context?.event not available with new odata adapter
116
117
  // Always take over the values from active entities
117
118
  if (cds.env.fiori?.lean_draft && req.context?.event === 'EDIT') return
118
119
 
@@ -126,6 +127,7 @@ const _processCategory = (req, category, value, elementInfo, assertMap) => {
126
127
 
127
128
  // remove immutable (can also be complex, so do first)
128
129
  if (category === 'immutable' && event === 'UPDATE') {
130
+ // FIXME: req.context?.event not available with new odata adapter
129
131
  // Always take over the values from active entities
130
132
  if (cds.env.fiori?.lean_draft && req.context?.event === 'EDIT') return
131
133
 
@@ -228,6 +230,7 @@ const _callError = (req, errors) => {
228
230
  for (const error of errors) req.error(error)
229
231
  }
230
232
 
233
+ // FIXME: req.context?.event not available with new odata adapter
231
234
  const _getBoundAction = req => req.target.actions?.[req._?.event || req.context?.event]
232
235
  const _getBoundActionBindingParameter = action => action['@cds.odata.bindingparameter.name'] || 'in'
233
236
 
@@ -1,21 +1,23 @@
1
1
  const cds = require('../../cds')
2
- const { getAllKeys } = require('../../cds-services/adapter/odata-v4/odata-to-cqn/utils')
3
2
  const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
4
3
 
5
4
  const _getStaticOrders = req => {
6
5
  const { target: entity, query } = req
7
6
  const defaultOrders = entity['@cds.default.order'] || entity['@odata.default.order'] || []
8
7
 
9
- if (defaultOrders.length > 0) {
10
- cds._logDeprecation(
11
- 'Annotations "@cds.default.order" and "@odata.default.order" are deprecated and will be removed.'
12
- )
8
+ if (entity['@cds.default.order']) {
9
+ // Remove with cds 8
10
+ cds.utils.deprecated({ kind: 'Annotation', old: '@cds.default.order of entity ' + entity.name })
11
+ }
12
+ if (entity['@odata.default.order']) {
13
+ // Remove with cds 8
14
+ cds.utils.deprecated({ kind: 'Annotation', old: '@odata.default.order of entity ' + entity.name })
13
15
  }
14
16
 
15
17
  const ordersFromKeys = []
16
18
 
17
19
  if (req.target._isSingleton || query.SELECT.limit) {
18
- const keys = getAllKeys(entity, true)
20
+ const keys = [...(entity.keys || [])].filter(k => !k.isAssociation).map(k => k.name)
19
21
  for (const key of keys) {
20
22
  if (!(key in DRAFT_COLUMNS_MAP) && !defaultOrders.some(o => o.by['='] === key)) {
21
23
  ordersFromKeys.push({ by: { '=': key } })
@@ -15,6 +15,7 @@
15
15
  415=Unsupported Media Type
16
16
  416=Range Not Satisfiable
17
17
  417=Expectation Failed
18
+ 422=Unprocessable Content
18
19
  424=Failed Dependency
19
20
  428=Precondition Required
20
21
  429=Too Many Requests
@@ -47,6 +47,9 @@ function where2obj(where, target = null, data = {}) {
47
47
  }
48
48
 
49
49
  function targetFromPath(from, model) {
50
+ if (from.SELECT) {
51
+ return targetFromPath(from.SELECT.from, model)
52
+ }
50
53
  const fromRef = from ? from.ref || [from] : []
51
54
  let last = fromRef.length ? model.definitions : {}
52
55
  const path = []
@@ -71,6 +74,8 @@ function targetFromPath(from, model) {
71
74
  if (last.kind === 'entity') {
72
75
  target = last
73
76
  setEntityContained(target, model, isContained)
77
+ } else if (last.kind === 'service') {
78
+ target = last
74
79
  }
75
80
  }
76
81
  return { last, path, target, isTargetComposition: isContained }
@@ -227,7 +227,13 @@ const _createForeignKey = (name, el, parent, foreignKeySource) => {
227
227
  const navigationCsn = parent.elements[foreignKeySource]
228
228
  const key = navigationCsn.key
229
229
 
230
- const foreignKeyCsn = Object.assign(Object.create(tk || el), { parent, name, foreignKeySource, key })
230
+ // IMPORTANT: Object.create is used to override inherited non-enumerable properties. Object.assign would not work.
231
+ const foreignKeyCsn = Object.create(tk || el, {
232
+ parent: { value: parent },
233
+ name: { value: name },
234
+ key: { value: key },
235
+ foreignKeySource: { value: foreignKeySource }
236
+ })
231
237
  // REVISIT: Overwrite previously defined annotations, maybe there's a better way.
232
238
  // We might need to be careful with cached information (__xxx)
233
239
  for (const prop in tk || el) {
@@ -5,8 +5,8 @@ const { getOnCond } = require('./generateOnCond')
5
5
  function _getOnCondElements(onCond, onCondElements = []) {
6
6
  const andIndex = onCond.indexOf('and')
7
7
 
8
- const ref0 = onCond[0].ref
9
- const ref1 = onCond[2].ref
8
+ const ref0 = onCond[0].xpr ? onCond[0].xpr[0].ref : onCond[0].ref
9
+ const ref1 = onCond[0].xpr ? onCond[0].xpr[2].ref : onCond[2].ref
10
10
 
11
11
  let entityRef, targetRef
12
12
  if (ref0 && ref0[0] === 'target') {