@sap/cds 9.2.0 → 9.3.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 +87 -1
- package/_i18n/i18n_es.properties +3 -3
- package/_i18n/i18n_es_MX.properties +3 -3
- package/_i18n/i18n_fr.properties +2 -2
- package/_i18n/messages.properties +6 -0
- package/app/index.js +0 -1
- package/bin/deploy.js +1 -1
- package/bin/serve.js +7 -20
- package/lib/compile/cdsc.js +3 -0
- package/lib/compile/for/flows.js +102 -0
- package/lib/compile/for/nodejs.js +28 -0
- package/lib/compile/to/edm.js +11 -4
- package/lib/core/classes.js +1 -1
- package/lib/core/linked-csn.js +8 -0
- package/lib/dbs/cds-deploy.js +12 -12
- package/lib/env/cds-env.js +1 -1
- package/lib/env/cds-requires.js +21 -20
- package/lib/env/defaults.js +2 -1
- package/lib/index.js +5 -6
- package/lib/log/cds-log.js +6 -5
- package/lib/log/format/aspects/cf.js +2 -2
- package/lib/plugins.js +1 -1
- package/lib/ql/UPDATE.js +3 -1
- package/lib/ql/cds-ql.js +0 -3
- package/lib/req/request.js +3 -3
- package/lib/req/response.js +12 -7
- package/lib/srv/bindings.js +17 -17
- package/lib/srv/cds-connect.js +6 -9
- package/lib/srv/cds-serve.js +74 -137
- package/lib/srv/cds.Service.js +49 -0
- package/lib/srv/factory.js +4 -4
- package/lib/srv/middlewares/auth/ias-auth.js +31 -11
- package/lib/srv/middlewares/auth/index.js +3 -2
- package/lib/srv/middlewares/auth/jwt-auth.js +19 -6
- package/lib/srv/protocols/hcql.js +16 -1
- package/lib/srv/srv-dispatch.js +1 -1
- package/lib/utils/cds-utils.js +4 -8
- package/lib/utils/csv-reader.js +27 -7
- package/libx/_runtime/cds.js +0 -6
- package/libx/_runtime/common/Service.js +5 -0
- package/libx/_runtime/common/generic/crud.js +1 -1
- package/libx/_runtime/common/generic/flows.js +106 -0
- package/libx/_runtime/common/generic/paging.js +3 -3
- package/libx/_runtime/common/utils/differ.js +5 -15
- package/libx/_runtime/common/utils/resolveView.js +2 -2
- package/libx/_runtime/common/utils/rewriteAsterisks.js +2 -2
- package/libx/_runtime/fiori/lean-draft.js +76 -40
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
- package/libx/_runtime/messaging/file-based.js +2 -1
- package/libx/_runtime/remote/Service.js +68 -62
- package/libx/_runtime/remote/utils/client.js +29 -216
- package/libx/_runtime/remote/utils/query.js +197 -0
- package/libx/_runtime/ucl/Service.js +180 -112
- package/libx/_runtime/ucl/queries.js +61 -0
- package/libx/odata/ODataAdapter.js +1 -4
- package/libx/odata/index.js +2 -10
- package/libx/odata/middleware/error.js +8 -1
- package/libx/odata/middleware/stream.js +1 -1
- package/libx/odata/middleware/update.js +12 -2
- package/libx/odata/parse/afterburner.js +113 -20
- package/libx/odata/parse/cqn2odata.js +1 -3
- package/libx/odata/parse/grammar.peggy +4 -2
- package/libx/odata/parse/parser.js +1 -1
- package/libx/queue/index.js +1 -1
- package/libx/rest/middleware/parse.js +9 -2
- package/package.json +2 -2
- package/server.js +2 -0
- package/srv/app-service.js +1 -0
- package/srv/db-service.js +1 -0
- package/srv/msg-service.js +1 -0
- package/srv/remote-service.js +1 -0
- package/srv/ucl-service.cds +32 -0
- package/srv/ucl-service.js +1 -0
- package/lib/ql/resolve.js +0 -45
- package/libx/common/assert/type-strict.js +0 -109
- package/libx/common/assert/utils.js +0 -60
|
@@ -8,6 +8,9 @@ const normalizeTimestamp = require('../../_runtime/common/utils/normalizeTimesta
|
|
|
8
8
|
const { rewriteExpandAsterisk } = require('../../_runtime/common/utils/rewriteAsterisks')
|
|
9
9
|
const resolveStructured = require('../../_runtime/common/utils/resolveStructured')
|
|
10
10
|
|
|
11
|
+
// Same regex as peggy parser
|
|
12
|
+
const RELAXED_UUID_REGEX = /^[0-9a-z]{8}-?[0-9a-z]{4}-?[0-9a-z]{4}-?[0-9a-z]{4}-?[0-9a-z]{12}$/i
|
|
13
|
+
|
|
11
14
|
function _getDefinition(definition, name, namespace) {
|
|
12
15
|
return (
|
|
13
16
|
definition?.definitions?.[name] ||
|
|
@@ -185,6 +188,13 @@ function _convertVal(value, element) {
|
|
|
185
188
|
case 'cds.Timestamp':
|
|
186
189
|
return normalizeTimestamp(value)
|
|
187
190
|
|
|
191
|
+
case 'cds.UUID':
|
|
192
|
+
if (!RELAXED_UUID_REGEX.test(value)) {
|
|
193
|
+
const msg = `Element "${element.name}" does not contain a valid UUID`
|
|
194
|
+
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
195
|
+
}
|
|
196
|
+
return value
|
|
197
|
+
|
|
188
198
|
default:
|
|
189
199
|
return value
|
|
190
200
|
}
|
|
@@ -528,6 +538,10 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
528
538
|
|
|
529
539
|
const AGGR_DFLT = '@Aggregation.default'
|
|
530
540
|
const CSTM_AGGR = '@Aggregation.CustomAggregate'
|
|
541
|
+
const SMTCS_CC = '@Semantics.currencyCode'
|
|
542
|
+
const SMTCS_UOM = '@Semantics.unitOfMeasure'
|
|
543
|
+
const SMTCS_AMT_CC = '@Semantics.amount.currencyCode'
|
|
544
|
+
const SMTCS_AMT_UOM = '@Semantics.amount.unitOfMeasure'
|
|
531
545
|
|
|
532
546
|
function _addKeys(columns, target) {
|
|
533
547
|
let hasAggregatedColumn = false,
|
|
@@ -586,17 +600,20 @@ const _structProperty = (ref, target) => {
|
|
|
586
600
|
}
|
|
587
601
|
|
|
588
602
|
function _processColumns(cqn, target, protocol) {
|
|
603
|
+
// Recursively process columns for nested SELECTs
|
|
589
604
|
if (cqn.SELECT.from.SELECT) _processColumns(cqn.SELECT.from, target)
|
|
590
605
|
|
|
591
606
|
let columns = cqn.SELECT.columns
|
|
592
607
|
|
|
608
|
+
// Error if groupBy is present but no columns are selected
|
|
593
609
|
if (columns && !columns.length && cqn.SELECT.groupBy) {
|
|
594
|
-
cds.error('Explicit select must include at least one column available in the result set of groupby
|
|
610
|
+
throw cds.error('Explicit select must include at least one column available in the result set of groupby', {
|
|
595
611
|
code: '400',
|
|
596
612
|
statusCode: 400
|
|
597
613
|
})
|
|
598
614
|
}
|
|
599
615
|
|
|
616
|
+
// If columns exist and no groupBy, handle asterisk and expand logic
|
|
600
617
|
if (columns && !cqn.SELECT.groupBy) {
|
|
601
618
|
let entity
|
|
602
619
|
if (target.kind === 'entity') entity = target
|
|
@@ -606,32 +623,108 @@ function _processColumns(cqn, target, protocol) {
|
|
|
606
623
|
_removeUnneededColumnsIfHasAsterisk(columns)
|
|
607
624
|
rewriteExpandAsterisk(columns, entity)
|
|
608
625
|
|
|
609
|
-
//
|
|
626
|
+
// For OData, add missing key fields to columns
|
|
610
627
|
if (protocol?.match(/odata/i)) _addKeys(columns, entity)
|
|
611
628
|
}
|
|
612
629
|
|
|
613
630
|
if (!Array.isArray(columns)) return
|
|
614
631
|
|
|
615
|
-
|
|
632
|
+
// Iterate columns to set default aggregation function if needed
|
|
616
633
|
for (let i = 0; i < columns.length; i++) {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
)
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
+
const processedColumn = columns[i]
|
|
635
|
+
|
|
636
|
+
// Skip if column is not an object or has a ref (not an aggregation)
|
|
637
|
+
if (typeof processedColumn !== 'object' || processedColumn.ref) continue
|
|
638
|
+
|
|
639
|
+
if (!processedColumn.args?.length) continue
|
|
640
|
+
if (!processedColumn.args[0].ref?.length) continue
|
|
641
|
+
const processedColumnRef = processedColumn.args[0].ref
|
|
642
|
+
|
|
643
|
+
// Extract relevant element characteristics
|
|
644
|
+
let aggregatedPropertyName, aggregatedElement
|
|
645
|
+
for (let refIdx = 0; refIdx < processedColumnRef.length; refIdx++) {
|
|
646
|
+
aggregatedPropertyName = processedColumnRef[refIdx]
|
|
647
|
+
if (aggregatedPropertyName.id) aggregatedPropertyName = aggregatedPropertyName.id
|
|
648
|
+
if (aggregatedElement && aggregatedElement.isAssociation) target = aggregatedElement._target
|
|
649
|
+
aggregatedElement = target.elements[aggregatedPropertyName]
|
|
650
|
+
}
|
|
651
|
+
const prefixRef = processedColumnRef.slice(0, processedColumnRef.length - 1)
|
|
652
|
+
const aggregatedElementRef = [...prefixRef, aggregatedPropertyName]
|
|
653
|
+
const isCurrencyCodeOrUnitOfMeasure = !!(aggregatedElement[SMTCS_CC] || aggregatedElement[SMTCS_UOM])
|
|
654
|
+
processedColumn.as = processedColumn.as || aggregatedPropertyName
|
|
655
|
+
|
|
656
|
+
// Specifically handle aggregating semantic amounts
|
|
657
|
+
if (isCurrencyCodeOrUnitOfMeasure) {
|
|
658
|
+
columns[i] = {
|
|
659
|
+
xpr: [
|
|
660
|
+
'case',
|
|
661
|
+
'when',
|
|
662
|
+
{ xpr: [{ func: 'max', args: [{ ref: aggregatedElementRef }] }, '=', { val: null }] },
|
|
663
|
+
'then',
|
|
664
|
+
{ val: '' },
|
|
665
|
+
'else',
|
|
666
|
+
{
|
|
667
|
+
xpr: [
|
|
668
|
+
'case',
|
|
669
|
+
'when',
|
|
670
|
+
{
|
|
671
|
+
xpr: [
|
|
672
|
+
{ func: 'min', args: [{ ref: aggregatedElementRef }] },
|
|
673
|
+
'=',
|
|
674
|
+
{ func: 'max', args: [{ ref: aggregatedElementRef }] }
|
|
675
|
+
]
|
|
676
|
+
},
|
|
677
|
+
'then',
|
|
678
|
+
{ func: 'min', args: [{ ref: aggregatedElementRef }] },
|
|
679
|
+
'else',
|
|
680
|
+
{ val: null },
|
|
681
|
+
'end'
|
|
682
|
+
]
|
|
683
|
+
},
|
|
684
|
+
'end'
|
|
685
|
+
],
|
|
686
|
+
as: processedColumn.as
|
|
634
687
|
}
|
|
688
|
+
|
|
689
|
+
continue
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Determine default aggregation function if necessary
|
|
693
|
+
const customAggregate = target[`${CSTM_AGGR}#${aggregatedPropertyName}`]
|
|
694
|
+
if (processedColumn.func === null) {
|
|
695
|
+
processedColumn.func = aggregatedElement?.[AGGR_DFLT]?.['#']?.toLowerCase()
|
|
696
|
+
if (!customAggregate)
|
|
697
|
+
throw cds.error(`Result type for custom aggregation of property "${aggregatedPropertyName}" not found`)
|
|
698
|
+
if (!processedColumn.func)
|
|
699
|
+
throw cds.error(`Default aggregation for property "${aggregatedPropertyName}" not found`)
|
|
700
|
+
if (processedColumn.func === 'count_distinct') processedColumn.func = 'countdistinct'
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Process Semantics Amount - Currency Code / Unit of Measure - if present
|
|
704
|
+
const semanticsAmountElementName = aggregatedElement?.[SMTCS_AMT_CC] ?? aggregatedElement?.[SMTCS_AMT_UOM]
|
|
705
|
+
if (!semanticsAmountElementName) continue
|
|
706
|
+
if (!target.elements[semanticsAmountElementName])
|
|
707
|
+
throw cds.error(`Referenced semantics amount element not found: ${semanticsAmountElementName}`)
|
|
708
|
+
const semanticsAmountElementRef = [...prefixRef, semanticsAmountElementName]
|
|
709
|
+
|
|
710
|
+
columns[i] = {
|
|
711
|
+
xpr: [
|
|
712
|
+
'case',
|
|
713
|
+
'when',
|
|
714
|
+
{
|
|
715
|
+
xpr: [
|
|
716
|
+
{ func: 'min', args: [{ ref: semanticsAmountElementRef }] },
|
|
717
|
+
'=',
|
|
718
|
+
{ func: 'max', args: [{ ref: semanticsAmountElementRef }] }
|
|
719
|
+
]
|
|
720
|
+
},
|
|
721
|
+
'then',
|
|
722
|
+
{ func: processedColumn.func, args: [{ ref: aggregatedElementRef }] },
|
|
723
|
+
'else',
|
|
724
|
+
{ val: null },
|
|
725
|
+
'end'
|
|
726
|
+
],
|
|
727
|
+
as: processedColumn.as
|
|
635
728
|
}
|
|
636
729
|
}
|
|
637
730
|
}
|
|
@@ -598,7 +598,7 @@ const _delete = (cqn, kind, model) => {
|
|
|
598
598
|
return { method: 'DELETE', path: `${url}${keys}` }
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
-
|
|
601
|
+
module.exports.cqn2odata = (cqn, { kind, model, method }) => {
|
|
602
602
|
if (cqn.SELECT) return _select(cqn, kind, model)
|
|
603
603
|
if (cqn.INSERT) return _insert(cqn, kind, model)
|
|
604
604
|
if (cqn.UPDATE) return _update(cqn, kind, model, method)
|
|
@@ -606,5 +606,3 @@ function cqn2odata(cqn, { kind, model, method }) {
|
|
|
606
606
|
|
|
607
607
|
throw new Error('Unknown CQN object cannot be translated to URL: ' + JSON.stringify(cqn))
|
|
608
608
|
}
|
|
609
|
-
|
|
610
|
-
module.exports = cqn2odata
|
|
@@ -208,7 +208,7 @@
|
|
|
208
208
|
if (
|
|
209
209
|
(topCqn.columns && topCqn.columns[0].as === '$count') ||
|
|
210
210
|
//In QUERY_WITH_AGGREGATION topCqn.where is a having and thus no further nesting needed
|
|
211
|
-
(!QUERY_WITH_AGGREGATION && topCqn.where && (cqn.SELECT.where ||
|
|
211
|
+
(!QUERY_WITH_AGGREGATION && topCqn.where && (cqn.SELECT.where || cqn.SELECT.limit)) ||
|
|
212
212
|
(cqn.SELECT.limit && topCqn.limit) ||
|
|
213
213
|
(cqn.SELECT.orderBy && topCqn.orderBy) ||
|
|
214
214
|
(cqn.SELECT.search && topCqn.search)
|
|
@@ -712,6 +712,8 @@
|
|
|
712
712
|
= (
|
|
713
713
|
c:('*' / ref) {
|
|
714
714
|
const col = c === '*' ? {} : c
|
|
715
|
+
if (col.ref?.length > 1) throw Object.assign(new Error(`navigation "${col.ref.join('/')}" in "$expand" is not supported`), { statusCode: 400 })
|
|
716
|
+
|
|
715
717
|
col.expand = ['*']
|
|
716
718
|
if (!Array.isArray(SELECT.expand)) SELECT.expand = []
|
|
717
719
|
if (!SELECT.expand.find(_compareRefs(col))) SELECT.expand.push(col)
|
|
@@ -986,7 +988,7 @@
|
|
|
986
988
|
// Loop through each element, add it to current level, if element is already part of result, increase level
|
|
987
989
|
for(let trafos of additionalTransformation) {
|
|
988
990
|
for(const transformation in trafos) {
|
|
989
|
-
if (transformation === 'where' && (mainTransformation.groupBy ||
|
|
991
|
+
if (transformation === 'where' && (mainTransformation.groupBy || mainTransformation.aggregate) && !mainTransformation.having) {
|
|
990
992
|
//When a group by or aggregate preceed a where, the where is a having
|
|
991
993
|
mainTransformation.having = trafos[transformation]
|
|
992
994
|
}
|