@sap/cds 6.0.3 → 6.1.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 +165 -18
- package/apis/cds.d.ts +11 -7
- package/apis/log.d.ts +46 -0
- package/apis/ql.d.ts +72 -15
- package/bin/build/buildTaskHandler.js +5 -2
- package/bin/build/constants.js +4 -1
- package/bin/build/provider/buildTaskHandlerEdmx.js +11 -39
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +14 -34
- package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
- package/bin/build/provider/buildTaskProviderInternal.js +22 -14
- package/bin/build/provider/hana/index.js +12 -9
- package/bin/build/provider/java/index.js +18 -8
- package/bin/build/provider/mtx/index.js +7 -4
- package/bin/build/provider/mtx/resourcesTarBuilder.js +60 -35
- package/bin/build/provider/mtx-extension/index.js +57 -0
- package/bin/build/provider/mtx-sidecar/index.js +46 -18
- package/bin/build/provider/nodejs/index.js +34 -13
- package/bin/deploy/to-hana/cfUtil.js +7 -2
- package/bin/deploy/to-hana/hana.js +20 -25
- package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
- package/bin/serve.js +7 -4
- package/lib/compile/{index.js → cds-compile.js} +0 -0
- package/lib/compile/extend.js +15 -5
- package/lib/compile/minify.js +1 -15
- package/lib/compile/parse.js +1 -1
- package/lib/compile/resolve.js +2 -2
- package/lib/compile/to/srvinfo.js +6 -4
- package/lib/{deploy.js → dbs/cds-deploy.js} +9 -8
- package/lib/env/{index.js → cds-env.js} +1 -17
- package/lib/env/{requires.js → cds-requires.js} +24 -3
- package/lib/env/defaults.js +7 -1
- package/lib/env/schemas/cds-package.json +11 -0
- package/lib/env/schemas/cds-rc.json +614 -0
- package/lib/index.js +19 -16
- package/lib/log/{errors.js → cds-error.js} +1 -1
- package/lib/log/{index.js → cds-log.js} +0 -0
- package/lib/ql/Query.js +9 -3
- package/lib/ql/SELECT.js +2 -2
- package/lib/ql/{index.js → cds-ql.js} +0 -9
- package/lib/req/context.js +49 -17
- package/lib/req/locale.js +5 -1
- package/lib/{serve → srv}/adapters.js +23 -19
- package/lib/{connect → srv}/bindings.js +0 -0
- package/lib/{connect/index.js → srv/cds-connect.js} +1 -1
- package/lib/{serve/index.js → srv/cds-serve.js} +1 -1
- package/lib/{serve → srv}/factory.js +1 -1
- package/lib/{serve/Service-api.js → srv/srv-api.js} +22 -6
- package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +13 -8
- package/lib/{serve/Service-handlers.js → srv/srv-handlers.js} +10 -0
- package/lib/{serve/Service-methods.js → srv/srv-methods.js} +10 -8
- package/lib/srv/srv-models.js +207 -0
- package/lib/{serve/Transaction.js → srv/srv-tx.js} +57 -40
- package/lib/utils/{tests.js → cds-test.js} +2 -2
- package/lib/utils/cds-utils.js +146 -0
- package/lib/utils/index.js +2 -145
- package/lib/utils/jest.js +43 -0
- package/lib/utils/resources/index.js +15 -25
- package/lib/utils/resources/tar.js +18 -41
- package/libx/_runtime/auth/index.js +14 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +7 -19
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +6 -19
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +38 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -3
- package/libx/_runtime/cds-services/services/utils/differ.js +4 -0
- package/libx/_runtime/cds-services/util/errors.js +1 -29
- package/libx/_runtime/common/i18n/messages.properties +2 -1
- package/libx/_runtime/common/perf/index.js +10 -15
- package/libx/_runtime/common/utils/binary.js +3 -4
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +0 -1
- package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
- package/libx/_runtime/common/utils/resolveView.js +1 -1
- package/libx/_runtime/common/utils/template.js +1 -1
- package/libx/_runtime/db/Service.js +2 -14
- package/libx/_runtime/db/expand/expandCQNToJoin.js +28 -25
- package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
- package/libx/_runtime/db/generic/input.js +8 -1
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
- package/libx/_runtime/extensibility/activate.js +47 -47
- package/libx/_runtime/extensibility/add.js +22 -13
- package/libx/_runtime/extensibility/addExtension.js +17 -13
- package/libx/_runtime/extensibility/defaults.js +25 -30
- package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
- package/libx/_runtime/extensibility/linter/allowlist_checker.js +373 -0
- package/libx/_runtime/extensibility/linter/annotations_checker.js +113 -0
- package/libx/_runtime/extensibility/linter/checker_base.js +20 -0
- package/libx/_runtime/extensibility/linter/namespace_checker.js +180 -0
- package/libx/_runtime/extensibility/linter.js +32 -0
- package/libx/_runtime/extensibility/push.js +77 -20
- package/libx/_runtime/extensibility/service.js +29 -12
- package/libx/_runtime/extensibility/token.js +56 -0
- package/libx/_runtime/extensibility/utils.js +8 -6
- package/libx/_runtime/extensibility/validation.js +6 -9
- package/libx/_runtime/fiori/generic/new.js +0 -11
- package/libx/_runtime/hana/Service.js +0 -1
- package/libx/_runtime/hana/conversion.js +12 -1
- package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +4 -3
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -0
- package/libx/_runtime/hana/pool.js +6 -10
- package/libx/_runtime/hana/search2Contains.js +0 -5
- package/libx/_runtime/hana/search2cqn4sql.js +1 -0
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/service.js +11 -6
- package/libx/_runtime/remote/utils/data.js +5 -0
- package/libx/_runtime/sqlite/Service.js +7 -6
- package/libx/_runtime/sqlite/execute.js +41 -28
- package/libx/odata/afterburner.js +79 -2
- package/libx/odata/cqn2odata.js +9 -7
- package/libx/odata/grammar.pegjs +157 -76
- package/libx/odata/index.js +9 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +39 -5
- package/libx/rest/RestAdapter.js +3 -7
- package/libx/rest/middleware/delete.js +4 -5
- package/libx/rest/middleware/parse.js +3 -2
- package/package.json +3 -3
- package/server.js +1 -1
- package/srv/extensibility-service.cds +6 -3
- package/srv/model-provider.cds +3 -1
- package/srv/model-provider.js +84 -104
- package/srv/mtx.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +0 -240
|
@@ -57,7 +57,7 @@ const _log = (req, challenges) => {
|
|
|
57
57
|
LOG.debug(`User "${req.user.id}" request URL`, req.url, '\n', ...challengesLog)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const
|
|
60
|
+
const cap_auth_callback = (req, res, next, internalError, user, challenges) => {
|
|
61
61
|
// An internal error occurs during the authentication process
|
|
62
62
|
if (internalError) {
|
|
63
63
|
// REVISIT: What to do? Security log?
|
|
@@ -83,7 +83,7 @@ const _authCallback = (req, res, next, internalError, user, challenges) => {
|
|
|
83
83
|
|
|
84
84
|
const _mountCustomAuth = (srv, app, config) => {
|
|
85
85
|
const impl = cds.resolve(config.impl)
|
|
86
|
-
app.use(
|
|
86
|
+
app.use(_require(impl))
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
const _mountMockAuth = (srv, app, strategy, config) => {
|
|
@@ -92,12 +92,12 @@ const _mountMockAuth = (srv, app, strategy, config) => {
|
|
|
92
92
|
? new (require('./strategies/dummy'))()
|
|
93
93
|
: new (require('./strategies/mock'))(config, `mock_${srv.name}`)
|
|
94
94
|
|
|
95
|
-
app.use(
|
|
95
|
+
app.use(function cap_auth(req, res, next) {
|
|
96
96
|
let user, challenge
|
|
97
97
|
impl.success = arg => (user = arg)
|
|
98
98
|
impl.fail = arg => (challenge = arg)
|
|
99
99
|
impl.authenticate(req)
|
|
100
|
-
|
|
100
|
+
cap_auth_callback(req, res, next, undefined, user, [challenge])
|
|
101
101
|
})
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -119,10 +119,10 @@ const _mountPassportAuth = (srv, app, strategy, config) => {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
// authenticate
|
|
122
|
-
app.use(
|
|
123
|
-
app.use(
|
|
122
|
+
app.use(passport.initialize())
|
|
123
|
+
app.use((req, res, next) => {
|
|
124
124
|
const options = { session: false, failWithError: true }
|
|
125
|
-
const callback =
|
|
125
|
+
const callback = cap_auth_callback.bind(undefined, req, res, next)
|
|
126
126
|
passport.authenticate(strategy === 'jwt' ? 'JWT' : strategy, options, callback)(req, res, next)
|
|
127
127
|
})
|
|
128
128
|
}
|
|
@@ -131,7 +131,9 @@ const _mountPassportAuth = (srv, app, strategy, config) => {
|
|
|
131
131
|
* export authentication middleware
|
|
132
132
|
*/
|
|
133
133
|
// eslint-disable-next-line complexity
|
|
134
|
-
module.exports = (srv,
|
|
134
|
+
module.exports = (srv, options = srv.options) => {
|
|
135
|
+
const handlers = [],
|
|
136
|
+
app = { use: h => handlers.push(h) }
|
|
135
137
|
// NOTE: options.auth is not an official API
|
|
136
138
|
let config = 'auth' in options ? options.auth : cds.env.requires.auth
|
|
137
139
|
if (!config) {
|
|
@@ -147,7 +149,7 @@ module.exports = (srv, app, options) => {
|
|
|
147
149
|
LOG._warn && LOG.warn(`No authentication configured. This is not recommended in production.`)
|
|
148
150
|
}
|
|
149
151
|
// no auth wanted > return
|
|
150
|
-
return
|
|
152
|
+
return handlers
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
// cds.env.requires.auth = { kind: 'xsuaa-auth' } was briefly documented on capire -> also support
|
|
@@ -181,11 +183,12 @@ module.exports = (srv, app, options) => {
|
|
|
181
183
|
(process.env.NODE_ENV === 'production' && config.credentials && config.restrict_all_services)
|
|
182
184
|
) {
|
|
183
185
|
if (!logged) LOG._debug && LOG.debug(`Enforcing authenticated users for all services`)
|
|
184
|
-
app.use(
|
|
186
|
+
app.use(cap_enforce_login)
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
// so we don't log the same stuff multiple times
|
|
188
190
|
logged = true
|
|
191
|
+
return handlers
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
const _strategy4 = config => {
|
|
@@ -196,7 +199,7 @@ const _strategy4 = config => {
|
|
|
196
199
|
throw new Error(`Authentication kind "${config.kind}" is not supported`)
|
|
197
200
|
}
|
|
198
201
|
|
|
199
|
-
const
|
|
202
|
+
const cap_enforce_login = (req, res, next) => {
|
|
200
203
|
if (req.user && req.user.is('authenticated-user')) return next() // pass if user is authenticated
|
|
201
204
|
if (!req.user || req.user._is_anonymous) {
|
|
202
205
|
if (req.user && req.user._challenges) res.set('WWW-Authenticate', req.user._challenges.join(';'))
|
|
@@ -107,8 +107,7 @@ class OData {
|
|
|
107
107
|
constructor(edm, csn, options = {}) {
|
|
108
108
|
this._validateEdm(edm)
|
|
109
109
|
this._options = options
|
|
110
|
-
this.
|
|
111
|
-
this._createOdataService(edm)
|
|
110
|
+
this._createOdataService(edm, csn)
|
|
112
111
|
}
|
|
113
112
|
|
|
114
113
|
_validateEdm(edm) {
|
|
@@ -118,7 +117,7 @@ class OData {
|
|
|
118
117
|
}
|
|
119
118
|
}
|
|
120
119
|
|
|
121
|
-
_createOdataService(edm) {
|
|
120
|
+
_createOdataService(edm, csn) {
|
|
122
121
|
const ServiceFactory = require('./okra/odata-server').ServiceFactory
|
|
123
122
|
|
|
124
123
|
// skip okra's validation in production or implicitly for w4 and x4
|
|
@@ -133,7 +132,7 @@ class OData {
|
|
|
133
132
|
effective.odata.proxies ||
|
|
134
133
|
effective.odata.xrefs
|
|
135
134
|
|
|
136
|
-
this._odataService = ServiceFactory.createService(edm, _config(edm,
|
|
135
|
+
this._odataService = ServiceFactory.createService(edm, _config(edm, csn, this._options)).trust(isTrusted)
|
|
137
136
|
|
|
138
137
|
// will be added to express app like app.use('/base/path/', service) and odata-v4 wants app.use('/', service) if basePath is set
|
|
139
138
|
this._odataService.setBasePath('/')
|
|
@@ -165,8 +164,7 @@ class OData {
|
|
|
165
164
|
req,
|
|
166
165
|
res
|
|
167
166
|
} = data
|
|
168
|
-
|
|
169
|
-
const tx = (txs[odataContext.id] = cdsService.tx({ user, req, res, _model: cdsService.model }))
|
|
167
|
+
const tx = (txs[odataContext.id] = cdsService.tx({ user, req, res }))
|
|
170
168
|
cds.context = tx.context
|
|
171
169
|
// for collecting results and errors
|
|
172
170
|
data.results = data.results || {}
|
|
@@ -183,19 +181,8 @@ class OData {
|
|
|
183
181
|
if (errors) {
|
|
184
182
|
// rollback without errors to not trigger srv.on('error') with array
|
|
185
183
|
await tx.rollback()
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const failedRequests = {}
|
|
189
|
-
|
|
190
|
-
for (const e of errors) {
|
|
191
|
-
const { error: err, req } = e
|
|
192
|
-
for (const each of cdsService._handlers._error) each.handler.call(cdsService, err, req)
|
|
193
|
-
const requestId = req._.odataReq.getOdataRequestId()
|
|
194
|
-
const { error, statusCode } = normalizeError(err, req)
|
|
195
|
-
failedRequests[requestId] = Object.assign(error, { statusCode })
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
done(new Error(`Atomicity group "${odataContext.id}" failed`), { failedRequests })
|
|
184
|
+
|
|
185
|
+
done()
|
|
199
186
|
return
|
|
200
187
|
}
|
|
201
188
|
|
|
@@ -226,6 +213,7 @@ class OData {
|
|
|
226
213
|
this._odataService.use(DATA_DELETE_HANDLER, _delete(cdsService))
|
|
227
214
|
|
|
228
215
|
this._odataService.use(ACTION_EXECUTE_HANDLER, _action(cdsService))
|
|
216
|
+
return this
|
|
229
217
|
}
|
|
230
218
|
|
|
231
219
|
/**
|
|
@@ -163,12 +163,9 @@ class ODataRequest extends cds.Request {
|
|
|
163
163
|
*/
|
|
164
164
|
const { user } = req
|
|
165
165
|
|
|
166
|
-
// REVISIT: _model should not be necessary
|
|
167
|
-
const _model = service.model
|
|
168
|
-
|
|
169
166
|
// REVISIT: public API for query options (express style req.query already in use)?
|
|
170
167
|
const _queryOptions = odataReq.getQueryOptions()
|
|
171
|
-
super({ event, target, data, query, user, method, headers, req, res,
|
|
168
|
+
super({ event, target, data, query, user, method, headers, req, res, _queryOptions })
|
|
172
169
|
}
|
|
173
170
|
|
|
174
171
|
/*
|
|
@@ -77,10 +77,7 @@ const action = service => {
|
|
|
77
77
|
} catch (e) {
|
|
78
78
|
err = e
|
|
79
79
|
|
|
80
|
-
if (changeset) {
|
|
81
|
-
// for passing into rollback
|
|
82
|
-
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
83
|
-
} else {
|
|
80
|
+
if (!changeset) {
|
|
84
81
|
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
85
82
|
await tx.rollback(e).catch(() => {})
|
|
86
83
|
}
|
|
@@ -63,10 +63,7 @@ const create = service => {
|
|
|
63
63
|
} catch (e) {
|
|
64
64
|
err = e
|
|
65
65
|
|
|
66
|
-
if (changeset) {
|
|
67
|
-
// for passing into rollback
|
|
68
|
-
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
69
|
-
} else {
|
|
66
|
+
if (!changeset) {
|
|
70
67
|
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
71
68
|
await tx.rollback(e).catch(() => {})
|
|
72
69
|
}
|
|
@@ -44,10 +44,7 @@ const del = service => {
|
|
|
44
44
|
} catch (e) {
|
|
45
45
|
err = e
|
|
46
46
|
|
|
47
|
-
if (changeset) {
|
|
48
|
-
// for passing into rollback
|
|
49
|
-
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
50
|
-
} else {
|
|
47
|
+
if (!changeset) {
|
|
51
48
|
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
52
49
|
await tx.rollback(e).catch(() => {})
|
|
53
50
|
}
|
|
@@ -114,13 +114,13 @@ const getErrorHandler = (crashOnError = true, srv) => {
|
|
|
114
114
|
|
|
115
115
|
// invoke srv.on('error', function (err, req) { ... }) here in special situations
|
|
116
116
|
// REVISIT: if for compat reasons, remove once cds^5.1
|
|
117
|
-
if (srv._handlers._error) {
|
|
117
|
+
if (srv._handlers._error.length) {
|
|
118
118
|
let ctx = cds.context
|
|
119
119
|
if (!ctx) {
|
|
120
120
|
// > error before req was dispatched
|
|
121
121
|
ctx = new cds.Request({ req, res: req.res, user: req.user || new cds.User.Anonymous() })
|
|
122
122
|
for (const each of srv._handlers._error) each.handler.call(srv, err, ctx)
|
|
123
|
-
} else
|
|
123
|
+
} else {
|
|
124
124
|
// > error after req was dispatched, e.g., serialization error in okra
|
|
125
125
|
for (const each of srv._handlers._error) each.handler.call(srv, err, ctx)
|
|
126
126
|
}
|
|
@@ -5,10 +5,6 @@ const { toODataResult } = require('../utils/result')
|
|
|
5
5
|
const { normalizeError } = require('../../../../common/error/frontend')
|
|
6
6
|
const getError = require('../../../../common/error')
|
|
7
7
|
|
|
8
|
-
const _getMetadata4Tenant = async (tenant, locale, service) => {
|
|
9
|
-
return await cds.mtx.getEdmx(tenant, service.name, locale)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
8
|
/**
|
|
13
9
|
* Provide localized metadata handler.
|
|
14
10
|
*
|
|
@@ -23,21 +19,12 @@ const metadata = service => {
|
|
|
23
19
|
const locale = odataRes.getContract().getLocale()
|
|
24
20
|
|
|
25
21
|
try {
|
|
26
|
-
let edmx
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (!edmx) {
|
|
33
|
-
edmx = cds.localize(
|
|
34
|
-
service.model,
|
|
35
|
-
locale,
|
|
36
|
-
// REVISIT: we could cache this in a weak map
|
|
37
|
-
cds.compile.to.edmx(service.model, { service: service.definition.name })
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
22
|
+
let edmx = cds.localize(
|
|
23
|
+
service.model,
|
|
24
|
+
locale,
|
|
25
|
+
// REVISIT: we could cache this in model._cached
|
|
26
|
+
cds.compile.to.edmx(service.model, { service: service.definition.name })
|
|
27
|
+
)
|
|
41
28
|
return next(null, toODataResult(edmx))
|
|
42
29
|
} catch (e) {
|
|
43
30
|
if (LOG._error) {
|
|
@@ -484,10 +484,7 @@ const read = service => {
|
|
|
484
484
|
} catch (e) {
|
|
485
485
|
err = e
|
|
486
486
|
|
|
487
|
-
if (changeset) {
|
|
488
|
-
// for passing into rollback
|
|
489
|
-
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
490
|
-
} else {
|
|
487
|
+
if (!changeset) {
|
|
491
488
|
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
492
489
|
await tx.rollback(e).catch(() => {})
|
|
493
490
|
}
|
|
@@ -6,7 +6,7 @@ module.exports = srv => {
|
|
|
6
6
|
const requires = getRequiresAsArray(srv.definition)
|
|
7
7
|
const restricted = isRestricted(srv)
|
|
8
8
|
|
|
9
|
-
return (odataReq, odataRes, next)
|
|
9
|
+
return function ODataRequestHandler(odataReq, odataRes, next) {
|
|
10
10
|
const req = odataReq.getBatchApplicationData()
|
|
11
11
|
? odataReq.getBatchApplicationData().req
|
|
12
12
|
: odataReq.getIncomingRequest()
|
|
@@ -174,10 +174,7 @@ const update = service => {
|
|
|
174
174
|
} catch (e) {
|
|
175
175
|
err = e
|
|
176
176
|
|
|
177
|
-
if (changeset) {
|
|
178
|
-
// for passing into rollback
|
|
179
|
-
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
180
|
-
} else {
|
|
177
|
+
if (!changeset) {
|
|
181
178
|
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
182
179
|
await tx.rollback(e).catch(() => {})
|
|
183
180
|
}
|
|
@@ -68,8 +68,6 @@ const MULTI_LINE_STRING_VALIDATION = new RegExp('^' + SRID + 'MultiLineString\\(
|
|
|
68
68
|
const MULTI_POLYGON_VALIDATION = new RegExp('^' + SRID + 'MultiPolygon\\((' + MULTI_POLYGON + ')\\)$', 'i')
|
|
69
69
|
const COLLECTION_VALIDATION = new RegExp('^' + SRID + 'Collection\\((' + MULTI_GEO_LITERAL + ')\\)$', 'i')
|
|
70
70
|
|
|
71
|
-
const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}={2})$/
|
|
72
|
-
|
|
73
71
|
function _getBase64(val) {
|
|
74
72
|
if (isInvalidBase64string(val)) return
|
|
75
73
|
// convert url-safe to standard base64
|
|
@@ -40,8 +40,10 @@ class DeserializerFactory {
|
|
|
40
40
|
let additionalInformation = { hasDelta: false }
|
|
41
41
|
const deserializer = new ResourceJsonDeserializer(edm, jsonContentTypeInfo)
|
|
42
42
|
return (edmObject, value) => {
|
|
43
|
+
const body = deserializer[name](edmObject, value, expand, additionalInformation)
|
|
44
|
+
|
|
43
45
|
return {
|
|
44
|
-
body
|
|
46
|
+
body,
|
|
45
47
|
expand,
|
|
46
48
|
additionalInformation
|
|
47
49
|
}
|
|
@@ -1,7 +1,41 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { alias2ref } = require('../../../common/utils/csn') // REVISIT: eliminate that
|
|
2
|
+
const cds = require('../../../cds')
|
|
3
|
+
const OData = require('./OData')
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
/**
|
|
6
|
+
* This is the express handler for a specific OData endpoint.
|
|
7
|
+
* Note: the same service can be served at different endpoints.
|
|
8
|
+
*/
|
|
9
|
+
module.exports = srv => {
|
|
10
|
+
const okra = new OkraAdapter(srv)
|
|
11
|
+
return okra.process.bind(okra)
|
|
5
12
|
}
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
function OkraAdapter(srv, model = srv.model) {
|
|
15
|
+
const edm = cds.compile.to.edm(model, { service: srv.definition?.name || srv.name })
|
|
16
|
+
alias2ref(srv, edm) // REVISIT: eliminate that -> done again and again -> search for _alias2ref
|
|
17
|
+
return new OData(edm, model, srv.options).addCDSServiceToChannel(srv)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//////////////////////////////////////////////////////////////////////////////
|
|
21
|
+
//
|
|
22
|
+
// REVISIT: Move to ExtensibilityService
|
|
23
|
+
//
|
|
24
|
+
if (cds.mtx || cds.requires.extensibility || cds.requires.toggles)
|
|
25
|
+
module.exports = srv => {
|
|
26
|
+
const id = `${++unique} - ${srv.path}` // REVISIT: this is to allow running multiple express apps serving same endpoints, as done by some questionable tests
|
|
27
|
+
return function ODataAdapter(req, res) {
|
|
28
|
+
const model = cds.context?.model || srv.model
|
|
29
|
+
if (!model._cached) Object.defineProperty(model, '_cached', { value: {} })
|
|
30
|
+
|
|
31
|
+
// Note: cache is attached to model cache so they get disposed when models are evicted from cache
|
|
32
|
+
let adapters = model._cached._odata_adapters || (model._cached._odata_adapters = {})
|
|
33
|
+
let okra = adapters[id]
|
|
34
|
+
if (!okra) {
|
|
35
|
+
const _srv = { __proto__: srv, _real_srv: srv, model } // REVISIT: we need to do that better in new adapters
|
|
36
|
+
okra = adapters[id] = new OkraAdapter(_srv, model)
|
|
37
|
+
}
|
|
38
|
+
return okra.process(req, res)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
let unique = 0
|
|
@@ -22,10 +22,8 @@ const _isNoAccessError = e => Number(e.code) === 403 || Number(e.code) === 401
|
|
|
22
22
|
const _isNotFoundError = e => Number(e.code) === 404
|
|
23
23
|
const _isEntityNotReadableError = e => Number(e.code) === 405
|
|
24
24
|
|
|
25
|
-
const _handleReadError =
|
|
25
|
+
const _handleReadError = err => {
|
|
26
26
|
if (!(_isNoAccessError(err) || _isEntityNotReadableError(err) || _isNotFoundError(err))) throw err
|
|
27
|
-
const log = Object.assign(err, { level: 'ERROR', message: normalizeError(err, req).error.message })
|
|
28
|
-
process.env.NODE_ENV !== 'production' && LOG._warn && LOG.warn(log)
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
const _getOperationQueryColumns = urlQueryOptions => {
|
|
@@ -18,6 +18,10 @@ module.exports = class Differ {
|
|
|
18
18
|
_createSelectColumnsForDelete(entity) {
|
|
19
19
|
const columns = []
|
|
20
20
|
for (const element of Object.values(entity.elements)) {
|
|
21
|
+
// Don't take into account virtual or computed properties to make the diff result
|
|
22
|
+
// consistent with the ones for UPDATE/CREATE (where we don't have access to that
|
|
23
|
+
// information).
|
|
24
|
+
if (!element.key && (element.virtual || element['@Core.Computed'])) continue
|
|
21
25
|
if (element.isComposition) {
|
|
22
26
|
if (element._target._hasPersistenceSkip) continue
|
|
23
27
|
columns.push({
|
|
@@ -9,35 +9,7 @@ const getFeatureNotSupportedError = message => {
|
|
|
9
9
|
return getError(501, `Feature is not supported: ${message}`)
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const getAuditLogNotWrittenError = (rootCauseError, phase, event) => {
|
|
13
|
-
const errorMessage =
|
|
14
|
-
!phase || event === 'READ' ? 'Audit log could not be written' : `Audit log could not be written ${phase}`
|
|
15
|
-
const error = new Error(errorMessage)
|
|
16
|
-
error.rootCause = rootCauseError
|
|
17
|
-
return error
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const hasBeenCalledError = (method, query) => {
|
|
21
|
-
return new Error(`Method ${method} has been called before. Invalid CQN: ${JSON.stringify(query)}`)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const unexpectedFunctionCallError = (functionName, expectedFunction) => {
|
|
25
|
-
return new Error(`Cannot build CQN object. Invalid call of "${functionName}" before "${expectedFunction}"`)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const invalidFunctionArgumentError = (statement, arg) => {
|
|
29
|
-
const details = JSON.stringify(arg, (key, value) => (value === undefined ? '__undefined__' : value)).replace(
|
|
30
|
-
/"__undefined__"/g,
|
|
31
|
-
'undefined'
|
|
32
|
-
)
|
|
33
|
-
return new Error(`Cannot build ${statement} statement. Invalid data provided: ${details}`)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
12
|
module.exports = {
|
|
37
13
|
getModelNotDefinedError,
|
|
38
|
-
getFeatureNotSupportedError
|
|
39
|
-
getAuditLogNotWrittenError,
|
|
40
|
-
hasBeenCalledError,
|
|
41
|
-
unexpectedFunctionCallError,
|
|
42
|
-
invalidFunctionArgumentError
|
|
14
|
+
getFeatureNotSupportedError
|
|
43
15
|
}
|
|
@@ -74,8 +74,9 @@ ENTITY_IS_NOT_CRUD=Entity "{0}" is not {1}
|
|
|
74
74
|
ENTITY_IS_NOT_CRUD_VIA_NAVIGATION=Entity "{0}" is not {1} via navigation "{2}"
|
|
75
75
|
ENTITY_IS_AUTOEXPOSED=Entity "{0}" is not explicitly exposed as part of the service
|
|
76
76
|
EXPAND_IS_RESTRICTED=Navigation property "{0}" is not allowed for expand operation
|
|
77
|
-
EXPAND_COUNT_UNSUPPORTED="
|
|
77
|
+
EXPAND_COUNT_UNSUPPORTED="/$count" is not supported for expand operation
|
|
78
78
|
ORDERBY_LAMBDA_UNSUPPORTED="$orderby" does not support lambda
|
|
79
|
+
EXPAND_APPLY_UNSUPPORTED="$apply" is not supported for expand operation
|
|
79
80
|
|
|
80
81
|
# rest protocol adapter
|
|
81
82
|
INVALID_RESOURCE="{0}" is not a valid resource
|
|
@@ -4,21 +4,16 @@ const _statisticsRequested = req =>
|
|
|
4
4
|
(req.query && req.query['sap-statistics'] === 'true') ||
|
|
5
5
|
(req.headers && req.headers['sap-statistics'] === 'true' && (!req.query || !req.query['sap-statistics']))
|
|
6
6
|
|
|
7
|
-
module.exports =
|
|
8
|
-
if (
|
|
9
|
-
else app._perf_measured = true
|
|
7
|
+
module.exports = function sap_statistics(req, res, next) {
|
|
8
|
+
if (!_statisticsRequested(req)) return next()
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const t0 = performance.now()
|
|
11
|
+
const { writeHead } = res
|
|
12
|
+
res.writeHead = function (...args) {
|
|
13
|
+
const total = Number((performance.now() - t0) / 1000).toFixed(2)
|
|
14
|
+
if (res.statusCode < 400) res.setHeader('sap-statistics', `total=${total}`)
|
|
15
|
+
writeHead.call(this, ...args)
|
|
16
|
+
}
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
const { writeHead } = res
|
|
16
|
-
res.writeHead = function (...args) {
|
|
17
|
-
const total = Number((performance.now() - t0) / 1000).toFixed(2)
|
|
18
|
-
if (res.statusCode < 400) res.setHeader('sap-statistics', `total=${total}`)
|
|
19
|
-
writeHead.call(this, ...args)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
next()
|
|
23
|
-
})
|
|
18
|
+
next()
|
|
24
19
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
const getTemplate = require('./template')
|
|
2
2
|
const templateProcessor = require('./templateProcessor')
|
|
3
3
|
|
|
4
|
-
const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}={0,1}|[A-Za-z0-9+/]{2}={0,2})$/
|
|
5
|
-
|
|
6
4
|
// convert the standard base64 encoding to the URL-safe variant
|
|
7
5
|
const toBase64url = value =>
|
|
8
6
|
(Buffer.isBuffer(value) ? value.toString('base64') : value).replace(/\//g, '_').replace(/\+/g, '-')
|
|
@@ -17,12 +15,13 @@ const isInvalidBase64string = value => {
|
|
|
17
15
|
if (Buffer.isBuffer(value)) return // ok
|
|
18
16
|
|
|
19
17
|
// convert to standard base64 string; let it crash if typeof value !== 'string'
|
|
20
|
-
const
|
|
18
|
+
const base64value = value.replace(/_/g, '/').replace(/-/g, '+')
|
|
21
19
|
const normalized = normalizeBase64string(value)
|
|
22
20
|
|
|
23
21
|
// example of invalid base64 string --> 'WTGTdDsD/k21LnFRb+uNcAi=' <-- '...i=' must be '...g='
|
|
24
22
|
// see https://datatracker.ietf.org/doc/html/rfc4648#section-4
|
|
25
|
-
|
|
23
|
+
if (base64value.replace(/=/g, '') !== normalized.replace(/=/g, '')) return true
|
|
24
|
+
return base64value.length > normalized.length
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
const _picker = element => {
|
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
|
|
2
2
|
|
|
3
|
-
const traverseFroms = (cqn, cb) => {
|
|
3
|
+
const traverseFroms = (cqn, cb, aliasForSet) => {
|
|
4
4
|
while (cqn.SELECT) cqn = cqn.SELECT.from
|
|
5
5
|
|
|
6
6
|
// Do the most likely first -> {ref}
|
|
7
7
|
if (cqn.ref) {
|
|
8
|
-
return cb(cqn)
|
|
8
|
+
return cb(cqn, aliasForSet)
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
if (cqn.SET) {
|
|
12
|
-
|
|
12
|
+
// if a union has an alias, we should use it for the columns we get out of the union
|
|
13
|
+
return cqn.SET.args.map(a => traverseFroms(a, cb, cqn.as))
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
if (cqn.join) {
|
|
16
|
-
return cqn.args.map(a => traverseFroms(a, cb))
|
|
17
|
+
return cqn.args.map(a => traverseFroms(a, cb, aliasForSet))
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
const getEntityNameFromCQN = cqn => {
|
|
21
22
|
const res = []
|
|
22
|
-
traverseFroms(cqn,
|
|
23
|
+
traverseFroms(cqn, (from, aliasForSet) =>
|
|
24
|
+
res.push({ entityName: from.ref[0].id || from.ref[0], alias: aliasForSet || from.as })
|
|
25
|
+
)
|
|
23
26
|
return res.length === 1 ? res[0] : res.find(n => n.entityName !== 'DRAFT.DraftAdministrativeData') || {}
|
|
24
27
|
}
|
|
25
28
|
|
|
@@ -612,7 +612,7 @@ const _newQuery = (query, event, model, service) => {
|
|
|
612
612
|
}[event]
|
|
613
613
|
const newQuery = Object.create(query)
|
|
614
614
|
const transitions = _entityTransitionsForTarget(query[event][_prop], model, service)
|
|
615
|
-
newQuery[event] = (transitions[0] && _func(newQuery, transitions, service)) || { ...query[event] }
|
|
615
|
+
newQuery[event] = (transitions?.[0] && _func(newQuery, transitions, service)) || { ...query[event] }
|
|
616
616
|
return newQuery
|
|
617
617
|
}
|
|
618
618
|
|
|
@@ -134,7 +134,7 @@ const getCache = (anything, cache, newCacheFn) => {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
module.exports = (usecase, tx, target, ...args) => {
|
|
137
|
-
// get model first as it may be added to tx (cf. "_ensureModel")
|
|
137
|
+
// get model first as it may be added to tx (cf. "_ensureModel") // REVISIT: _ensureModel is gone
|
|
138
138
|
const model = tx.model
|
|
139
139
|
if (!model) return
|
|
140
140
|
|
|
@@ -24,28 +24,16 @@ class DatabaseService extends cds.Service {
|
|
|
24
24
|
this[`_${each}`] = generic[each]
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// REVISIT: ensures tenant-aware this.model if this is a transaction -> this should be fixed in mtx integration, not here
|
|
28
|
-
this._ensureModel = function (req) {
|
|
29
|
-
if (this.context) {
|
|
30
|
-
// if the tx was initiated in messaging, then this.context._model is not unfolded
|
|
31
|
-
// -> use this.context._model._4odata if present
|
|
32
|
-
const { _model } = this.context
|
|
33
|
-
if (_model) this.model = _model._4odata || _model
|
|
34
|
-
else this.model = req._model
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
this._ensureModel._initial = true
|
|
38
|
-
|
|
39
27
|
// REVISIT: how to generic handler registration?
|
|
40
28
|
}
|
|
41
29
|
|
|
42
30
|
/** Database services don't support custom-defined operations */
|
|
43
|
-
operations() {
|
|
31
|
+
get operations() {
|
|
44
32
|
return []
|
|
45
33
|
}
|
|
46
34
|
|
|
47
35
|
/** Database services don't support custom-defined events */
|
|
48
|
-
events() {
|
|
36
|
+
get events() {
|
|
49
37
|
return []
|
|
50
38
|
}
|
|
51
39
|
|