@sap/cds 6.0.4 → 6.1.0
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 +128 -18
- package/apis/cds.d.ts +11 -7
- package/apis/log.d.ts +48 -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 +13 -32
- package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
- package/bin/build/provider/buildTaskProviderInternal.js +22 -14
- package/bin/build/provider/hana/index.js +8 -7
- 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 +64 -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/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} +7 -6
- 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 +605 -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/SELECT.js +1 -1
- package/lib/ql/{index.js → cds-ql.js} +0 -0
- package/lib/req/context.js +35 -7
- 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} +0 -0
- package/lib/{serve → srv}/factory.js +1 -1
- package/lib/{serve/Service-api.js → srv/srv-api.js} +14 -6
- package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +3 -2
- 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 +206 -0
- package/lib/{serve/Transaction.js → srv/srv-tx.js} +6 -1
- 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 +14 -24
- package/lib/utils/resources/tar.js +18 -41
- package/libx/_runtime/auth/index.js +13 -10
- 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/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/cqn2cqn4sql.js +0 -1
- package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
- 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/generic/input.js +4 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
- package/libx/_runtime/extensibility/activate.js +47 -47
- package/libx/_runtime/extensibility/add.js +19 -13
- package/libx/_runtime/extensibility/addExtension.js +17 -13
- package/libx/_runtime/extensibility/defaults.js +25 -30
- 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 +78 -21
- package/libx/_runtime/extensibility/service.js +29 -12
- package/libx/_runtime/extensibility/token.js +56 -0
- 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 +0 -1
- 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 +1 -2
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
2
|
const queued = require('./common-utils/queued')
|
|
3
3
|
const OutboxService = require('./Outbox')
|
|
4
|
+
const ExtendedModels = require('../../../lib/srv/srv-models')
|
|
4
5
|
|
|
5
6
|
const appId = require('./common-utils/appId')
|
|
6
7
|
|
|
@@ -76,9 +77,17 @@ class MessagingService extends OutboxService {
|
|
|
76
77
|
return super.init()
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
emit(event, data, headers) {
|
|
80
|
+
async emit(event, data, headers) {
|
|
80
81
|
const _msg = typeof event === 'object' ? event : { event, data, headers }
|
|
81
|
-
|
|
82
|
+
if (_msg instanceof cds.Event) return super.emit(_msg)
|
|
83
|
+
if (_msg.inbound && !cds.context) {
|
|
84
|
+
cds.context = { tenant: _msg.tenant, user: cds.User.privileged }
|
|
85
|
+
if (cds.model) {
|
|
86
|
+
const ctx = cds.context
|
|
87
|
+
ctx.model = await ExtendedModels.model4(ctx.tenant, ctx.features)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const msg = new cds.Event(this.message4(_msg))
|
|
82
91
|
return super.emit(msg)
|
|
83
92
|
}
|
|
84
93
|
|
|
@@ -112,10 +121,6 @@ class MessagingService extends OutboxService {
|
|
|
112
121
|
|
|
113
122
|
message4(msg) {
|
|
114
123
|
const _msg = { ...msg }
|
|
115
|
-
if (msg.inbound && !cds.context) {
|
|
116
|
-
// REVISIT: why are all inbound messages executed with privileged user?
|
|
117
|
-
cds.context = { tenant: msg.tenant, user: cds.User.privileged }
|
|
118
|
-
}
|
|
119
124
|
_msg.event = _warnAndStripTopicPrefix(_msg.event, this.LOG)
|
|
120
125
|
if (!_msg.headers) _msg.headers = {}
|
|
121
126
|
if (!_msg.inbound) {
|
|
@@ -114,6 +114,7 @@ const _convertValue = (ieee754Compatible, exponentialDecimals) => (value, elemen
|
|
|
114
114
|
|
|
115
115
|
return value
|
|
116
116
|
}
|
|
117
|
+
const _PT = ([hh, mm, ss]) => `PT${hh}H${mm}M${ss}S`
|
|
117
118
|
|
|
118
119
|
const _convertPayloadValue = (value, element) => {
|
|
119
120
|
const type = _elementType(element)
|
|
@@ -121,6 +122,10 @@ const _convertPayloadValue = (value, element) => {
|
|
|
121
122
|
// see https://www.odata.org/documentation/odata-version-2-0/json-format/
|
|
122
123
|
if (value == null) return value
|
|
123
124
|
switch (type) {
|
|
125
|
+
case 'cds.Time':
|
|
126
|
+
return value.match(/^(PT)([H,M,S,0-9])*$/) ? value : _PT(value.split(':'))
|
|
127
|
+
case 'cds.Decimal':
|
|
128
|
+
return typeof value === 'string' ? value : new String(value)
|
|
124
129
|
case 'cds.Date':
|
|
125
130
|
case 'cds.DateTime':
|
|
126
131
|
return `/Date(${new Date(value).getTime()})/`
|
|
@@ -70,7 +70,6 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
_registerBeforeHandlers() {
|
|
73
|
-
this._ensureModel && this.before('*', this._ensureModel)
|
|
74
73
|
this.before(['CREATE', 'UPDATE'], '*', this._input) // > has to run before rewrite
|
|
75
74
|
this.before(['CREATE', 'READ', 'UPDATE', 'DELETE'], '*', this._rewrite)
|
|
76
75
|
|
|
@@ -111,6 +111,77 @@ function addRefToWhereIfNecessary(where, entity) {
|
|
|
111
111
|
return 1
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
function getResolvedElement(entity, { ref }) {
|
|
115
|
+
const element = entity.elements[ref[0]]
|
|
116
|
+
if (element && element.isAssociation && ref.length > 1) {
|
|
117
|
+
return getResolvedElement(element._target, { ref: ref.slice(1) })
|
|
118
|
+
} else if (element && element._isStructured) {
|
|
119
|
+
return getResolvedElement(element, { ref: ref.slice(1) })
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return element
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function _processWhere(where, entity) {
|
|
126
|
+
for (let i = 0; i < where.length; i++) {
|
|
127
|
+
const ref = where[i]
|
|
128
|
+
const val = where[i + 2]
|
|
129
|
+
|
|
130
|
+
if (ref === '(' || ref === ')' || ref === 'and' || ref === 'or' || ref === 'not' || val === 'not' || ref.func) {
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
if (ref.xpr) {
|
|
134
|
+
_processWhere(ref.xpr, entity)
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let valIndex = -1
|
|
139
|
+
let refIndex = -1
|
|
140
|
+
if (typeof val === 'object') {
|
|
141
|
+
if (val.val !== undefined) valIndex = i + 2
|
|
142
|
+
if (val.ref != undefined) refIndex = i + 2
|
|
143
|
+
}
|
|
144
|
+
if (typeof ref === 'object') {
|
|
145
|
+
if (ref.val !== undefined) valIndex = i
|
|
146
|
+
if (ref.ref != undefined) refIndex = i
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// no need to check ref = ref or val = val, if no ref or no val exists we can't do anything
|
|
150
|
+
if (valIndex === refIndex || valIndex === -1 || refIndex == -1) continue
|
|
151
|
+
|
|
152
|
+
const realRef = where[refIndex]
|
|
153
|
+
const element = getResolvedElement(entity, realRef)
|
|
154
|
+
|
|
155
|
+
if (element) {
|
|
156
|
+
i += 2
|
|
157
|
+
where[valIndex].val = _convertVal(element, where[valIndex].val)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function _convertVal(element, value) {
|
|
163
|
+
if (value === null) return value
|
|
164
|
+
switch (element._type) {
|
|
165
|
+
case 'cds.Integer':
|
|
166
|
+
// eslint-disable-next-line no-case-declarations
|
|
167
|
+
const n = Number(value)
|
|
168
|
+
if (Number.isSafeInteger(n)) return n
|
|
169
|
+
throw new Error('Not a valid integer') // TODO
|
|
170
|
+
case 'cds.String':
|
|
171
|
+
case 'cds.LargeString':
|
|
172
|
+
case 'cds.Decimal':
|
|
173
|
+
case 'cds.DecimalFloat':
|
|
174
|
+
case 'cds.Double':
|
|
175
|
+
case 'cds.Integer64':
|
|
176
|
+
if (typeof value === 'string') return value
|
|
177
|
+
return String(value)
|
|
178
|
+
case 'cds.Boolean':
|
|
179
|
+
return typeof value === 'string' ? value === 'true' : value
|
|
180
|
+
default:
|
|
181
|
+
return value
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
114
185
|
function _processSegments(from, model, namespace) {
|
|
115
186
|
const { ref } = from
|
|
116
187
|
|
|
@@ -142,8 +213,7 @@ function _processSegments(from, model, namespace) {
|
|
|
142
213
|
.join(',')})`
|
|
143
214
|
base.where.push({ ref: [key] }, '=', { val })
|
|
144
215
|
} else {
|
|
145
|
-
const val =
|
|
146
|
-
element._type === 'cds.Integer' ? Number(seg) : element._type === 'cds.Boolean' ? seg === 'true' : seg
|
|
216
|
+
const val = _convertVal(element, seg)
|
|
147
217
|
base.where.push({ ref: [key] }, '=', { val })
|
|
148
218
|
}
|
|
149
219
|
ref[i] = null
|
|
@@ -168,6 +238,7 @@ function _processSegments(from, model, namespace) {
|
|
|
168
238
|
keyCount += addRefToWhereIfNecessary(ref[i].where, current)
|
|
169
239
|
_resolveAliasesInXpr(ref[i].where, current)
|
|
170
240
|
_resolveAliasInParams(params, current)
|
|
241
|
+
_processWhere(ref[i].where, current)
|
|
171
242
|
}
|
|
172
243
|
|
|
173
244
|
_addDefaultParams(ref[i], current)
|
|
@@ -201,6 +272,7 @@ function _processSegments(from, model, namespace) {
|
|
|
201
272
|
_resolveAliasInParams(params, current)
|
|
202
273
|
// in case of Foo(1), params will be {} (before addRefToWhereIfNecessary was called)
|
|
203
274
|
if (!Object.keys(params).length) params = where2obj(ref[i].where)
|
|
275
|
+
_processWhere(ref[i].where, current)
|
|
204
276
|
_checkAllKeysProvided(params, current)
|
|
205
277
|
}
|
|
206
278
|
} else if ({ action: 1, function: 1 }[current.kind]) {
|
|
@@ -221,6 +293,7 @@ function _processSegments(from, model, namespace) {
|
|
|
221
293
|
if (ref[i].where) {
|
|
222
294
|
keyCount += addRefToWhereIfNecessary(ref[i].where, current)
|
|
223
295
|
_resolveAliasesInXpr(ref[i].where, current)
|
|
296
|
+
_processWhere(ref[i].where, current)
|
|
224
297
|
}
|
|
225
298
|
} else if (current._isStructured) {
|
|
226
299
|
// > nested property
|
|
@@ -349,6 +422,10 @@ function _4service(service) {
|
|
|
349
422
|
*/
|
|
350
423
|
const { one, current, target } = _processSegments(from, model, namespace)
|
|
351
424
|
|
|
425
|
+
if (cqn.SELECT.where) {
|
|
426
|
+
_processWhere(cqn.SELECT.where, root)
|
|
427
|
+
}
|
|
428
|
+
|
|
352
429
|
// one?
|
|
353
430
|
if (one) cqn.SELECT.one = true
|
|
354
431
|
|
package/libx/odata/cqn2odata.js
CHANGED
|
@@ -114,11 +114,11 @@ const _odataV2Func = (func, args) => {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
const _format = (cur,
|
|
118
|
-
if (typeof cur !== 'object') return encodeURIComponent(formatVal(cur,
|
|
117
|
+
const _format = (cur, elementName, target, kind, isLambda) => {
|
|
118
|
+
if (typeof cur !== 'object') return encodeURIComponent(formatVal(cur, elementName, target, kind))
|
|
119
119
|
if (hasValidProps(cur, 'ref'))
|
|
120
120
|
return encodeURIComponent(isLambda ? [LAMBDA_VARIABLE, ...cur.ref].join('/') : cur.ref[0].id || cur.ref.join('/'))
|
|
121
|
-
if (hasValidProps(cur, 'val')) return encodeURIComponent(formatVal(cur.val,
|
|
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
124
|
if (hasValidProps(cur, 'func', 'args')) {
|
|
@@ -180,7 +180,8 @@ function _xpr(expr, target, kind, isLambda) {
|
|
|
180
180
|
res.push(OPERATORS[cur] || cur.toLowerCase())
|
|
181
181
|
}
|
|
182
182
|
} else {
|
|
183
|
-
const
|
|
183
|
+
const ref = expr[i - 2]
|
|
184
|
+
const formatted = _format(cur, ref?.ref && (ref.ref.length ? ref.ref : ref.ref[0]), target, kind, isLambda)
|
|
184
185
|
if (formatted !== undefined) res.push(formatted)
|
|
185
186
|
}
|
|
186
187
|
}
|
|
@@ -406,7 +407,7 @@ function $orderBy(orderBy) {
|
|
|
406
407
|
return '$orderby=' + res.join(',')
|
|
407
408
|
}
|
|
408
409
|
|
|
409
|
-
function parseSearch(search) {
|
|
410
|
+
function parseSearch(search, kind) {
|
|
410
411
|
const res = []
|
|
411
412
|
|
|
412
413
|
for (const cur of search) {
|
|
@@ -417,7 +418,8 @@ function parseSearch(search) {
|
|
|
417
418
|
|
|
418
419
|
if (hasValidProps(cur, 'val')) {
|
|
419
420
|
// search term must not be formatted
|
|
420
|
-
res.push(
|
|
421
|
+
if (kind === 'odata-v2') res.push(`${encodeURIComponent(cur.val)}`)
|
|
422
|
+
else res.push(`"${encodeURIComponent(cur.val)}"`)
|
|
421
423
|
}
|
|
422
424
|
|
|
423
425
|
if (typeof cur === 'string') {
|
|
@@ -433,7 +435,7 @@ function parseSearch(search) {
|
|
|
433
435
|
}
|
|
434
436
|
|
|
435
437
|
function $search(search, kind) {
|
|
436
|
-
const expr = parseSearch(search).join('%20').replace('(%20', '(').replace('%20)', ')')
|
|
438
|
+
const expr = parseSearch(search, kind).join('%20').replace('(%20', '(').replace('%20)', ')')
|
|
437
439
|
|
|
438
440
|
if (expr) {
|
|
439
441
|
// odata-v2 may support custom query option "search"
|
package/libx/odata/grammar.pegjs
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
//
|
|
28
28
|
// ---------- JavaScript Helpers -------------
|
|
29
29
|
{
|
|
30
|
+
|
|
30
31
|
const $ = Object.assign
|
|
31
32
|
const { strict, minimal } = options
|
|
32
33
|
const stack = []
|
|
@@ -243,7 +244,7 @@
|
|
|
243
244
|
|
|
244
245
|
ODataRelativeURI // Note: case-sensitive!
|
|
245
246
|
= '/'? (p:path { SELECT = p })
|
|
246
|
-
( o"?"o QueryOption ( o'&'o QueryOption )*
|
|
247
|
+
( o"?"o (QueryOption ( o'&'o QueryOption )*)? )? o {
|
|
247
248
|
if (count) {
|
|
248
249
|
// columns set because of $count: ignore $select, $expand, $top, $skip, $orderby
|
|
249
250
|
// REVISIT: don't ignore query options but throw bad request (as okra did)?
|
|
@@ -334,46 +335,39 @@
|
|
|
334
335
|
QueryOption = option:ExpandOption { if(option && option.apply) SELECT.apply = option.apply} /
|
|
335
336
|
"$skiptoken=" o skiptoken /
|
|
336
337
|
format /
|
|
337
|
-
custom
|
|
338
|
+
custom /
|
|
339
|
+
aliasedParamEqualsVal
|
|
338
340
|
// @OData spec for $expand:
|
|
339
341
|
// "Allowed system query options are $filter, $select, $orderby, $skip, $top, $count, $search, $expand and $apply (http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs02/odata-data-aggregation-ext-v4.0-cs02.html#_The_expand_Transformation)."
|
|
340
342
|
ExpandOption =
|
|
341
343
|
"$select=" o select ( COMMA select )* /
|
|
342
|
-
"$expand=" o expand ( COMMA expand )* /
|
|
343
|
-
"$filter=" o f:filter{SELECT.where = f} /
|
|
344
|
+
"$expand=" o expand ( COMMA expand )* expandCount? /
|
|
345
|
+
"$filter=" o f:filter { SELECT.where = f } /
|
|
344
346
|
"$orderby=" o o:orderby ( COMMA o2:orderby{_setOrderBy(o2)} )* {_setOrderBy(o,true)} /
|
|
345
|
-
"$top=" o val:top{(SELECT.limit || (SELECT.limit={})).rows = {val}} /
|
|
346
|
-
"$skip=" o val:skip{_setLimitOffset(val)} /
|
|
347
|
-
"$search=" o s:search {if (s) SELECT.search = s} /
|
|
347
|
+
"$top=" o val:top { (SELECT.limit || (SELECT.limit={})).rows = {val} } /
|
|
348
|
+
"$skip=" o val:skip { _setLimitOffset(val) } /
|
|
349
|
+
"$search=" o s:search { if (s) SELECT.search = s } /
|
|
348
350
|
"$count=" o count /
|
|
349
|
-
"$apply=" o trafos:transformations {return trafos}
|
|
351
|
+
"$apply=" o trafos:transformations { return trafos }
|
|
350
352
|
|
|
351
353
|
|
|
352
354
|
select
|
|
353
|
-
= col:('*'/ref) {
|
|
355
|
+
= col:('*' / ref) {
|
|
354
356
|
SELECT.columns = Array.isArray(SELECT.columns) ? SELECT.columns : []
|
|
355
357
|
if (!SELECT.columns.find(_compareRefs(col))) SELECT.columns.push(col)
|
|
356
358
|
return col
|
|
357
359
|
}
|
|
358
360
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
)
|
|
370
|
-
( // --- nested query options, if any
|
|
371
|
-
(OPEN {
|
|
372
|
-
stack.push (SELECT)
|
|
373
|
-
SELECT = SELECT.expand[SELECT.expand.length-1]
|
|
374
|
-
SELECT.expand = '*' // by default expand everything
|
|
375
|
-
})(
|
|
376
|
-
expandOptions:( o ";"? o option:ExpandOption{return option})*
|
|
361
|
+
expandCount
|
|
362
|
+
= "/$count" {
|
|
363
|
+
const err = new Error("EXPAND_COUNT_UNSUPPORTED");
|
|
364
|
+
err.statusCode=501;
|
|
365
|
+
throw err;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
expandQueryOptions
|
|
369
|
+
= (
|
|
370
|
+
expandOptions:(option:ExpandOption o ";"? { return option })*
|
|
377
371
|
{
|
|
378
372
|
if (expandOptions.find(option => option && option.apply !== undefined)) {
|
|
379
373
|
const err = new Error("EXPAND_APPLY_UNSUPPORTED");
|
|
@@ -390,29 +384,50 @@
|
|
|
390
384
|
if (Array.isArray(SELECT.expand) && SELECT.expand.indexOf('*') === -1) SELECT.expand.unshift('*')
|
|
391
385
|
}
|
|
392
386
|
}
|
|
393
|
-
)
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
expandQueryOption
|
|
390
|
+
= (OPEN {
|
|
391
|
+
stack.push (SELECT)
|
|
392
|
+
SELECT = SELECT.expand[SELECT.expand.length-1]
|
|
393
|
+
SELECT.expand = '*' // by default expand everything
|
|
394
|
+
})
|
|
395
|
+
expandQueryOptions?
|
|
396
|
+
(CLOSE {
|
|
394
397
|
SELECT = stack.pop()
|
|
395
398
|
})
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
//REVISIT: per OData spec $apply should be also supported inside of $expand
|
|
402
|
+
expand
|
|
403
|
+
= (
|
|
404
|
+
c:('*' / ref) {
|
|
405
|
+
const col = c === '*' ? {} : c
|
|
406
|
+
col.expand = '*'
|
|
407
|
+
if (!Array.isArray(SELECT.expand)) SELECT.expand = []
|
|
408
|
+
if (!SELECT.expand.find(_compareRefs(col))) SELECT.expand.push(col)
|
|
409
|
+
return col
|
|
410
|
+
}
|
|
411
|
+
)
|
|
412
|
+
expandQueryOption?
|
|
413
|
+
|
|
403
414
|
top
|
|
404
|
-
= val:integer {return val}
|
|
415
|
+
= val:integer { return val }
|
|
416
|
+
|
|
405
417
|
skiptoken
|
|
406
418
|
= val:integer? skiptoken:skiptokenChars? {
|
|
407
419
|
// REVISIT ignore non-numeric $skiptoken as not supported by CQN
|
|
408
420
|
if (skiptoken) return
|
|
409
421
|
_setLimitOffset(val)
|
|
410
422
|
}
|
|
423
|
+
|
|
411
424
|
skip
|
|
412
|
-
= val:integer {return val}
|
|
425
|
+
= val:integer { return val }
|
|
426
|
+
|
|
413
427
|
search
|
|
414
428
|
= p:search_clause {return p}
|
|
415
429
|
/ o // Do not add search property for space only
|
|
430
|
+
|
|
416
431
|
search_clause
|
|
417
432
|
= p:( n:NOT? {return n?[n]:[]} )(
|
|
418
433
|
OPEN xpr:search_clause CLOSE {p.push({xpr})}
|
|
@@ -422,9 +437,11 @@
|
|
|
422
437
|
val:word {p.push({val})}
|
|
423
438
|
)
|
|
424
439
|
)( ao:(AND/OR/AND_SPACE) more:search_clause {p.push(ao,...more)} )*
|
|
425
|
-
{return p}
|
|
440
|
+
{ return p }
|
|
441
|
+
|
|
426
442
|
filter
|
|
427
443
|
= p:where_clause { return p }
|
|
444
|
+
|
|
428
445
|
where_clause = p:( n:NOT? {return n?[n]:[]} )(
|
|
429
446
|
OPEN xpr:where_clause CLOSE {p.push({xpr})}
|
|
430
447
|
/ comp:comparison {p.push(...comp)}
|
|
@@ -437,8 +454,10 @@
|
|
|
437
454
|
}
|
|
438
455
|
/ func:boolish {p.push(func)}
|
|
439
456
|
/ val:bool {p.push({val})}
|
|
457
|
+
/ list:listFilter {p.push(...list)}
|
|
440
458
|
)( ao:(AND/OR) more:where_clause {p.push(ao,...more)} )*
|
|
441
|
-
{return p}
|
|
459
|
+
{ return p }
|
|
460
|
+
|
|
442
461
|
lambda =
|
|
443
462
|
nav:( n:identifier {return[n]} ) '/' ( n:identifier '/' {nav.push(n)} )*
|
|
444
463
|
xpr:(
|
|
@@ -467,28 +486,48 @@
|
|
|
467
486
|
}
|
|
468
487
|
)
|
|
469
488
|
{ return xpr }
|
|
489
|
+
|
|
470
490
|
inner_lambda =
|
|
471
491
|
p:( n:NOT? { return n ? [n] : [] } )(
|
|
472
492
|
OPEN xpr:inner_lambda CLOSE { p.push('(', ...xpr, ')') }
|
|
473
493
|
/ comp:comparison { p.push(...comp) }
|
|
474
494
|
/ func:function { p.push(func) }
|
|
475
495
|
/ lambda:lambda { p.push(...lambda)}
|
|
496
|
+
/ list:listFilter {p.push(...list)}
|
|
476
497
|
)
|
|
477
498
|
( ao:(AND/OR) more:inner_lambda { p.push(ao, ...more) } )*
|
|
478
499
|
{ return p }
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
500
|
+
|
|
501
|
+
lambda_clause =
|
|
502
|
+
prefix:identifier ":" inner:inner_lambda {
|
|
503
|
+
return _removeLambdaPrefix(prefix, inner)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
any =
|
|
507
|
+
"any" OPEN p:lambda_clause? CLOSE { return p }
|
|
508
|
+
|
|
509
|
+
all =
|
|
510
|
+
"all" OPEN p:lambda_clause CLOSE { return p }
|
|
511
|
+
|
|
484
512
|
orderby
|
|
485
|
-
= ref:(
|
|
513
|
+
= ref:(
|
|
514
|
+
lambda {
|
|
515
|
+
const err = new Error("ORDERBY_LAMBDA_UNSUPPORTED");
|
|
516
|
+
err.statusCode=501;
|
|
517
|
+
throw err;
|
|
518
|
+
} /
|
|
519
|
+
function /
|
|
520
|
+
ref
|
|
521
|
+
)
|
|
522
|
+
sort:( _ s:$("asc" / "desc") { return s })? {
|
|
486
523
|
// TODO: Lambda support
|
|
487
524
|
const appendObj = $(ref, sort && {sort});
|
|
488
525
|
return appendObj;
|
|
489
526
|
}
|
|
527
|
+
|
|
490
528
|
count
|
|
491
529
|
= val:bool { if(val) SELECT.count = true }
|
|
530
|
+
|
|
492
531
|
transformations
|
|
493
532
|
= mainTransformation:trafo additionalTransformation:("/" t2:trafo {
|
|
494
533
|
return t2
|
|
@@ -523,6 +562,8 @@
|
|
|
523
562
|
|
|
524
563
|
custom
|
|
525
564
|
= [a-zA-Z0-9-_.~]+ "=" [^&]*
|
|
565
|
+
aliasedParam "an aliased parameter (@param)" = "@" i:identifier { return "@" + i }
|
|
566
|
+
aliasedParamEqualsVal "@alias=value" = a:aliasedParam "=" v:([^&]*) {return a + "=" + v}
|
|
526
567
|
|
|
527
568
|
format = "$format=" f:$([^&]*) {
|
|
528
569
|
if (f.toLowerCase() !== "json") {
|
|
@@ -531,17 +572,25 @@
|
|
|
531
572
|
throw err;
|
|
532
573
|
}
|
|
533
574
|
}
|
|
575
|
+
|
|
534
576
|
//
|
|
535
577
|
// ---------- Expressions ------------
|
|
536
|
-
comparison
|
|
537
|
-
= a:operand _ o:$("eq"/"ne"/"lt"/"gt"/"le"/"ge") _ b:operand {
|
|
538
|
-
const op = { eq:'=', ne:'!=', lt:'<', gt:'>', le:'<=', ge:'>=' }[o]||o
|
|
578
|
+
comparison
|
|
579
|
+
= a:operand _ o:$("eq" / "ne" / "lt" / "gt" / "le" / "ge") _ b:operand {
|
|
580
|
+
const op = { eq:'=', ne:'!=', lt:'<', gt:'>', le:'<=', ge:'>=' }[o] || o
|
|
539
581
|
return [ a, op, b ]
|
|
540
582
|
}
|
|
583
|
+
|
|
584
|
+
listFilter
|
|
585
|
+
= a:operand _ "in" _ b:listRoundBrackets {
|
|
586
|
+
return [ a, "in", b ]
|
|
587
|
+
}
|
|
541
588
|
mathCalc
|
|
542
589
|
= operand (_ ("add" / "sub" / "mul" / "div" / "mod") _ operand)*
|
|
543
|
-
|
|
590
|
+
|
|
591
|
+
operand
|
|
544
592
|
= navigationCount / function / val / ref / jsonObject / jsonArray / list
|
|
593
|
+
|
|
545
594
|
navigationCount "navigation with $count"
|
|
546
595
|
= navigationPath:(head:identifier key:(OPEN keyArgs:args CLOSE {return keyArgs;})? '/' {
|
|
547
596
|
if (key) {
|
|
@@ -551,6 +600,7 @@
|
|
|
551
600
|
return head;
|
|
552
601
|
})+ count: '$count'
|
|
553
602
|
{ return {func: 'count', as: '$count', args: [{ref: navigationPath}]} }
|
|
603
|
+
|
|
554
604
|
ref "a reference"
|
|
555
605
|
= head:identifier tail:( '/' n:identifier {return n})*
|
|
556
606
|
{
|
|
@@ -559,34 +609,56 @@
|
|
|
559
609
|
}
|
|
560
610
|
return { ref:[ head, ...tail ] }
|
|
561
611
|
}
|
|
612
|
+
|
|
562
613
|
val
|
|
563
614
|
= val:(bool / date) {return {val}}
|
|
564
615
|
/ val:guid {return {val}}
|
|
565
616
|
/ val:number {return typeof val === 'number' ? {val} : { val, literal:'number' }}
|
|
566
617
|
/ val:string {return {val}}
|
|
567
618
|
/ val:binary {return {val}}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
619
|
+
/ val:aliasedParam {return {val}}
|
|
620
|
+
/ null
|
|
621
|
+
|
|
622
|
+
null "null" = "null" {return {val: null }}
|
|
623
|
+
|
|
624
|
+
jsonObject "a json object"
|
|
625
|
+
= val:$("{" (jsonObject / [^}])* "}") {return {val}}
|
|
626
|
+
|
|
627
|
+
jsonArray "a json array"
|
|
628
|
+
= val:$("[" o "]" / "[" o "{" (jsonArray / [^\]])* "]") {return {val}}
|
|
629
|
+
|
|
630
|
+
list "a list"
|
|
572
631
|
= "[" any:$([^\]])* "]" // > needs improvment
|
|
573
632
|
{ return { list: any.replace(/"/g,'').split(',').map(ele => ({ val: ele })) } }
|
|
574
633
|
|
|
575
|
-
|
|
576
|
-
=
|
|
634
|
+
listRoundBrackets "a list"
|
|
635
|
+
= OPEN list:(val1:val val2:("," v:val { return v })* { return [val1, ...val2] }) CLOSE // > needs improvment
|
|
636
|
+
{
|
|
637
|
+
return { list }
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
functionName "a function name"
|
|
642
|
+
= $[a-zA-Z]+
|
|
643
|
+
|
|
644
|
+
function
|
|
645
|
+
= func:functionName OPEN fnArgs:functionArgs CLOSE {
|
|
577
646
|
if (strict && !(func.toLowerCase() in strict.functions)) {
|
|
578
647
|
throw Object.assign(new Error(`"${func}" is an unknown function in OData URL spec (strict mode)`), { statusCode: 400 })
|
|
579
648
|
}
|
|
580
|
-
return { func: func.toLowerCase(), args:[a,...more] }
|
|
649
|
+
return { func: func.toLowerCase(), args:[fnArgs.a,...fnArgs.more] }
|
|
581
650
|
}
|
|
582
651
|
|
|
583
|
-
|
|
652
|
+
functionArgs
|
|
653
|
+
= a:operand more:( COMMA o:operand {return o} )* {return { a, more }}
|
|
654
|
+
|
|
655
|
+
boolish
|
|
584
656
|
= func:("contains"i/"endswith"i/"startswith"i) OPEN a:operand COMMA b:operand CLOSE
|
|
585
657
|
{ return { func: func.toLowerCase(), args:[a,b] }}
|
|
586
658
|
|
|
587
659
|
NOT = o "NOT"i _ {return 'not'}
|
|
588
660
|
AND = _ "AND"i _ {return 'and'}
|
|
589
|
-
AND_SPACE =
|
|
661
|
+
AND_SPACE = _ {return 'and'}
|
|
590
662
|
OR = _ "OR"i _ {return 'or'}
|
|
591
663
|
|
|
592
664
|
|
|
@@ -622,7 +694,7 @@
|
|
|
622
694
|
}
|
|
623
695
|
return {aggregate: [{ func, args }]}
|
|
624
696
|
} /
|
|
625
|
-
identity: identityTrafo{return identity}
|
|
697
|
+
identity: identityTrafo {return identity}
|
|
626
698
|
// customFunction
|
|
627
699
|
)
|
|
628
700
|
|
|
@@ -663,7 +735,11 @@
|
|
|
663
735
|
groupByElem
|
|
664
736
|
= c:(rollupSpec / ref) { return c }
|
|
665
737
|
rollupSpec // TODO fix this + add CAP support
|
|
666
|
-
= rollup:("rollup" OPEN o ('$all' / ref) (o COMMA ref)+ o CLOSE) {
|
|
738
|
+
= rollup:("rollup" OPEN o ('$all' / ref) (o COMMA ref)+ o CLOSE) {
|
|
739
|
+
const err = new Error("Rollup in groupby is not supported yet.");
|
|
740
|
+
err.statusCode=501;
|
|
741
|
+
throw err;
|
|
742
|
+
}
|
|
667
743
|
|
|
668
744
|
filterTrafo = OPEN o where:(where:filter{
|
|
669
745
|
return where
|
|
@@ -707,20 +783,21 @@
|
|
|
707
783
|
//
|
|
708
784
|
// ---------- Literals -----------
|
|
709
785
|
|
|
710
|
-
bool
|
|
786
|
+
bool "a boolean"
|
|
787
|
+
= b:("true" / "false") { return b === 'true'}
|
|
711
788
|
|
|
712
|
-
string "Edm.String"
|
|
713
|
-
= "'" s:$("''"/[^'])* "'"
|
|
789
|
+
string "a single quoted string" // "Edm.String"
|
|
790
|
+
= "'" s:$("''" / [^'])* "'" // 'A user''s story'
|
|
714
791
|
{return s.replace(/''/g,"'")}
|
|
715
792
|
|
|
716
|
-
doubleQuotedString
|
|
793
|
+
doubleQuotedString "a doubled quoted string"
|
|
717
794
|
= '"' s:$('\\"'/[^"])* '"'
|
|
718
795
|
{return s.replace(/\\\\/g,"\\").replace(/\\"/g,'"')}
|
|
719
796
|
|
|
720
|
-
word
|
|
797
|
+
word "a string"
|
|
721
798
|
= $([^ \t\n()"&;]+)
|
|
722
799
|
|
|
723
|
-
date
|
|
800
|
+
date "a date"
|
|
724
801
|
= s:$( [0-9]+"-"[0-9][0-9]"-"[0-9][0-9] // date
|
|
725
802
|
( "T"[0-9][0-9]":"[0-9][0-9](":"[0-9][0-9]("."[0-9]+)?)? // time
|
|
726
803
|
( "Z" / (("+" / "-")[0-9][0-9]":"[0-9][0-9]) )? // timezone (Z or +-hh:mm)
|
|
@@ -730,19 +807,23 @@
|
|
|
730
807
|
return s
|
|
731
808
|
}
|
|
732
809
|
|
|
733
|
-
number
|
|
734
|
-
|
|
810
|
+
// to avoid 123-123-123 being matched as number and showing error for "-123-123"
|
|
811
|
+
endsWithMinus
|
|
812
|
+
= [0-9]+ "-"
|
|
813
|
+
|
|
814
|
+
number "a number"
|
|
815
|
+
= !endsWithMinus s:$( [+-]? [0-9]+ ("."[0-9]+)? ("e"[0-9]+)? ) { return safeNumber(s) }
|
|
735
816
|
|
|
736
|
-
integer
|
|
817
|
+
integer "an integer"
|
|
737
818
|
= s:$( [+-]? [0-9]+ ) { return parseInt(s) }
|
|
738
819
|
|
|
739
|
-
identifier
|
|
820
|
+
identifier "an identifier"
|
|
740
821
|
= !bool !guid s:$([_a-zA-Z][_a-zA-Z0-9"."]*) { return s }
|
|
741
822
|
|
|
742
|
-
guid
|
|
823
|
+
guid "a guid"
|
|
743
824
|
= $( hex16 hex16 "-"? hex16 "-"? hex16 "-"? hex16 "-"? hex16 hex16 hex16 )
|
|
744
825
|
|
|
745
|
-
hex16
|
|
826
|
+
hex16 "a hex value"
|
|
746
827
|
= $( [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] )
|
|
747
828
|
|
|
748
829
|
segment // > everything except / and ?
|
|
@@ -751,7 +832,7 @@
|
|
|
751
832
|
skiptokenChars
|
|
752
833
|
= $( [a-zA-Z0-9-"."_~!$'()*+,;=:@"/""?"]+ )
|
|
753
834
|
|
|
754
|
-
binary // > url-safe base64
|
|
835
|
+
binary "a binary" // > url-safe base64
|
|
755
836
|
= "binary'" s:$([a-zA-Z0-9-_]+ ("=="/"=")?) "'" { return standardBase64(s) }
|
|
756
837
|
|
|
757
838
|
//
|
|
@@ -766,8 +847,8 @@
|
|
|
766
847
|
//
|
|
767
848
|
// ---------- Whitespaces -----------
|
|
768
849
|
|
|
769
|
-
o "optional
|
|
770
|
-
_ "
|
|
850
|
+
o "an optional whitespace" = $[ \t\n]*
|
|
851
|
+
_ "a whitespace" = $[ \t\n]+
|
|
771
852
|
|
|
772
853
|
//
|
|
773
|
-
// ------------------------------------
|
|
854
|
+
// ------------------------------------
|