@sap/cds 9.4.4 → 9.5.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +81 -1
  2. package/_i18n/messages_en_US_saptrc.properties +1 -1
  3. package/common.cds +5 -2
  4. package/lib/compile/cds-compile.js +1 -0
  5. package/lib/compile/for/assert.js +64 -0
  6. package/lib/compile/for/flows.js +194 -58
  7. package/lib/compile/for/lean_drafts.js +75 -7
  8. package/lib/compile/parse.js +1 -1
  9. package/lib/compile/to/csn.js +6 -2
  10. package/lib/compile/to/edm.js +1 -1
  11. package/lib/compile/to/yaml.js +8 -1
  12. package/lib/dbs/cds-deploy.js +2 -2
  13. package/lib/env/cds-env.js +14 -4
  14. package/lib/env/defaults.js +6 -1
  15. package/lib/i18n/localize.js +1 -1
  16. package/lib/index.js +7 -7
  17. package/lib/req/event.js +4 -0
  18. package/lib/req/validate.js +4 -1
  19. package/lib/srv/cds.Service.js +2 -1
  20. package/lib/srv/middlewares/auth/ias-auth.js +5 -7
  21. package/lib/srv/middlewares/auth/index.js +1 -1
  22. package/lib/srv/protocols/index.js +7 -6
  23. package/lib/srv/srv-handlers.js +7 -0
  24. package/libx/_runtime/common/Service.js +5 -1
  25. package/libx/_runtime/common/constants/events.js +1 -0
  26. package/libx/_runtime/common/generic/assert.js +220 -0
  27. package/libx/_runtime/common/generic/flows.js +168 -108
  28. package/libx/_runtime/common/generic/input.js +6 -4
  29. package/libx/_runtime/common/utils/cqn.js +0 -24
  30. package/libx/_runtime/common/utils/normalizeTimestamp.js +2 -2
  31. package/libx/_runtime/common/utils/resolveView.js +8 -2
  32. package/libx/_runtime/common/utils/templateProcessor.js +10 -1
  33. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +21 -9
  34. package/libx/_runtime/fiori/lean-draft.js +511 -379
  35. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +39 -35
  36. package/libx/_runtime/messaging/enterprise-messaging.js +2 -2
  37. package/libx/_runtime/remote/Service.js +4 -5
  38. package/libx/_runtime/ucl/Service.js +111 -15
  39. package/libx/common/utils/streaming.js +1 -1
  40. package/libx/odata/middleware/batch.js +8 -6
  41. package/libx/odata/middleware/create.js +2 -2
  42. package/libx/odata/middleware/delete.js +2 -2
  43. package/libx/odata/middleware/metadata.js +18 -11
  44. package/libx/odata/middleware/read.js +2 -2
  45. package/libx/odata/middleware/service-document.js +1 -1
  46. package/libx/odata/middleware/update.js +1 -1
  47. package/libx/odata/parse/afterburner.js +46 -36
  48. package/libx/odata/parse/cqn2odata.js +2 -6
  49. package/libx/odata/parse/grammar.peggy +91 -13
  50. package/libx/odata/parse/parser.js +1 -1
  51. package/libx/odata/utils/index.js +2 -2
  52. package/libx/odata/utils/readAfterWrite.js +2 -0
  53. package/libx/queue/TaskRunner.js +26 -1
  54. package/libx/queue/index.js +11 -1
  55. package/package.json +1 -1
  56. 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
- throw Object.assign(new Error(msg), { statusCode: 400 })
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
- throw Object.assign(new Error(msg), { statusCode: 400 })
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
- throw Object.assign(new Error(msg), { statusCode: 400 })
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
- throw Object.assign(new Error(msg), { statusCode: 400 })
194
+ cds.error({ status: 400, message: msg })
195
195
  }
196
196
  return value
197
197
 
@@ -230,10 +230,9 @@ const getStructTargetName = element => {
230
230
  const _getDataFromParams = (params, operation) => {
231
231
  try {
232
232
  return Object.keys(params).reduce((acc, cur) => {
233
+ const def = operation.params?.[cur]
233
234
  acc[cur] =
234
- typeof params[cur] === 'string' && (operation.params[cur]?.elements || operation.params[cur]?.items)
235
- ? JSON.parse(params[cur])
236
- : params[cur]
235
+ typeof params[cur] === 'string' && (def?.elements || def?.items) ? JSON.parse(params[cur]) : params[cur]
237
236
  return acc
238
237
  }, {})
239
238
  } catch (e) {
@@ -262,7 +261,7 @@ function _handleCollectionBoundActions(current, ref, i, namespace, one) {
262
261
  const msg = `${action.kind.at(0).toUpperCase() + action.kind.slice(1)} "${
263
262
  action.name
264
263
  }" must be called on a collection of ${current.name}`
265
- throw Object.assign(new Error(msg), { statusCode: 400 })
264
+ cds.error({ status: 400, message: msg })
266
265
  }
267
266
 
268
267
  if (incompleteKeys) {
@@ -270,7 +269,7 @@ function _handleCollectionBoundActions(current, ref, i, namespace, one) {
270
269
  const msg = `${action.kind.at(0).toUpperCase() + action.kind.slice(1)} "${
271
270
  action.name
272
271
  }" must be called on a single instance of ${current.name}`
273
- throw Object.assign(new Error(msg), { statusCode: 400 })
272
+ cds.error({ status: 400, message: msg })
274
273
  }
275
274
 
276
275
  incompleteKeys = false
@@ -337,7 +336,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
337
336
 
338
337
  ref[i] = null
339
338
  ref[i - keyCount] = base
340
- incompleteKeys = keyCount < keys.length
339
+ incompleteKeys = keyCount < keys.filter(k => k !== 'IsActiveEntity').length
341
340
  } else {
342
341
  // > entity or property (incl. nested) or navigation or action or function
343
342
  keys = null
@@ -363,7 +362,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
363
362
  } else {
364
363
  // parentheses are missing
365
364
  const msg = `Invalid call to "${current.name}". Parentheses are missing`
366
- throw cds.error(msg, { code: '400', statusCode: 400 })
365
+ cds.error({ status: 400, message: msg })
367
366
  }
368
367
 
369
368
  _addDefaultParams(ref[i], current)
@@ -384,7 +383,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
384
383
  if (ref[i + 1] !== 'Set') {
385
384
  // /Set is missing
386
385
  const msg = `Invalid call to "${current.name}". You need to navigate to Set`
387
- throw cds.error(msg, { code: '400', statusCode: 400 })
386
+ cds.error({ status: 400, message: msg })
388
387
  }
389
388
 
390
389
  ref[++i] = null
@@ -406,19 +405,19 @@ function _processSegments(from, model, namespace, cqn, protocol) {
406
405
 
407
406
  if (keyCount === 0 && !Object.keys(params).length && whereRef.length === 1) {
408
407
  const msg = `Entity "${current.name}" can not be accessed by key.`
409
- throw Object.assign(new Error(msg), { statusCode: 400 })
408
+ cds.error({ status: 400, message: msg })
410
409
  }
411
410
  }
412
411
  } else if ({ action: 1, function: 1 }[current.kind]) {
413
412
  // > action or function
414
413
  if (current.kind === 'action' && ref && ref.at(-1)?.where?.length === 0) {
415
414
  const msg = `Parentheses are not allowed for action calls.`
416
- throw Object.assign(new Error(msg), { statusCode: 400 })
415
+ cds.error({ status: 400, message: msg })
417
416
  }
418
417
 
419
418
  if (i !== ref.length - 1) {
420
419
  const msg = `${i ? 'Bound' : 'Unbound'} ${current.kind}s are only supported as the last path segment`
421
- throw Object.assign(new Error(msg), { statusCode: 400 })
420
+ cds.error({ status: 400, message: msg })
422
421
  }
423
422
 
424
423
  ref[i] = { operation: current.name }
@@ -449,10 +448,11 @@ function _processSegments(from, model, namespace, cqn, protocol) {
449
448
  target = current.returns.items ?? current.returns
450
449
  }
451
450
  } else if (current.isAssociation) {
452
- if (!current._target._service) {
451
+ if (current._target._service !== _get_service_of(target)) {
453
452
  // not exposed target
454
- cds.error(`Property '${current.name}' does not exist in type '${target.name.replace(namespace + '.', '')}'`, {
455
- statusCode: 404
453
+ cds.error({
454
+ status: 400,
455
+ message: `Property "${current.name}" does not exist in "${target.name.replace(namespace + '.', '')}"`
456
456
  })
457
457
  }
458
458
 
@@ -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
- throw Object.assign(new Error(msg), { statusCode: 400 })
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
- throw Object.assign(new Error(msg), { statusCode: 400 })
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
- throw cds.error('Explicit select must include at least one column available in the result set of groupby', {
611
- code: '400',
612
- statusCode: 400
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
- throw cds.error(`Result type for custom aggregation of property "${aggregatedPropertyName}" not found`)
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
- throw cds.error(`Referenced semantics amount element not found: ${semanticsAmountElementName}`)
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
- throw cds.error(`Invalid call to "${entity.name}". You need to navigate to Set`, { code: '400', statusCode: 400 })
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,16 +754,22 @@ 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
- throw Object.assign(new Error(msg), { statusCode: 400 })
757
+ cds.error({ status: 400, message: msg })
759
758
  }
760
759
  }
761
760
  }
762
761
 
763
762
  const _doesNotExistError = (isExpand, refName, targetName, targetKind) => {
764
763
  const msg = isExpand
765
- ? `Navigation property "${refName}" is not defined in "${targetName}"`
764
+ ? `Navigation property "${refName}" does not exist in "${targetName}"`
766
765
  : `Property "${refName}" does not exist in ${targetKind === 'type' ? 'type ' : ''}"${targetName}"`
767
- throw Object.assign(new Error(msg), { statusCode: 400 })
766
+ cds.error({ status: 400, message: msg })
767
+ }
768
+
769
+ const _get_service_of = element => {
770
+ if (element._service) return element._service
771
+ if (element.parent) return _get_service_of(element.parent)
772
+ return null
768
773
  }
769
774
 
770
775
  function _validateXpr(xpr, target, isOne, model, aliases = []) {
@@ -818,8 +823,13 @@ function _validateXpr(xpr, target, isOne, model, aliases = []) {
818
823
  _validateXpr([{ ref: x.ref.slice(1) }], element, isOne, model)
819
824
  element = _structProperty(x.ref.slice(1), element)
820
825
  }
821
- if (!element._target) {
822
- _doesNotExistError(true, refName, target.name)
826
+ const target_service = _get_service_of(target)
827
+ if (!element._target || element._target._service !== target_service) {
828
+ _doesNotExistError(
829
+ true,
830
+ refName,
831
+ target_service ? target.name.replace(target_service.name + '.', '') : target.name
832
+ )
823
833
  }
824
834
  _validateXpr(x.expand, element._target, false, model)
825
835
  if (x.where) {
@@ -872,14 +882,14 @@ module.exports = (cqn, model, namespace, protocol) => {
872
882
  let edmName = ref[0].id || ref[0]
873
883
  // REVISIT: shouldn't be necessary
874
884
  if (edmName.split('.').length > 1)
875
- //required for concat query, where the root is already identified with the first query and subsequent queries already have correct root
885
+ // required for concat query, where the root is already identified with the first query and subsequent queries already have correct root
876
886
  edmName = edmName.split('.')[edmName.split('.').length - 1]
877
887
 
878
888
  // Make first path segment fully qualified
879
889
  const root = findCsnTargetFor(edmName, model, namespace)
880
890
 
881
891
  if (!root) {
882
- //404 else we would expose knowledge to potential attackers
892
+ // 404 else we would expose knowledge to potential attackers
883
893
  throw new cds.error(404, `Invalid resource path "${namespace}.${ref[0].id || ref[0]}"`)
884
894
  }
885
895
  if (cds.env.effective.odata.containment && model.definitions[namespace]._containedEntities.has(root.name)) {
@@ -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
- throw cds.error(`Invalid call to "${target.name}". You need to navigate to Set`, { code: '400', statusCode: 400 })
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
 
@@ -189,7 +189,7 @@
189
189
  }
190
190
  if (newCqn.SELECT?.recurse && cqn.where) {
191
191
  const where = cqn.where
192
- delete cqn.where
192
+ delete newCqn.SELECT.where
193
193
  const columns = newCqn.SELECT.columns
194
194
  delete newCqn.SELECT.columns
195
195
  newCqn = { SELECT: { from: newCqn, where, columns } }
@@ -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