@sap/cds 6.1.2 → 6.2.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 +92 -8
- package/apis/cds.d.ts +18 -6
- package/apis/connect.d.ts +1 -1
- package/apis/cqn.d.ts +1 -1
- package/apis/log.d.ts +23 -5
- package/apis/ql.d.ts +128 -61
- package/apis/services.d.ts +11 -0
- package/apis/test.d.ts +61 -0
- package/apis/utils.d.ts +15 -0
- package/app/fiori/preview.js +1 -0
- package/bin/build/buildTaskEngine.js +70 -22
- package/bin/build/buildTaskFactory.js +18 -11
- package/bin/build/buildTaskHandler.js +1 -1
- package/bin/build/buildTaskProviderFactory.js +3 -13
- package/bin/build/constants.js +0 -1
- package/bin/build/index.js +14 -6
- package/bin/build/provider/buildTaskHandlerEdmx.js +2 -3
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +2 -2
- package/bin/build/provider/buildTaskHandlerInternal.js +3 -6
- package/bin/build/provider/buildTaskProviderInternal.js +51 -39
- package/bin/build/provider/fiori/index.js +3 -3
- package/bin/build/provider/hana/2migration.js +1 -1
- package/bin/build/provider/hana/index.js +34 -27
- package/bin/build/provider/java/index.js +6 -7
- package/bin/build/provider/mtx/index.js +20 -18
- package/bin/build/provider/mtx/resourcesTarBuilder.js +8 -11
- package/bin/build/provider/mtx-sidecar/index.js +13 -17
- package/bin/build/provider/nodejs/index.js +8 -7
- package/bin/build/util.js +22 -4
- package/bin/cds.js +8 -4
- package/bin/deploy/to-hana/cfUtil.js +53 -18
- package/bin/mtx/in-cds.js +1 -0
- package/bin/serve.js +37 -30
- package/lib/auth/basic-auth.js +33 -0
- package/lib/auth/dummy-auth.js +7 -0
- package/lib/auth/ias-auth.js +2 -0
- package/lib/auth/index.js +31 -0
- package/lib/auth/jwt-auth.js +3 -0
- package/lib/auth/mocked-users.js +72 -0
- package/lib/auth/passport-basic.js +12 -0
- package/lib/auth/passport-digest.js +14 -0
- package/lib/auth/xsuaa-auth.js +3 -0
- package/lib/compile/cds-compile.js +3 -3
- package/lib/compile/to/cdl.js +5 -1
- package/lib/compile/to/edm.js +8 -0
- package/lib/compile/to/gql.js +1 -0
- package/lib/compile/to/json.js +30 -5
- package/lib/compile/to/sql.js +3 -1
- package/lib/core/index.js +5 -1
- package/lib/dbs/cds-deploy.js +36 -6
- package/lib/env/cds-env.js +15 -5
- package/lib/env/cds-requires.js +51 -58
- package/lib/env/defaults.js +1 -0
- package/lib/env/schemas/cds-package.json +4 -0
- package/lib/env/schemas/cds-rc.json +63 -77
- package/lib/i18n/localize.js +16 -5
- package/lib/index.js +9 -4
- package/lib/log/cds-error.js +4 -6
- package/lib/log/cds-log.js +89 -53
- package/lib/log/service/index.js +1 -0
- package/lib/ql/CREATE.js +2 -5
- package/lib/ql/DELETE.js +1 -1
- package/lib/ql/DROP.js +1 -3
- package/lib/ql/INSERT.js +3 -3
- package/lib/ql/Query.js +10 -23
- package/lib/ql/SELECT.js +1 -2
- package/lib/ql/UPDATE.js +2 -2
- package/lib/ql/Whereable.js +7 -15
- package/lib/ql/cds-ql.js +9 -3
- package/lib/req/cds-context.js +11 -3
- package/lib/req/context.js +29 -23
- package/lib/req/locale.js +9 -5
- package/lib/req/request.js +1 -0
- package/lib/req/user.js +2 -1
- package/lib/srv/cds-connect.js +1 -1
- package/lib/srv/cds-serve.js +21 -14
- package/lib/srv/middlewares/cds-context.js +29 -0
- package/lib/srv/middlewares/ctx-model.js +24 -0
- package/lib/srv/middlewares/errors.js +9 -0
- package/lib/srv/middlewares/index.js +22 -0
- package/lib/srv/middlewares/sap-statistics.js +13 -0
- package/lib/srv/middlewares/trace.js +102 -0
- package/lib/srv/protocols/_legacy.js +42 -0
- package/lib/srv/protocols/graphql.js +39 -0
- package/lib/srv/protocols/hcql.js +37 -0
- package/lib/srv/protocols/index.js +86 -0
- package/lib/srv/protocols/odata-v2-proxy.js +3767 -0
- package/lib/srv/protocols/odata-v2.js +26 -0
- package/lib/srv/protocols/odata-v4.js +16 -0
- package/lib/srv/protocols/rest.js +13 -0
- package/lib/srv/srv-api.js +5 -0
- package/lib/srv/srv-models.js +4 -6
- package/lib/utils/axios.js +3 -2
- package/lib/utils/cds-test.js +27 -21
- package/lib/utils/cds-utils.js +19 -20
- package/lib/utils/tar.js +175 -0
- package/libx/_runtime/audit/generic/personal/utils.js +18 -7
- package/libx/_runtime/audit/utils/v2.js +1 -0
- package/libx/_runtime/auth/index.js +4 -0
- package/libx/_runtime/auth/strategies/ias-auth.js +76 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +8 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +15 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +9 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriInfo.js +5 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +12 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/RequestValidator.js +47 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +0 -2
- package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -1
- package/libx/_runtime/cds-services/util/assert.js +7 -0
- package/libx/_runtime/common/aspects/relation.js +1 -1
- package/libx/_runtime/common/composition/data.js +61 -15
- package/libx/_runtime/common/composition/delete.js +0 -1
- package/libx/_runtime/common/composition/insert.js +0 -1
- package/libx/_runtime/common/composition/tree.js +4 -10
- package/libx/_runtime/common/composition/update.js +44 -21
- package/libx/_runtime/common/generic/auth/capabilities.js +8 -10
- package/libx/_runtime/common/generic/crud.js +1 -2
- package/libx/_runtime/common/generic/etag.js +4 -4
- package/libx/_runtime/common/generic/input.js +21 -6
- package/libx/_runtime/common/generic/paging.js +3 -3
- package/libx/_runtime/common/generic/put.js +7 -4
- package/libx/_runtime/common/generic/sorting.js +4 -4
- package/libx/_runtime/common/generic/temporal.js +3 -6
- package/libx/_runtime/common/i18n/messages.properties +0 -7
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +11 -6
- package/libx/_runtime/common/utils/csn.js +0 -28
- package/libx/_runtime/common/utils/draft.js +8 -1
- package/libx/_runtime/common/utils/path.js +7 -1
- package/libx/_runtime/common/utils/propagateForeignKeys.js +122 -0
- package/libx/_runtime/common/utils/resolveView.js +2 -3
- package/libx/_runtime/common/utils/template.js +2 -3
- package/libx/_runtime/db/data-conversion/post-processing.js +3 -44
- package/libx/_runtime/db/generic/input.js +6 -6
- package/libx/_runtime/db/sql-builder/dataTypes.js +4 -0
- package/libx/_runtime/fiori/generic/activate.js +2 -2
- package/libx/_runtime/fiori/generic/before.js +40 -72
- package/libx/_runtime/fiori/generic/cancel.js +2 -2
- package/libx/_runtime/fiori/generic/delete.js +2 -2
- package/libx/_runtime/fiori/generic/edit.js +2 -2
- package/libx/_runtime/fiori/generic/new.js +3 -5
- package/libx/_runtime/fiori/generic/patch.js +49 -43
- package/libx/_runtime/fiori/generic/prepare.js +2 -2
- package/libx/_runtime/fiori/generic/read.js +27 -37
- package/libx/_runtime/fiori/utils/where.js +4 -2
- package/libx/_runtime/hana/Service.js +1 -3
- package/libx/_runtime/hana/conversion.js +3 -0
- package/libx/_runtime/hana/driver.js +33 -3
- package/libx/_runtime/hana/dynatrace.js +1 -0
- package/libx/_runtime/hana/search2Contains.js +12 -1
- package/libx/_runtime/hana/search2cqn4sql.js +10 -27
- package/libx/_runtime/hana/streaming.js +1 -0
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -0
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +5 -2
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -0
- package/libx/_runtime/messaging/enterprise-messaging.js +62 -3
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/redis-messaging.js +1 -0
- package/libx/_runtime/remote/Service.js +2 -2
- package/libx/_runtime/remote/utils/client.js +35 -11
- package/libx/_runtime/remote/utils/data.js +7 -2
- package/libx/_runtime/sqlite/Service.js +18 -7
- package/libx/_runtime/sqlite/conversion.js +3 -0
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +3 -3
- package/libx/_runtime/sqlite/localized.js +8 -8
- package/libx/odata/afterburner.js +39 -7
- package/libx/odata/cqn2odata.js +6 -3
- package/libx/odata/grammar.pegjs +66 -18
- package/libx/odata/index.js +3 -2
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +2 -0
- package/libx/rest/RestAdapter.js +62 -43
- package/libx/rest/middleware/input.js +2 -3
- package/libx/rest/middleware/parse.js +2 -1
- package/libx/rest/middleware/update.js +1 -1
- package/package.json +2 -2
- package/server.js +5 -4
- package/srv/mtx.cds +1 -1
- package/srv/mtx.js +4 -24
- package/lib/srv/adapters.js +0 -85
- package/lib/utils/resources/index.js +0 -48
- package/lib/utils/resources/tar.js +0 -49
- package/lib/utils/resources/utils.js +0 -11
- package/libx/_runtime/db/utils/propagateForeignKeys.js +0 -93
- package/libx/_runtime/extensibility/activate.js +0 -69
- package/libx/_runtime/extensibility/add.js +0 -50
- package/libx/_runtime/extensibility/addExtension.js +0 -72
- package/libx/_runtime/extensibility/defaults.js +0 -34
- package/libx/_runtime/extensibility/handler/transformREAD.js +0 -121
- package/libx/_runtime/extensibility/handler/transformRESULT.js +0 -51
- package/libx/_runtime/extensibility/handler/transformWRITE.js +0 -64
- package/libx/_runtime/extensibility/linter/allowlist_checker.js +0 -373
- package/libx/_runtime/extensibility/linter/annotations_checker.js +0 -113
- package/libx/_runtime/extensibility/linter/checker_base.js +0 -20
- package/libx/_runtime/extensibility/linter/namespace_checker.js +0 -180
- package/libx/_runtime/extensibility/linter.js +0 -32
- package/libx/_runtime/extensibility/push.js +0 -118
- package/libx/_runtime/extensibility/service.js +0 -38
- package/libx/_runtime/extensibility/token.js +0 -57
- package/libx/_runtime/extensibility/utils.js +0 -131
- package/libx/_runtime/extensibility/validation.js +0 -50
- package/libx/_runtime/extensibility/views.js +0 -12
- package/srv/extensibility-service.cds +0 -59
- package/srv/extensibility-service.js +0 -1
- package/srv/extensions.cds +0 -8
- package/srv/model-provider.cds +0 -61
- package/srv/model-provider.js +0 -143
|
@@ -37,7 +37,7 @@ const _convert = (refEntries, req) => {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// REVISIT once sql can handle structured keys properly, this handler should not be required anymore
|
|
40
|
-
const
|
|
40
|
+
const sqliteConvertAssocToOneManaged = function (req) {
|
|
41
41
|
// do simple checks upfront and exit early
|
|
42
42
|
if (!(req.query && req.query.SELECT) || typeof req.query === 'string') return
|
|
43
43
|
if (!req.query.SELECT.orderBy && !req.query.SELECT.groupBy && !req.query.SELECT.where && !req.query.SELECT.having) {
|
|
@@ -49,6 +49,6 @@ const _handler = function (req) {
|
|
|
49
49
|
_convert(_getConvertibleEntries(req), req)
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
sqliteConvertAssocToOneManaged._initial = true
|
|
53
53
|
|
|
54
|
-
module.exports =
|
|
54
|
+
module.exports = sqliteConvertAssocToOneManaged
|
|
@@ -7,6 +7,8 @@ if (cds.env.i18n && Array.isArray(cds.env.i18n.for_sqlite) && !cds.env.i18n.for_
|
|
|
7
7
|
LOG._warn && LOG.warn('No language configuration found in cds.env.i18n.for_sqlite')
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
const _translations = cds.env.i18n.for_sqlite.reduce((all, l) => ((all[l] = true), all), {})
|
|
11
|
+
|
|
10
12
|
// REVISIT: this is actually configurable
|
|
11
13
|
// there is no localized.en.<name>
|
|
12
14
|
const getLocalize = (locale, model) => name => {
|
|
@@ -15,15 +17,13 @@ const getLocalize = (locale, model) => name => {
|
|
|
15
17
|
// if we get here via onReadDraft, target is already localized
|
|
16
18
|
// because of subrequest using SELECT.from as new target
|
|
17
19
|
const target = model.definitions[ensureUnlocalized(name)]
|
|
18
|
-
|
|
19
|
-
target &&
|
|
20
|
-
target['@cds.localized'] !== false &&
|
|
21
|
-
model.definitions[`localized.${locale !== 'en' ? locale + '.' : ''}${name}`]
|
|
20
|
+
if (target?.['@cds.localized'] === false) return name
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
const view = model.definitions[`localized${locale in _translations ? '.' + locale : ''}.${name}`]
|
|
23
|
+
return view?.name || name
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const
|
|
26
|
+
const sqliteLocalized = function (req) {
|
|
27
27
|
const { query } = req
|
|
28
28
|
|
|
29
29
|
// do simple checks upfront and exit early
|
|
@@ -44,6 +44,6 @@ const _handler = function (req) {
|
|
|
44
44
|
redirect(query, getLocalize(req.locale, this.model))
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
sqliteLocalized._initial = true
|
|
48
48
|
|
|
49
|
-
module.exports =
|
|
49
|
+
module.exports = sqliteLocalized
|
|
@@ -163,20 +163,27 @@ function _convertVal(element, value) {
|
|
|
163
163
|
if (value === null) return value
|
|
164
164
|
switch (element._type) {
|
|
165
165
|
case 'cds.Integer':
|
|
166
|
+
case 'cds.UInt8':
|
|
167
|
+
case 'cds.Int16':
|
|
168
|
+
case 'cds.Int32':
|
|
166
169
|
// eslint-disable-next-line no-case-declarations
|
|
167
170
|
const n = Number(value)
|
|
168
171
|
if (Number.isSafeInteger(n)) return n
|
|
169
172
|
throw new Error('Not a valid integer') // TODO
|
|
173
|
+
|
|
170
174
|
case 'cds.String':
|
|
171
175
|
case 'cds.LargeString':
|
|
172
176
|
case 'cds.Decimal':
|
|
173
177
|
case 'cds.DecimalFloat':
|
|
174
178
|
case 'cds.Double':
|
|
179
|
+
case 'cds.Int64':
|
|
175
180
|
case 'cds.Integer64':
|
|
176
181
|
if (typeof value === 'string') return value
|
|
177
182
|
return String(value)
|
|
183
|
+
|
|
178
184
|
case 'cds.Boolean':
|
|
179
185
|
return typeof value === 'string' ? value === 'true' : value
|
|
186
|
+
|
|
180
187
|
default:
|
|
181
188
|
return value
|
|
182
189
|
}
|
|
@@ -189,12 +196,13 @@ function _processSegments(from, model, namespace) {
|
|
|
189
196
|
let path
|
|
190
197
|
let keys = null
|
|
191
198
|
let keyCount = 0
|
|
192
|
-
let incompleteKeys
|
|
199
|
+
let incompleteKeys = false
|
|
193
200
|
let one
|
|
194
201
|
let target
|
|
195
202
|
for (let i = 0; i < ref.length; i++) {
|
|
196
203
|
const seg = ref[i].id || ref[i]
|
|
197
|
-
|
|
204
|
+
const whereRef = ref[i].where
|
|
205
|
+
let params = whereRef && where2obj(whereRef)
|
|
198
206
|
|
|
199
207
|
if (incompleteKeys) {
|
|
200
208
|
// > key
|
|
@@ -234,7 +242,7 @@ function _processSegments(from, model, namespace) {
|
|
|
234
242
|
if (current.params && current.kind === 'entity') {
|
|
235
243
|
// > View with params
|
|
236
244
|
target = current
|
|
237
|
-
if (
|
|
245
|
+
if (whereRef) {
|
|
238
246
|
keyCount += addRefToWhereIfNecessary(ref[i].where, current)
|
|
239
247
|
_resolveAliasesInXpr(ref[i].where, current)
|
|
240
248
|
_resolveAliasInParams(params, current)
|
|
@@ -265,10 +273,28 @@ function _processSegments(from, model, namespace) {
|
|
|
265
273
|
// > entity
|
|
266
274
|
target = current
|
|
267
275
|
one = !!(ref[i].where || current._isSingleton)
|
|
276
|
+
|
|
277
|
+
let action
|
|
278
|
+
if (current.actions) {
|
|
279
|
+
const nextRef = typeof ref[i + 1] === 'string' && ref[i + 1]
|
|
280
|
+
const shortName = nextRef && nextRef.replace(namespace + '.', '')
|
|
281
|
+
action = shortName && current.actions[shortName]
|
|
282
|
+
}
|
|
283
|
+
|
|
268
284
|
incompleteKeys = ref[i].where ? false : i === ref.length - 1 || one ? false : true
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
285
|
+
|
|
286
|
+
if (incompleteKeys && action) {
|
|
287
|
+
if (action['@cds.odata.bindingparameter.collection']) {
|
|
288
|
+
incompleteKeys = false
|
|
289
|
+
} else {
|
|
290
|
+
const msg = `Bound operations are not supported on entity collections.`
|
|
291
|
+
throw Object.assign(new Error(msg), { statusCode: 501 })
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (whereRef) {
|
|
296
|
+
keyCount += addRefToWhereIfNecessary(whereRef, current)
|
|
297
|
+
_resolveAliasesInXpr(whereRef, current)
|
|
272
298
|
_resolveAliasInParams(params, current)
|
|
273
299
|
// in case of Foo(1), params will be {} (before addRefToWhereIfNecessary was called)
|
|
274
300
|
if (!Object.keys(params).length) params = where2obj(ref[i].where)
|
|
@@ -277,6 +303,11 @@ function _processSegments(from, model, namespace) {
|
|
|
277
303
|
}
|
|
278
304
|
} else if ({ action: 1, function: 1 }[current.kind]) {
|
|
279
305
|
// > action or function
|
|
306
|
+
if (current.kind === 'action' && ref && ref[ref.length - 1]?.where?.length === 0) {
|
|
307
|
+
const msg = `Round brackets (parentheses) are not allowed for action calls.`
|
|
308
|
+
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
309
|
+
}
|
|
310
|
+
|
|
280
311
|
if (i !== ref.length - 1) {
|
|
281
312
|
const msg = `${i ? 'Unbound' : 'Bound'} ${current.kind} are only supported as the last path segment.`
|
|
282
313
|
throw Object.assign(new Error(msg), { statusCode: 501 })
|
|
@@ -401,7 +432,8 @@ function _4service(service) {
|
|
|
401
432
|
const { ref } = from
|
|
402
433
|
|
|
403
434
|
// REVISIT: shouldn't be necessary
|
|
404
|
-
//Second findCsnTargetFor is required for concat query, where the root is already identified with the first query
|
|
435
|
+
// Second findCsnTargetFor is required for concat query, where the root is already identified with the first query
|
|
436
|
+
// and subsequent queries already have correct root
|
|
405
437
|
/*
|
|
406
438
|
* make first path segment fully qualified
|
|
407
439
|
*/
|
package/libx/odata/cqn2odata.js
CHANGED
|
@@ -121,8 +121,11 @@ const _format = (cur, elementName, target, kind, isLambda) => {
|
|
|
121
121
|
if (hasValidProps(cur, 'val')) return encodeURIComponent(formatVal(cur.val, elementName, target, kind))
|
|
122
122
|
if (hasValidProps(cur, 'xpr')) return `(${_xpr(cur.xpr, target, kind, isLambda)})`
|
|
123
123
|
// REVISIT: How to detect the types for all functions?
|
|
124
|
-
if (hasValidProps(cur, 'func'
|
|
125
|
-
|
|
124
|
+
if (hasValidProps(cur, 'func')) {
|
|
125
|
+
if (cur.args?.length) {
|
|
126
|
+
return kind === 'odata-v2' ? _odataV2Func(cur.func, cur.args) : `${cur.func}(${_args(cur.args)})`
|
|
127
|
+
}
|
|
128
|
+
return `${cur.func}()`
|
|
126
129
|
}
|
|
127
130
|
}
|
|
128
131
|
|
|
@@ -474,7 +477,7 @@ const parsers = {
|
|
|
474
477
|
count: (cqnPart, url, kind, target, isCount) => !isCount && $count(cqnPart, kind),
|
|
475
478
|
limit: (cqnPart, url, kind, target, isCount) => !isCount && $limit(cqnPart),
|
|
476
479
|
one: (cqnPart, url, kind, target, isCount) => !isCount && $one(cqnPart, url, kind),
|
|
477
|
-
ref: (cqnPart, url, kind, target,
|
|
480
|
+
ref: (cqnPart, url, kind, target, _isCount) => cqnPart[0].where && $where(cqnPart[0].where, target, kind)
|
|
478
481
|
}
|
|
479
482
|
|
|
480
483
|
function getOptions(cqnPart, url, kind, target, isCount) {
|
package/libx/odata/grammar.pegjs
CHANGED
|
@@ -238,6 +238,35 @@
|
|
|
238
238
|
}
|
|
239
239
|
return elements
|
|
240
240
|
}
|
|
241
|
+
|
|
242
|
+
const _replaceAliasedInWhere = (where, alias, value, isFromWhere = false) => {
|
|
243
|
+
where?.forEach(element => {
|
|
244
|
+
if (element.val === alias) {
|
|
245
|
+
// TODO check if we want to store replaced aliases/values for req.data in actions/functions in CQN
|
|
246
|
+
element.val = value.val
|
|
247
|
+
} else if (element.list === alias) {
|
|
248
|
+
element.list = value.list
|
|
249
|
+
} else if (element.func) {
|
|
250
|
+
element.args.forEach((arg, i) => {
|
|
251
|
+
if (arg.val === alias) {
|
|
252
|
+
arg.val = value.val
|
|
253
|
+
} else if (arg.func) {
|
|
254
|
+
_replaceAliasedInWhere(arg.args, alias, value, isFromWhere)
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
} else if (element.SELECT) {
|
|
258
|
+
_replaceAliased(element.SELECT, alias, value, isFromWhere)
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
const _replaceAliased = (select, alias, value, isFromWhere = false) => {
|
|
263
|
+
const {where, from} = select
|
|
264
|
+
_replaceAliasedInWhere(where, alias, value);
|
|
265
|
+
|
|
266
|
+
from?.ref?.forEach(element => {
|
|
267
|
+
_replaceAliasedInWhere(element.where, alias, value, true);
|
|
268
|
+
})
|
|
269
|
+
}
|
|
241
270
|
}
|
|
242
271
|
|
|
243
272
|
// ---------- Entity Paths ---------------
|
|
@@ -334,11 +363,13 @@
|
|
|
334
363
|
|
|
335
364
|
QueryOption = option:ExpandOption { if(option && option.apply) SELECT.apply = option.apply} /
|
|
336
365
|
"$skiptoken=" o skiptoken /
|
|
366
|
+
temporal /
|
|
337
367
|
format /
|
|
338
368
|
custom /
|
|
339
369
|
aliasedParamEqualsVal
|
|
340
370
|
// @OData spec for $expand:
|
|
341
|
-
// "Allowed system query options are $filter, $select, $orderby, $skip, $top, $count, $search, $expand and
|
|
371
|
+
// "Allowed system query options are $filter, $select, $orderby, $skip, $top, $count, $search, $expand and
|
|
372
|
+
// $apply (https://go.sap.corp/0jzs)."
|
|
342
373
|
ExpandOption =
|
|
343
374
|
"$select=" o select ( COMMA select )* /
|
|
344
375
|
"$expand=" o expand ( COMMA expand )* expandCount? /
|
|
@@ -350,6 +381,7 @@
|
|
|
350
381
|
"$count=" o count /
|
|
351
382
|
"$apply=" o trafos:transformations { return trafos }
|
|
352
383
|
|
|
384
|
+
temporal = ("$at" / "$from" / "$toInclusive" / "$to") "=" date
|
|
353
385
|
|
|
354
386
|
select
|
|
355
387
|
= col:('*' / ref) {
|
|
@@ -360,7 +392,7 @@
|
|
|
360
392
|
|
|
361
393
|
expandCount
|
|
362
394
|
= "/$count" {
|
|
363
|
-
const err = new Error("
|
|
395
|
+
const err = new Error('"/$count" is not supported for expand operation');
|
|
364
396
|
err.statusCode=501;
|
|
365
397
|
throw err;
|
|
366
398
|
}
|
|
@@ -370,7 +402,7 @@
|
|
|
370
402
|
expandOptions:(option:ExpandOption o ";"? { return option })*
|
|
371
403
|
{
|
|
372
404
|
if (expandOptions.find(option => option && option.apply !== undefined)) {
|
|
373
|
-
const err = new Error("
|
|
405
|
+
const err = new Error('"$apply" is not supported for expand operation');
|
|
374
406
|
err.statusCode=501;
|
|
375
407
|
throw err;
|
|
376
408
|
}
|
|
@@ -468,12 +500,16 @@
|
|
|
468
500
|
for (let i=0, k=0; i<any.length; ++i) {
|
|
469
501
|
let each = any[i]
|
|
470
502
|
if (each.ref && each.ref.length === 0 && any[i+1] === '=') {
|
|
471
|
-
xpr[k++] = { func:'contains', args:[{ref:id}, any[i+=2]] }
|
|
503
|
+
xpr[k++] = { func:'contains', args:[{ref:[id]}, any[i+=2]] }
|
|
472
504
|
} else {
|
|
473
505
|
xpr[k++] = each
|
|
474
506
|
}
|
|
475
507
|
}
|
|
476
508
|
if (xpr.length < any.length) {
|
|
509
|
+
if (!nav.length) {
|
|
510
|
+
// no navigation
|
|
511
|
+
return xpr
|
|
512
|
+
}
|
|
477
513
|
id = nav.pop()
|
|
478
514
|
return ['exists', { ref: [...nav, { id, where: xpr }] }]
|
|
479
515
|
} else {
|
|
@@ -493,7 +529,7 @@
|
|
|
493
529
|
/ comp:comparison { p.push(...comp) }
|
|
494
530
|
/ func:function { p.push(func) }
|
|
495
531
|
/ lambda:lambda { p.push(...lambda)}
|
|
496
|
-
/ list:listFilter {p.push(...list)}
|
|
532
|
+
/ list:listFilter { p.push(...list) }
|
|
497
533
|
)
|
|
498
534
|
( ao:(AND/OR) more:inner_lambda { p.push(ao, ...more) } )*
|
|
499
535
|
{ return p }
|
|
@@ -512,7 +548,7 @@
|
|
|
512
548
|
orderby
|
|
513
549
|
= ref:(
|
|
514
550
|
lambda {
|
|
515
|
-
const err = new Error("
|
|
551
|
+
const err = new Error('"$orderby" does not support lambda');
|
|
516
552
|
err.statusCode=501;
|
|
517
553
|
throw err;
|
|
518
554
|
} /
|
|
@@ -560,14 +596,18 @@
|
|
|
560
596
|
return {apply: mainTransformation}
|
|
561
597
|
}
|
|
562
598
|
|
|
599
|
+
aliasedParamVal = val / jsonObject / jsonArray / "[" list:innerList "]" { return { list } }
|
|
600
|
+
|
|
563
601
|
custom
|
|
564
602
|
= [a-zA-Z0-9-_.~]+ "=" [^&]*
|
|
565
603
|
aliasedParam "an aliased parameter (@param)" = "@" i:identifier { return "@" + i }
|
|
566
|
-
aliasedParamEqualsVal
|
|
604
|
+
aliasedParamEqualsVal = alias:aliasedParam "=" !aliasedParam value:aliasedParamVal {
|
|
605
|
+
_replaceAliased(SELECT, alias, value);
|
|
606
|
+
}
|
|
567
607
|
|
|
568
608
|
format = "$format=" f:$([^&]*) {
|
|
569
609
|
if (f.toLowerCase() !== "json") {
|
|
570
|
-
const err = new Error("
|
|
610
|
+
const err = new Error('Only query parameter "json" is allowed in "$format".')
|
|
571
611
|
err.statusCode = 501
|
|
572
612
|
throw err;
|
|
573
613
|
}
|
|
@@ -581,10 +621,13 @@
|
|
|
581
621
|
return [ a, op, b ]
|
|
582
622
|
}
|
|
583
623
|
|
|
624
|
+
listFilterParam = aliased:aliasedParam { return { list: aliased } } / listRoundBrackets
|
|
625
|
+
|
|
584
626
|
listFilter
|
|
585
|
-
= a:operand _ "in" _ b:
|
|
627
|
+
= a:operand _ "in" _ b:listFilterParam {
|
|
586
628
|
return [ a, "in", b ]
|
|
587
629
|
}
|
|
630
|
+
|
|
588
631
|
mathCalc
|
|
589
632
|
= operand (_ ("add" / "sub" / "mul" / "div" / "mod") _ operand)*
|
|
590
633
|
|
|
@@ -621,36 +664,39 @@
|
|
|
621
664
|
|
|
622
665
|
null "null" = "null" {return {val: null }}
|
|
623
666
|
|
|
667
|
+
// REVISIT why not JSON.parse() and return JS object?
|
|
624
668
|
jsonObject "a json object"
|
|
625
669
|
= val:$("{" (jsonObject / [^}])* "}") {return {val}}
|
|
626
670
|
|
|
671
|
+
// REVISIT why not JSON.parse() and return JS array?
|
|
627
672
|
jsonArray "a json array"
|
|
628
673
|
= val:$("[" o "]" / "[" o "{" (jsonArray / [^\]])* "]") {return {val}}
|
|
629
674
|
|
|
675
|
+
// REVISIT: only used for contains(identifier, ["searchterm"]) <- use innerList instead?
|
|
630
676
|
list "a list"
|
|
631
677
|
= "[" any:$([^\]])* "]" // > needs improvment
|
|
632
678
|
{ return { list: any.replace(/"/g,'').split(',').map(ele => ({ val: ele })) } }
|
|
633
679
|
|
|
680
|
+
innerList = (val1:val val2:("," v:val { return v })* { return [val1, ...val2] })
|
|
681
|
+
|
|
634
682
|
listRoundBrackets "a list"
|
|
635
|
-
= OPEN list:
|
|
636
|
-
{
|
|
637
|
-
return { list }
|
|
638
|
-
}
|
|
683
|
+
= OPEN list:innerList CLOSE // > needs improvment
|
|
684
|
+
{ return {list} }
|
|
639
685
|
|
|
640
686
|
|
|
641
687
|
functionName "a function name"
|
|
642
688
|
= $[a-zA-Z]+
|
|
643
689
|
|
|
644
690
|
function
|
|
645
|
-
= func:functionName OPEN
|
|
691
|
+
= func:functionName OPEN args:functionArgs CLOSE {
|
|
646
692
|
if (strict && !(func.toLowerCase() in strict.functions)) {
|
|
647
693
|
throw Object.assign(new Error(`"${func}" is an unknown function in OData URL spec (strict mode)`), { statusCode: 400 })
|
|
648
694
|
}
|
|
649
|
-
return { func: func.toLowerCase(), args
|
|
695
|
+
return { func: func.toLowerCase(), args }
|
|
650
696
|
}
|
|
651
697
|
|
|
652
698
|
functionArgs
|
|
653
|
-
= a:operand more:( COMMA o:operand {return o} )* {return
|
|
699
|
+
= args:(a:operand more:( COMMA o:operand { return o } )* { return [ a, ...more ] })* { return args.length ? args[0] : args }
|
|
654
700
|
|
|
655
701
|
boolish
|
|
656
702
|
= func:("contains"i/"endswith"i/"startswith"i) OPEN a:operand COMMA b:operand CLOSE
|
|
@@ -758,13 +804,15 @@
|
|
|
758
804
|
return {concat: [trafo1, ...trafo2]}
|
|
759
805
|
}
|
|
760
806
|
|
|
761
|
-
|
|
807
|
+
// REVISIT: support compute - current implementation is deviating from odata
|
|
808
|
+
computeTrafo = OPEN o computeExpr (o COMMA o computeExpr)* o CLOSE
|
|
762
809
|
|
|
763
810
|
computeExpr = where_clause asAlias
|
|
764
811
|
|
|
765
812
|
commonFuncTrafo = OPEN o first:operand o COMMA o second:operand o CLOSE { return [first, second] }
|
|
766
813
|
|
|
767
|
-
|
|
814
|
+
// REVISIT: support identity
|
|
815
|
+
identityTrafo = "identity" {return {identity: true }}
|
|
768
816
|
|
|
769
817
|
topTrafo
|
|
770
818
|
= OPEN o val:top o CLOSE {
|
package/libx/odata/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const cds = require('../_runtime/cds')
|
|
1
|
+
const cds = require('../_runtime/cds'), { decodeURIComponent } = cds.utils
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
|
|
4
4
|
const odata2cqn = require('./parser').parse
|
|
@@ -64,7 +64,7 @@ module.exports = {
|
|
|
64
64
|
// REVISIT: for okra, remove when no longer needed
|
|
65
65
|
else if (url.getIncomingRequest) url = url.getIncomingRequest().url
|
|
66
66
|
|
|
67
|
-
url = decodeURIComponent(url)
|
|
67
|
+
url = decodeURIComponent(url) // REVISIT: do we need that?
|
|
68
68
|
|
|
69
69
|
options = options === 'strict' ? { strict } : options.strict ? { ...options, strict } : options
|
|
70
70
|
if (options.service) Object.assign(options, { minimal: true, afterburner: afterburner.for(options.service) })
|
|
@@ -101,6 +101,7 @@ module.exports = {
|
|
|
101
101
|
|
|
102
102
|
return cqn
|
|
103
103
|
},
|
|
104
|
+
|
|
104
105
|
urlify: (cqn, options = {}) => {
|
|
105
106
|
return cqn2odata(cqn, options)
|
|
106
107
|
}
|