@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.
- package/CHANGELOG.md +31 -1
- package/lib/auth/ias-auth.js +5 -3
- package/lib/auth/jwt-auth.js +4 -2
- package/lib/compile/cdsc.js +0 -10
- package/lib/compile/for/java.js +9 -5
- package/lib/compile/for/lean_drafts.js +1 -1
- package/lib/compile/to/edm.js +2 -1
- package/lib/compile/to/sql.js +0 -21
- package/lib/compile/to/srvinfo.js +13 -4
- package/lib/dbs/cds-deploy.js +7 -7
- package/lib/env/cds-requires.js +6 -0
- package/lib/index.js +4 -3
- package/lib/linked/classes.js +151 -88
- package/lib/linked/entities.js +28 -23
- package/lib/linked/models.js +57 -36
- package/lib/linked/types.js +42 -104
- package/lib/ql/Whereable.js +3 -3
- package/lib/req/context.js +9 -5
- package/lib/srv/protocols/hcql.js +2 -1
- package/lib/srv/protocols/http.js +7 -7
- package/lib/srv/protocols/index.js +31 -13
- package/lib/srv/protocols/odata-v4.js +79 -58
- package/lib/srv/srv-api.js +7 -6
- package/lib/srv/srv-dispatch.js +1 -12
- package/lib/srv/srv-tx.js +9 -13
- package/lib/utils/cds-utils.js +6 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +11 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +21 -12
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +5 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +5 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +3 -7
- package/libx/_runtime/cds-services/services/utils/columns.js +6 -3
- package/libx/_runtime/cds.js +0 -13
- package/libx/_runtime/common/generic/input.js +3 -0
- package/libx/_runtime/common/generic/sorting.js +8 -6
- package/libx/_runtime/common/i18n/messages.properties +1 -0
- package/libx/_runtime/common/utils/cqn.js +5 -0
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +7 -1
- package/libx/_runtime/common/utils/keys.js +2 -2
- package/libx/_runtime/common/utils/resolveView.js +2 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
- package/libx/_runtime/common/utils/stream.js +0 -10
- package/libx/_runtime/common/utils/template.js +20 -35
- package/libx/_runtime/db/Service.js +5 -1
- package/libx/_runtime/db/utils/columns.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +14 -2
- package/libx/_runtime/messaging/Outbox.js +7 -5
- package/libx/_runtime/messaging/kafka.js +266 -0
- package/libx/_runtime/messaging/service.js +7 -5
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
- package/libx/common/assert/validation.js +1 -1
- package/libx/odata/index.js +8 -2
- package/libx/odata/middleware/batch.js +340 -0
- package/libx/odata/middleware/create.js +43 -46
- package/libx/odata/middleware/delete.js +27 -15
- package/libx/odata/middleware/error.js +6 -5
- package/libx/odata/middleware/metadata.js +16 -15
- package/libx/odata/middleware/operation.js +107 -59
- package/libx/odata/middleware/parse.js +15 -7
- package/libx/odata/middleware/read.js +150 -24
- package/libx/odata/middleware/service-document.js +17 -6
- package/libx/odata/middleware/stream.js +34 -17
- package/libx/odata/middleware/update.js +123 -87
- package/libx/odata/parse/afterburner.js +131 -28
- package/libx/odata/parse/cqn2odata.js +1 -1
- package/libx/odata/parse/grammar.peggy +4 -5
- package/libx/odata/parse/multipartToJson.js +163 -0
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/index.js +29 -47
- package/libx/odata/utils/path.js +72 -0
- package/libx/odata/utils/result.js +123 -20
- package/package.json +1 -1
- 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
|
-
|
|
10
|
-
const
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
req.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
26
|
-
|
|
29
|
+
router4(srv) {
|
|
30
|
+
const router = super.router4(srv)
|
|
31
|
+
const jsonBodyParser = express.json()
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
}
|
package/lib/srv/srv-api.js
CHANGED
|
@@ -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
|
|
70
|
-
|
|
71
|
-
if (!
|
|
72
|
-
if (
|
|
73
|
-
else
|
|
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.
|
package/lib/srv/srv-dispatch.js
CHANGED
|
@@ -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 (
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
204
|
+
exports._is_done = done => new cds.error (
|
|
205
|
+
`Transaction is ${done}, no subsequent .run allowed, without prior .begin`,
|
|
206
|
+
{ code: 'TRANSACTION_CLOSED' }
|
|
207
|
+
)
|
package/lib/utils/cds-utils.js
CHANGED
|
@@ -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
|
-
|
|
191
|
+
log (
|
|
191
192
|
yellow,
|
|
192
193
|
'\n------------------------------------------------------------------------------',
|
|
193
194
|
'\nDEPRECATED:', old, '\n',
|
|
194
|
-
'\n ', kind
|
|
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
|
-
|
|
203
|
+
log (
|
|
203
204
|
yellow,
|
|
204
205
|
'\n------------------------------------------------------------------------------',
|
|
205
206
|
'\nDEPRECATED:', old, '\n',
|
|
206
|
-
'\n ', kind
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
-
|
|
41
|
+
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
2
|
const { isCustomOperation } = require('./request')
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
117
|
-
|
|
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
|
|
package/libx/_runtime/cds.js
CHANGED
|
@@ -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 (
|
|
10
|
-
cds
|
|
11
|
-
|
|
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 =
|
|
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 } })
|
|
@@ -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
|
-
|
|
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') {
|