@sap/cds 9.4.5 → 9.5.2
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 +79 -1
- package/_i18n/messages_en_US_saptrc.properties +1 -1
- package/common.cds +5 -2
- package/lib/compile/cds-compile.js +1 -0
- package/lib/compile/for/assert.js +64 -0
- package/lib/compile/for/flows.js +194 -58
- package/lib/compile/for/lean_drafts.js +75 -7
- package/lib/compile/parse.js +1 -1
- package/lib/compile/to/csn.js +6 -2
- package/lib/compile/to/edm.js +1 -1
- package/lib/compile/to/yaml.js +8 -1
- package/lib/dbs/cds-deploy.js +2 -2
- package/lib/env/cds-env.js +14 -4
- package/lib/env/defaults.js +6 -1
- package/lib/i18n/localize.js +1 -1
- package/lib/index.js +7 -7
- package/lib/req/event.js +4 -0
- package/lib/req/validate.js +3 -0
- package/lib/srv/cds.Service.js +2 -1
- package/lib/srv/middlewares/auth/ias-auth.js +5 -7
- package/lib/srv/middlewares/auth/index.js +1 -1
- package/lib/srv/protocols/index.js +7 -6
- package/lib/srv/srv-handlers.js +7 -0
- package/libx/_runtime/common/Service.js +5 -1
- package/libx/_runtime/common/constants/events.js +1 -0
- package/libx/_runtime/common/generic/assert.js +236 -0
- package/libx/_runtime/common/generic/flows.js +168 -108
- package/libx/_runtime/common/utils/cqn.js +0 -24
- package/libx/_runtime/common/utils/normalizeTimestamp.js +2 -2
- package/libx/_runtime/common/utils/resolveView.js +8 -2
- package/libx/_runtime/common/utils/templateProcessor.js +10 -1
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +21 -9
- package/libx/_runtime/fiori/lean-draft.js +511 -379
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +39 -35
- package/libx/_runtime/messaging/enterprise-messaging.js +2 -2
- package/libx/_runtime/remote/Service.js +4 -5
- package/libx/_runtime/ucl/Service.js +111 -15
- package/libx/common/utils/streaming.js +1 -1
- package/libx/odata/middleware/batch.js +8 -6
- package/libx/odata/middleware/create.js +2 -2
- package/libx/odata/middleware/delete.js +2 -2
- package/libx/odata/middleware/metadata.js +18 -11
- package/libx/odata/middleware/read.js +2 -2
- package/libx/odata/middleware/service-document.js +1 -1
- package/libx/odata/middleware/update.js +1 -1
- package/libx/odata/parse/afterburner.js +24 -25
- package/libx/odata/parse/cqn2odata.js +2 -6
- package/libx/odata/parse/grammar.peggy +90 -12
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/index.js +2 -2
- package/libx/odata/utils/readAfterWrite.js +2 -0
- package/libx/queue/TaskRunner.js +26 -1
- package/libx/queue/index.js +11 -1
- package/package.json +1 -1
- package/srv/ucl-service.cds +2 -0
|
@@ -150,19 +150,19 @@ function _convertVal(value, element) {
|
|
|
150
150
|
case 'cds.Int32':
|
|
151
151
|
if (!/^-?\+?\d+$/.test(value)) {
|
|
152
152
|
const msg = `Element "${element.name}" does not contain a valid Integer`
|
|
153
|
-
|
|
153
|
+
cds.error({ status: 400, message: msg })
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
// eslint-disable-next-line no-case-declarations
|
|
157
157
|
const n = Number(value)
|
|
158
158
|
if (!Number.isSafeInteger(n)) {
|
|
159
159
|
const msg = `Element "${element.name}" does not contain a valid Integer`
|
|
160
|
-
|
|
160
|
+
cds.error({ status: 400, message: msg })
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
if (element._type === 'cds.UInt8' && n < 0) {
|
|
164
164
|
const msg = `Element "${element.name}" does not contain a valid positive Integer`
|
|
165
|
-
|
|
165
|
+
cds.error({ status: 400, message: msg })
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
return n
|
|
@@ -191,7 +191,7 @@ function _convertVal(value, element) {
|
|
|
191
191
|
case 'cds.UUID':
|
|
192
192
|
if (!RELAXED_UUID_REGEX.test(value)) {
|
|
193
193
|
const msg = `Element "${element.name}" does not contain a valid UUID`
|
|
194
|
-
|
|
194
|
+
cds.error({ status: 400, message: msg })
|
|
195
195
|
}
|
|
196
196
|
return value
|
|
197
197
|
|
|
@@ -261,7 +261,7 @@ function _handleCollectionBoundActions(current, ref, i, namespace, one) {
|
|
|
261
261
|
const msg = `${action.kind.at(0).toUpperCase() + action.kind.slice(1)} "${
|
|
262
262
|
action.name
|
|
263
263
|
}" must be called on a collection of ${current.name}`
|
|
264
|
-
|
|
264
|
+
cds.error({ status: 400, message: msg })
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
if (incompleteKeys) {
|
|
@@ -269,7 +269,7 @@ function _handleCollectionBoundActions(current, ref, i, namespace, one) {
|
|
|
269
269
|
const msg = `${action.kind.at(0).toUpperCase() + action.kind.slice(1)} "${
|
|
270
270
|
action.name
|
|
271
271
|
}" must be called on a single instance of ${current.name}`
|
|
272
|
-
|
|
272
|
+
cds.error({ status: 400, message: msg })
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
incompleteKeys = false
|
|
@@ -336,7 +336,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
336
336
|
|
|
337
337
|
ref[i] = null
|
|
338
338
|
ref[i - keyCount] = base
|
|
339
|
-
incompleteKeys = keyCount < keys.length
|
|
339
|
+
incompleteKeys = keyCount < keys.filter(k => k !== 'IsActiveEntity').length
|
|
340
340
|
} else {
|
|
341
341
|
// > entity or property (incl. nested) or navigation or action or function
|
|
342
342
|
keys = null
|
|
@@ -362,7 +362,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
362
362
|
} else {
|
|
363
363
|
// parentheses are missing
|
|
364
364
|
const msg = `Invalid call to "${current.name}". Parentheses are missing`
|
|
365
|
-
|
|
365
|
+
cds.error({ status: 400, message: msg })
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
_addDefaultParams(ref[i], current)
|
|
@@ -383,7 +383,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
383
383
|
if (ref[i + 1] !== 'Set') {
|
|
384
384
|
// /Set is missing
|
|
385
385
|
const msg = `Invalid call to "${current.name}". You need to navigate to Set`
|
|
386
|
-
|
|
386
|
+
cds.error({ status: 400, message: msg })
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
ref[++i] = null
|
|
@@ -405,19 +405,19 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
405
405
|
|
|
406
406
|
if (keyCount === 0 && !Object.keys(params).length && whereRef.length === 1) {
|
|
407
407
|
const msg = `Entity "${current.name}" can not be accessed by key.`
|
|
408
|
-
|
|
408
|
+
cds.error({ status: 400, message: msg })
|
|
409
409
|
}
|
|
410
410
|
}
|
|
411
411
|
} else if ({ action: 1, function: 1 }[current.kind]) {
|
|
412
412
|
// > action or function
|
|
413
413
|
if (current.kind === 'action' && ref && ref.at(-1)?.where?.length === 0) {
|
|
414
414
|
const msg = `Parentheses are not allowed for action calls.`
|
|
415
|
-
|
|
415
|
+
cds.error({ status: 400, message: msg })
|
|
416
416
|
}
|
|
417
417
|
|
|
418
418
|
if (i !== ref.length - 1) {
|
|
419
419
|
const msg = `${i ? 'Bound' : 'Unbound'} ${current.kind}s are only supported as the last path segment`
|
|
420
|
-
|
|
420
|
+
cds.error({ status: 400, message: msg })
|
|
421
421
|
}
|
|
422
422
|
|
|
423
423
|
ref[i] = { operation: current.name }
|
|
@@ -503,7 +503,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
503
503
|
const propRef = ref.slice(i)
|
|
504
504
|
if (propRef[0].where?.length === 0) {
|
|
505
505
|
const msg = 'Parentheses are not allowed when addressing properties.'
|
|
506
|
-
|
|
506
|
+
cds.error({ status: 400, message: msg })
|
|
507
507
|
}
|
|
508
508
|
cqn.SELECT.columns.push({ ref: propRef })
|
|
509
509
|
|
|
@@ -527,7 +527,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
527
527
|
const msg = `Entity "${current.name}" has ${keysOf(current).length} keys. Only ${keyCount} ${
|
|
528
528
|
keyCount === 1 ? 'was' : 'were'
|
|
529
529
|
} provided.`
|
|
530
|
-
|
|
530
|
+
cds.error({ status: 400, message: msg })
|
|
531
531
|
}
|
|
532
532
|
|
|
533
533
|
// remove all nulled refs
|
|
@@ -607,9 +607,9 @@ function _processColumns(cqn, target, protocol) {
|
|
|
607
607
|
|
|
608
608
|
// Error if groupBy is present but no columns are selected
|
|
609
609
|
if (columns && !columns.length && cqn.SELECT.groupBy) {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
610
|
+
cds.error({
|
|
611
|
+
status: 400,
|
|
612
|
+
message: 'Explicit select must include at least one column available in the result set of groupby'
|
|
613
613
|
})
|
|
614
614
|
}
|
|
615
615
|
|
|
@@ -695,9 +695,8 @@ function _processColumns(cqn, target, protocol) {
|
|
|
695
695
|
if (processedColumn.func === null) {
|
|
696
696
|
processedColumn.func = aggregatedElement?.[AGGR_DFLT]?.['#']?.toLowerCase()
|
|
697
697
|
if (!customAggregate)
|
|
698
|
-
|
|
699
|
-
if (!processedColumn.func)
|
|
700
|
-
throw cds.error(`Default aggregation for property "${aggregatedPropertyName}" not found`)
|
|
698
|
+
cds.error(`Result type for custom aggregation of property "${aggregatedPropertyName}" not found`)
|
|
699
|
+
if (!processedColumn.func) cds.error(`Default aggregation for property "${aggregatedPropertyName}" not found`)
|
|
701
700
|
if (processedColumn.func === 'count_distinct') processedColumn.func = 'countdistinct'
|
|
702
701
|
}
|
|
703
702
|
|
|
@@ -705,7 +704,7 @@ function _processColumns(cqn, target, protocol) {
|
|
|
705
704
|
const semanticsAmountElementName = aggregatedElement?.[SMTCS_AMT_CC] ?? aggregatedElement?.[SMTCS_AMT_UOM]
|
|
706
705
|
if (!semanticsAmountElementName) continue
|
|
707
706
|
if (!target.elements[semanticsAmountElementName])
|
|
708
|
-
|
|
707
|
+
cds.error(`Referenced semantics amount element not found: ${semanticsAmountElementName}`)
|
|
709
708
|
const semanticsAmountElementRef = [...prefixRef, semanticsAmountElementName]
|
|
710
709
|
|
|
711
710
|
columns[i] = {
|
|
@@ -737,7 +736,7 @@ const _checkAllKeysProvided = (params, entity) => {
|
|
|
737
736
|
if (isView) {
|
|
738
737
|
// view with params
|
|
739
738
|
if (params === undefined) {
|
|
740
|
-
|
|
739
|
+
cds.error({ status: 400, message: `Invalid call to "${entity.name}". You need to navigate to Set` })
|
|
741
740
|
}
|
|
742
741
|
|
|
743
742
|
keysOfEntity = Object.keys(entity.params)
|
|
@@ -747,7 +746,7 @@ const _checkAllKeysProvided = (params, entity) => {
|
|
|
747
746
|
|
|
748
747
|
if (!keysOfEntity) return
|
|
749
748
|
for (const keyOfEntity of keysOfEntity) {
|
|
750
|
-
if (!(keyOfEntity in params)) {
|
|
749
|
+
if (keyOfEntity !== 'IsActiveEntity' && !(keyOfEntity in params)) {
|
|
751
750
|
if (isView && entity.params[keyOfEntity].default) {
|
|
752
751
|
// will be added later?
|
|
753
752
|
continue
|
|
@@ -755,7 +754,7 @@ const _checkAllKeysProvided = (params, entity) => {
|
|
|
755
754
|
|
|
756
755
|
// prettier-ignore
|
|
757
756
|
const msg = `${isView ? 'Parameter' : 'Key'} "${keyOfEntity}" is missing for ${isView ? 'view' : 'entity'} "${entity.name}"`
|
|
758
|
-
|
|
757
|
+
cds.error({ status: 400, message: msg })
|
|
759
758
|
}
|
|
760
759
|
}
|
|
761
760
|
}
|
|
@@ -764,7 +763,7 @@ const _doesNotExistError = (isExpand, refName, targetName, targetKind) => {
|
|
|
764
763
|
const msg = isExpand
|
|
765
764
|
? `Navigation property "${refName}" does not exist in "${targetName}"`
|
|
766
765
|
: `Property "${refName}" does not exist in ${targetKind === 'type' ? 'type ' : ''}"${targetName}"`
|
|
767
|
-
|
|
766
|
+
cds.error({ status: 400, message: msg })
|
|
768
767
|
}
|
|
769
768
|
|
|
770
769
|
const _get_service_of = element => {
|
|
@@ -210,7 +210,7 @@ function _xpr(expr, target, kind, isLambda, navPrefix = []) {
|
|
|
210
210
|
res.push(OPERATORS[cur] || cur.toLowerCase())
|
|
211
211
|
}
|
|
212
212
|
} else {
|
|
213
|
-
const ref = expr[i - 2]
|
|
213
|
+
const ref = expr[i - 1] in OPERATORS ? expr[i - 2] : expr[i + 1] in OPERATORS ? expr[i + 2] : null
|
|
214
214
|
const formatted = _format(
|
|
215
215
|
cur,
|
|
216
216
|
ref?.ref && (ref.ref.length ? ref.ref : ref.ref[0]),
|
|
@@ -281,7 +281,7 @@ function _getQueryTarget(entity, propOrEntity, model) {
|
|
|
281
281
|
|
|
282
282
|
const _params = (args, kind, target) => {
|
|
283
283
|
if (!args) {
|
|
284
|
-
|
|
284
|
+
cds.error({ status: 400, message: `Invalid call to "${target.name}". You need to navigate to Set` })
|
|
285
285
|
}
|
|
286
286
|
const params = Object.keys(args)
|
|
287
287
|
if (params.length !== Object.keys(target.params).length) {
|
|
@@ -298,10 +298,6 @@ const _params = (args, kind, target) => {
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
function _from(from, kind, model) {
|
|
301
|
-
if (typeof from === 'string') {
|
|
302
|
-
return { url: _entityUrl(from), queryTarget: model && model.definitions[from] }
|
|
303
|
-
}
|
|
304
|
-
|
|
305
301
|
let ref = getProp(from, 'ref')
|
|
306
302
|
ref = (Array.isArray(ref) && ref) || [ref]
|
|
307
303
|
|
|
@@ -515,8 +515,58 @@
|
|
|
515
515
|
})
|
|
516
516
|
return isAliased
|
|
517
517
|
}
|
|
518
|
+
|
|
519
|
+
const _replaceComputedProperties = (columns, col) => {
|
|
520
|
+
for (let i = 0; i < columns.length; i++) {
|
|
521
|
+
if (columns[i].ref && columns[i].ref.at(-1) === col.as && (col.xpr || col.ref || col.val)) {
|
|
522
|
+
columns[i] = col
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const _checkComputedPropsUsage = (elements, computedAliases, context) => {
|
|
530
|
+
if (!elements || !computedAliases.length) return
|
|
531
|
+
|
|
532
|
+
const _checkElement = element => {
|
|
533
|
+
if (element.ref && computedAliases.includes(element.ref.at(-1))) {
|
|
534
|
+
const err = new Error(`Computed property "${element.ref.at(-1)}" cannot be used in ${context}. Computed properties are only supported in $select`)
|
|
535
|
+
err.statusCode = 501
|
|
536
|
+
throw err
|
|
537
|
+
}
|
|
538
|
+
if (Array.isArray(element.xpr)) element.xpr.forEach(_checkElement)
|
|
539
|
+
if (Array.isArray(element.args)) element.args.forEach(_checkElement)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (Array.isArray(elements)) elements.forEach(_checkElement)
|
|
543
|
+
else _checkElement(elements)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const _validateComputedPropsUsage = (SELECT) => {
|
|
547
|
+
const computedAliases = SELECT.compute.map(col => col.as)
|
|
548
|
+
|
|
549
|
+
if (SELECT.where) _checkComputedPropsUsage(SELECT.where, computedAliases, '$filter')
|
|
550
|
+
if (SELECT.orderBy) _checkComputedPropsUsage(SELECT.orderBy, computedAliases, '$orderby')
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const _processComputedProps = (SELECT) => {
|
|
554
|
+
_validateComputedPropsUsage(SELECT)
|
|
555
|
+
|
|
556
|
+
for (const col of SELECT.compute) {
|
|
557
|
+
if (SELECT.columns) {
|
|
558
|
+
const propFound = _replaceComputedProperties(SELECT.columns, col)
|
|
559
|
+
if (!propFound) SELECT.columns.push(col)
|
|
560
|
+
} else {
|
|
561
|
+
SELECT.columns = ['*']
|
|
562
|
+
SELECT.columns.push(col)
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
delete SELECT.compute
|
|
566
|
+
}
|
|
518
567
|
}
|
|
519
568
|
|
|
569
|
+
|
|
520
570
|
// ---------- Entity Paths ---------------
|
|
521
571
|
|
|
522
572
|
ODataRelativeURI // Note: case-sensitive!
|
|
@@ -551,6 +601,8 @@
|
|
|
551
601
|
SELECT.__countAggregated = true
|
|
552
602
|
if(SELECT.apply)
|
|
553
603
|
return _handleApply(SELECT, SELECT.apply, onlyColumnsFromExpand)
|
|
604
|
+
if(SELECT.compute)
|
|
605
|
+
_processComputedProps(SELECT)
|
|
554
606
|
return { SELECT }
|
|
555
607
|
}
|
|
556
608
|
|
|
@@ -640,10 +692,11 @@
|
|
|
640
692
|
aliasedParamEqualsValOrPrefixParam /
|
|
641
693
|
deltaToken
|
|
642
694
|
// @OData spec for $expand:
|
|
643
|
-
// "Allowed system query options are $filter, $select, $orderby, $skip, $top, $count, $search, $expand and
|
|
695
|
+
// "Allowed system query options are $filter, $select, $compute, $orderby, $skip, $top, $count, $search, $expand and
|
|
644
696
|
// $apply (https://go.sap.corp/0jzs)."
|
|
645
697
|
ExpandOption =
|
|
646
698
|
"$select=" o select ( COMMA select )* /
|
|
699
|
+
"$compute=" o compute ( COMMA compute )* /
|
|
647
700
|
"$expand=" o expand ( COMMA expand )* expandCount? /
|
|
648
701
|
"$filter=" o f:filter { SELECT.where = f } /
|
|
649
702
|
"$orderby=" o o:orderby ( COMMA o2:orderby{_setOrderBy(o2)} )* {_setOrderBy(o,true)} /
|
|
@@ -748,6 +801,14 @@
|
|
|
748
801
|
= val:$( [^;)]+ ) { return [{ val }] }
|
|
749
802
|
/ o // Do not add search property for space only
|
|
750
803
|
|
|
804
|
+
compute
|
|
805
|
+
= expr:mathCalc as:asAlias {
|
|
806
|
+
SELECT.compute = Array.isArray(SELECT.compute) ? SELECT.compute : []
|
|
807
|
+
const comp = expr.xpr ? { xpr: expr.xpr, as } : { ...expr, as }
|
|
808
|
+
SELECT.compute.push(comp)
|
|
809
|
+
return comp
|
|
810
|
+
}
|
|
811
|
+
|
|
751
812
|
filter
|
|
752
813
|
= p:where_clause { return p }
|
|
753
814
|
|
|
@@ -877,6 +938,33 @@
|
|
|
877
938
|
//
|
|
878
939
|
// ---------- Expressions ------------
|
|
879
940
|
|
|
941
|
+
mathCalc
|
|
942
|
+
= head:mathOperand tail:(o op:("add" / "sub" / "mul" / "divby" / "div" ) o operand:mathOperand { return { op, operand } })* {
|
|
943
|
+
const opMap = { sub: '-', add: '+', mul: '*', divby: '/', div: '/' }
|
|
944
|
+
|
|
945
|
+
const toXprElement = (operand) => {
|
|
946
|
+
if (typeof operand === 'number') return { val: operand }
|
|
947
|
+
if (typeof operand === 'string') return { ref: [operand] }
|
|
948
|
+
return operand // xpr
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
let xpr = [toXprElement(head)]
|
|
952
|
+
for (let i = 0; i < tail.length; i++) {
|
|
953
|
+
xpr.push(opMap[tail[i].op])
|
|
954
|
+
xpr.push(toXprElement(tail[i].operand))
|
|
955
|
+
}
|
|
956
|
+
return tail.length === 0 ? xpr[0] : { xpr }
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
mathOperand
|
|
960
|
+
= integer / negativeIdentifier / identifier / parenthesizedMath
|
|
961
|
+
|
|
962
|
+
negativeIdentifier
|
|
963
|
+
= "-" id:identifier { return { xpr: ['-', { ref: [id] }] } }
|
|
964
|
+
|
|
965
|
+
parenthesizedMath
|
|
966
|
+
= OPEN expr:mathCalc CLOSE { return expr }
|
|
967
|
+
|
|
880
968
|
comparison
|
|
881
969
|
= a:operand _ o:$("eq" / "ne" / "lt" / "gt" / "le" / "ge") _ b:operand {
|
|
882
970
|
return [ a, OPERATORS[o] || o, b ]
|
|
@@ -888,9 +976,6 @@
|
|
|
888
976
|
= a:operand _ "in" _ b:listFilterParam {
|
|
889
977
|
return [ a, "in", b ]
|
|
890
978
|
}
|
|
891
|
-
|
|
892
|
-
mathCalc
|
|
893
|
-
= operand (_ ("add" / "sub" / "mul" / "div" / "mod") _ operand)*
|
|
894
979
|
|
|
895
980
|
operand
|
|
896
981
|
= navigationCount / function / val / ref / jsonObject / jsonArray / list
|
|
@@ -1025,7 +1110,6 @@
|
|
|
1025
1110
|
// REVISIT: All transformations below need improvment
|
|
1026
1111
|
"search" search:searchTrafo{return search} /
|
|
1027
1112
|
"concat" con:concatTrafo{return con} / //> Return con so that concat string is not returned
|
|
1028
|
-
"compute" compute:computeTrafo{return compute} /
|
|
1029
1113
|
"top" top:topTrafo{return top} /
|
|
1030
1114
|
"skip" skip:skipTrafo{return skip} /
|
|
1031
1115
|
"orderby" order:orderbyTrafo{return order} /
|
|
@@ -1054,8 +1138,7 @@
|
|
|
1054
1138
|
) { return res }
|
|
1055
1139
|
aggregateExpr
|
|
1056
1140
|
= path:(
|
|
1057
|
-
ref
|
|
1058
|
-
// / mathCalc - needs CAP support
|
|
1141
|
+
ref
|
|
1059
1142
|
)
|
|
1060
1143
|
func:aggregateWith? aggregateFrom? as:asAlias?
|
|
1061
1144
|
{ return { func, args: [ path ], as: as ?? path.ref[0] } }
|
|
@@ -1102,11 +1185,6 @@
|
|
|
1102
1185
|
return {concat: [trafo1, ...trafo2]}
|
|
1103
1186
|
}
|
|
1104
1187
|
|
|
1105
|
-
// REVISIT: support compute - current implementation is deviating from odata
|
|
1106
|
-
computeTrafo = OPEN o computeExpr (o COMMA o computeExpr)* o CLOSE
|
|
1107
|
-
|
|
1108
|
-
computeExpr = where_clause asAlias
|
|
1109
|
-
|
|
1110
1188
|
commonFuncTrafo = OPEN o first:operand o COMMA o second:operand o CLOSE { return [first, second] }
|
|
1111
1189
|
|
|
1112
1190
|
// REVISIT: support identity
|