@sap/cds-compiler 3.8.0 → 3.9.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 +61 -0
- package/bin/cdsc.js +2 -2
- package/doc/CHANGELOG_BETA.md +26 -5
- package/lib/api/.eslintrc.json +3 -2
- package/lib/api/options.js +3 -1
- package/lib/api/validate.js +1 -1
- package/lib/base/message-registry.js +27 -18
- package/lib/base/messages.js +6 -1
- package/lib/base/model.js +2 -2
- package/lib/checks/.eslintrc.json +1 -0
- package/lib/checks/actionsFunctions.js +6 -6
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/elements.js +28 -17
- package/lib/checks/foreignKeys.js +1 -1
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/onConditions.js +11 -6
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/types.js +1 -1
- package/lib/checks/utils.js +1 -1
- package/lib/checks/validator.js +3 -2
- package/lib/compiler/assert-consistency.js +8 -3
- package/lib/compiler/base.js +19 -13
- package/lib/compiler/builtins.js +7 -0
- package/lib/compiler/checks.js +73 -6
- package/lib/compiler/define.js +10 -5
- package/lib/compiler/extend.js +924 -1709
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +838 -0
- package/lib/compiler/index.js +2 -0
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +20 -8
- package/lib/compiler/resolve.js +3 -3
- package/lib/compiler/shared.js +11 -6
- package/lib/edm/annotations/genericTranslation.js +6 -6
- package/lib/edm/csn2edm.js +1 -1
- package/lib/edm/edm.js +25 -11
- package/lib/edm/edmPreprocessor.js +47 -23
- package/lib/edm/edmUtils.js +37 -9
- package/lib/gen/Dictionary.json +5 -7
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/language.tokens +24 -23
- package/lib/gen/languageLexer.interp +4 -1
- package/lib/gen/languageLexer.js +792 -784
- package/lib/gen/languageLexer.tokens +12 -11
- package/lib/gen/languageParser.js +3944 -3865
- package/lib/json/from-csn.js +27 -6
- package/lib/json/to-csn.js +10 -6
- package/lib/language/antlrParser.js +11 -3
- package/lib/language/genericAntlrParser.js +4 -2
- package/lib/language/language.g4 +32 -24
- package/lib/model/csnRefs.js +15 -7
- package/lib/model/csnUtils.js +41 -76
- package/lib/modelCompare/utils/.eslintrc.json +1 -1
- package/lib/optionProcessor.js +7 -4
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/toCdl.js +244 -168
- package/lib/render/toHdbcds.js +18 -10
- package/lib/render/toSql.js +24 -2
- package/lib/transform/db/.eslintrc.json +4 -3
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +11 -6
- package/lib/transform/db/flattening.js +22 -15
- package/lib/transform/db/rewriteCalculatedElements.js +50 -29
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/views.js +1 -1
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/draft/odata.js +3 -4
- package/lib/transform/forOdataNew.js +5 -6
- package/lib/transform/forRelationalDB.js +7 -7
- package/lib/transform/odata/toFinalBaseType.js +6 -6
- package/lib/transform/odata/typesExposure.js +12 -3
- package/lib/transform/odata/utils.js +3 -0
- package/lib/transform/transformUtilsNew.js +11 -26
- package/lib/transform/translateAssocsToJoins.js +9 -9
- package/lib/transform/universalCsn/.eslintrc.json +3 -2
- package/lib/transform/universalCsn/coreComputed.js +1 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +6 -4
- package/package.json +1 -1
package/lib/json/from-csn.js
CHANGED
|
@@ -135,7 +135,18 @@ const xorGroups = {
|
|
|
135
135
|
// quantifiers 'some' and 'any are 'xpr' token strings in CSN v1.0
|
|
136
136
|
};
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Properties that are required next to `=` to make an annotation value an actual expression
|
|
140
|
+
* and not some foreign structure.
|
|
141
|
+
*
|
|
142
|
+
* @type {string[]}
|
|
143
|
+
*/
|
|
144
|
+
const xprInAnnoProperties = [
|
|
145
|
+
'ref', 'xpr', 'list', 'literal', 'val',
|
|
146
|
+
'#', 'func', 'args', 'SELECT', 'SET',
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
// Functions reading properties which do not count for the message
|
|
139
150
|
// 'Object in $(PROP) must have at least one property'
|
|
140
151
|
const functionsOfIrrelevantProps = [ ignore, extra, explicitName ];
|
|
141
152
|
|
|
@@ -688,6 +699,10 @@ const schema = compileSchema( {
|
|
|
688
699
|
vZeroFor: 'val', // CSN v0.1.0 property for `val` in enum def
|
|
689
700
|
// type: annoValue,
|
|
690
701
|
inKind: [ 'element' ],
|
|
702
|
+
optional: exprProperties.concat([ 'stored' ]),
|
|
703
|
+
},
|
|
704
|
+
stored: {
|
|
705
|
+
type: boolOrNull,
|
|
691
706
|
},
|
|
692
707
|
// ignored: ----------------------------------------------------------------
|
|
693
708
|
$location: { // special
|
|
@@ -1354,8 +1369,15 @@ function annoValue( val, spec ) {
|
|
|
1354
1369
|
return retval;
|
|
1355
1370
|
}
|
|
1356
1371
|
else if (typeof val['='] === 'string') {
|
|
1372
|
+
// An object with `=` is an expression if and only if:
|
|
1373
|
+
// - there is exactly one property ('=')
|
|
1374
|
+
// - there is at least one other expression property (e.g. "xpr")
|
|
1375
|
+
// TODO: Have xprInAnnoProperties centrally for other backends to use as well (toCdl)
|
|
1357
1376
|
const valKeys = Object.keys(val);
|
|
1358
|
-
if (valKeys.length > 1 &&
|
|
1377
|
+
if (valKeys.length > 1 &&
|
|
1378
|
+
(isBetaEnabled(userOptions, 'annotationExpressions') ||
|
|
1379
|
+
isBetaEnabled(userOptions, 'v4preview')) &&
|
|
1380
|
+
xprInAnnoProperties.some(prop => val[prop] !== undefined)) {
|
|
1359
1381
|
const s = schema['@'].schema['-expr'];
|
|
1360
1382
|
const r = { location: location() };
|
|
1361
1383
|
Object.assign(r, object(val, s));
|
|
@@ -1367,6 +1389,7 @@ function annoValue( val, spec ) {
|
|
|
1367
1389
|
++virtualLine;
|
|
1368
1390
|
return r;
|
|
1369
1391
|
}
|
|
1392
|
+
// fallthrough -> unchecked structure
|
|
1370
1393
|
}
|
|
1371
1394
|
if (typeof val['#'] === 'string') {
|
|
1372
1395
|
if (Object.keys( val ).length === 1) {
|
|
@@ -1409,8 +1432,7 @@ function annotation( val, spec, xsn, csn, name ) {
|
|
|
1409
1432
|
return undefined;
|
|
1410
1433
|
n.absolute = absolute;
|
|
1411
1434
|
if (variantIndex < absolute.length)
|
|
1412
|
-
n.variant =
|
|
1413
|
-
|
|
1435
|
+
n.variant = refSplit( name.substring( variantIndex ), null );
|
|
1414
1436
|
const r = annoValue( val, spec );
|
|
1415
1437
|
r.name = n;
|
|
1416
1438
|
return r;
|
|
@@ -1726,8 +1748,7 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
|
|
|
1726
1748
|
{
|
|
1727
1749
|
'#': variant,
|
|
1728
1750
|
prop,
|
|
1729
|
-
parentprop:
|
|
1730
|
-
parentSpec.msgProp,
|
|
1751
|
+
parentprop: parentSpec.msgProp,
|
|
1731
1752
|
kind,
|
|
1732
1753
|
} );
|
|
1733
1754
|
}
|
package/lib/json/to-csn.js
CHANGED
|
@@ -431,14 +431,15 @@ function i18n( i18nNode ) {
|
|
|
431
431
|
return csn;
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
-
function sources( srcDict, csn ) {
|
|
435
|
-
|
|
434
|
+
function sources( srcDict, csn, model ) {
|
|
435
|
+
let names = model._sources || Object.keys( srcDict );
|
|
436
436
|
const $sources = names.length && srcDict[names[0]].$sources;
|
|
437
437
|
if ($sources) {
|
|
438
438
|
setHidden( csn, '$sources', $sources );
|
|
439
439
|
return undefined;
|
|
440
440
|
}
|
|
441
|
-
|
|
441
|
+
if (model._sortedSources)
|
|
442
|
+
names = model._sortedSources.map( s => s.realname );
|
|
442
443
|
setHidden( csn, '$sources', (!strictMode) ? names : names.map( relativeName ) );
|
|
443
444
|
return undefined;
|
|
444
445
|
|
|
@@ -1242,10 +1243,13 @@ function enumValueOrCalc( v, csn, node ) {
|
|
|
1242
1243
|
if (v.$inferred && (universalCsn || gensrcFlavor))
|
|
1243
1244
|
return undefined;
|
|
1244
1245
|
// Enums can have values but if enums are extended, their kind is 'element'
|
|
1245
|
-
if (node.kind === 'enum' || node.$syntax === 'enum')
|
|
1246
|
+
if (node.kind === 'enum' || node.$syntax === 'enum') {
|
|
1246
1247
|
Object.assign( csn, expression( v ) );
|
|
1247
|
-
|
|
1248
|
-
|
|
1248
|
+
}
|
|
1249
|
+
else if (node.$syntax === 'calc') { // TODO: || node._parent?.kind === 'extend'
|
|
1250
|
+
const stored = v.stored ? { stored: value(v.stored) } : {};
|
|
1251
|
+
return Object.assign( stored, expression( v ) );
|
|
1252
|
+
}
|
|
1249
1253
|
return undefined;
|
|
1250
1254
|
}
|
|
1251
1255
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
const antlr4 = require('antlr4');
|
|
12
12
|
|
|
13
13
|
const { CompileMessage } = require('../base/messages');
|
|
14
|
+
const { isBetaEnabled } = require('../base/model');
|
|
14
15
|
const errorStrategy = require('./errorStrategy');
|
|
15
16
|
|
|
16
17
|
const Parser = require('../gen/languageParser').default;
|
|
@@ -29,6 +30,11 @@ class ErrorListener extends antlr4.error.ErrorListener {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
class RewriteTypeTokenStream extends antlr4.CommonTokenStream {
|
|
33
|
+
constructor(lexer, v4newKeyword) {
|
|
34
|
+
super(lexer);
|
|
35
|
+
this.v4newKeyword = v4newKeyword;
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
LT( k ) {
|
|
33
39
|
const t = super.LT(k);
|
|
34
40
|
if (!t || !t.type)
|
|
@@ -49,9 +55,10 @@ class RewriteTypeTokenStream extends antlr4.CommonTokenStream {
|
|
|
49
55
|
}
|
|
50
56
|
else if (t.type === this.NEW) {
|
|
51
57
|
const n = super.LT(k + 1);
|
|
52
|
-
|
|
58
|
+
// TODO v4: rewrite token in grammar via `this.setLocalToken`
|
|
59
|
+
if (n?.type === this.Identifier && (this.v4newKeyword || /^st_/i.test( n.text ))) {
|
|
53
60
|
const o = super.LT(k + 2);
|
|
54
|
-
if (o
|
|
61
|
+
if (o?.type === this.PAREN)
|
|
55
62
|
return t;
|
|
56
63
|
}
|
|
57
64
|
t.type = this.Identifier;
|
|
@@ -111,7 +118,8 @@ function parse( source, filename = '<undefined>.cds',
|
|
|
111
118
|
options = {}, messageFunctions = null,
|
|
112
119
|
rule = 'cdl' ) {
|
|
113
120
|
const lexer = new Lexer( new antlr4.InputStream(source) );
|
|
114
|
-
const
|
|
121
|
+
const v4newKeyword = isBetaEnabled(options, 'v4preview');
|
|
122
|
+
const tokenStream = new RewriteTypeTokenStream(lexer, v4newKeyword);
|
|
115
123
|
/** @type {object} */
|
|
116
124
|
const parser = new Parser( tokenStream );
|
|
117
125
|
const errorListener = new ErrorListener();
|
|
@@ -974,7 +974,8 @@ function pushIdent( path, ident, prefix ) {
|
|
|
974
974
|
}
|
|
975
975
|
|
|
976
976
|
// For :param, #variant, #symbol, @(…) and @Begin and `@` inside annotation paths
|
|
977
|
-
function reportUnexpectedSpace( prefix
|
|
977
|
+
function reportUnexpectedSpace( prefix = this._input.LT(-1),
|
|
978
|
+
location = this.tokenLocation( this._input.LT(1) ) ) {
|
|
978
979
|
const prefixLoc = this.tokenLocation( prefix );
|
|
979
980
|
if (prefixLoc.endLine !== location.line ||
|
|
980
981
|
prefixLoc.endCol !== location.col) {
|
|
@@ -1094,7 +1095,8 @@ function addExtension( ext, parent, kind, artName, elemPath ) {
|
|
|
1094
1095
|
}
|
|
1095
1096
|
|
|
1096
1097
|
function aspectWithoutElements( art ) {
|
|
1097
|
-
// Empty dictionary to allow element extensions.
|
|
1098
|
+
// Empty dictionary to allow element extensions. NO, please NO empty dict.
|
|
1099
|
+
// TODO: Checking it here does not prevent aspect in CSN input having no elements!
|
|
1098
1100
|
art.elements = this.createDict();
|
|
1099
1101
|
if (!isBetaEnabled( this.options, 'aspectWithoutElements' )) {
|
|
1100
1102
|
this.error( null, [ art.name.location ], {},
|
package/lib/language/language.g4
CHANGED
|
@@ -607,10 +607,14 @@ elementDefInner[ art, outer, mightBeEnum ]
|
|
|
607
607
|
':'
|
|
608
608
|
elementType[ $art, $mightBeEnum ]
|
|
609
609
|
|
|
|
610
|
-
'=' e=expression // SQL has syntax variant using AS - we DO NOT
|
|
610
|
+
eq='=' e=expression // SQL has syntax variant using AS - we DO NOT
|
|
611
|
+
stored=STORED?
|
|
611
612
|
{ $art.value = $e.expr;
|
|
612
613
|
// this.setIntroLocation( eq ); -- future
|
|
613
|
-
if ($
|
|
614
|
+
if ($stored)
|
|
615
|
+
$art.value.stored = this.valueWithTokenLocation( true, $stored );
|
|
616
|
+
if ($mightBeEnum && !$stored &&
|
|
617
|
+
($e.expr?.val !== undefined || $e.expr?.sym !== undefined) &&
|
|
614
618
|
!$virtual && !$key && !$masked && !$art.elements && !$art.type)
|
|
615
619
|
$art['$'+'syntax'] = 'enum';
|
|
616
620
|
}
|
|
@@ -721,7 +725,12 @@ elementProperties[ elem ]
|
|
|
721
725
|
:
|
|
722
726
|
defaultAndNullablity[ $elem ]
|
|
723
727
|
|
|
|
724
|
-
'=' e=expression
|
|
728
|
+
'=' e=expression
|
|
729
|
+
stored=STORED?
|
|
730
|
+
{ $elem.value = $e.expr;
|
|
731
|
+
if ($stored)
|
|
732
|
+
$elem.value.stored = this.valueWithTokenLocation( true, $stored );
|
|
733
|
+
}
|
|
725
734
|
;
|
|
726
735
|
|
|
727
736
|
defaultAndNullablity[ elem ]
|
|
@@ -2583,7 +2592,7 @@ annotationAssignment_fix[ art ] locals[ assignment ]
|
|
|
2583
2592
|
|
|
|
2584
2593
|
{ $assignment = { name: {} }; }
|
|
2585
2594
|
annotationPath[ $assignment.name, 'anno' ]
|
|
2586
|
-
annotationPathVariant[ $assignment.name ]?
|
|
2595
|
+
( '#' annotationPathVariant[ $assignment.name ] )?
|
|
2587
2596
|
{ this.warnIfColonFollows( $assignment ); }
|
|
2588
2597
|
)
|
|
2589
2598
|
;
|
|
@@ -2601,7 +2610,7 @@ annotationAssignment_ll1[ art ] locals[ assignment ]
|
|
|
2601
2610
|
|
|
|
2602
2611
|
{ $assignment = { name: {} }; }
|
|
2603
2612
|
annotationPath[ $assignment.name, 'anno' ]
|
|
2604
|
-
annotationPathVariant[ $assignment.name ]?
|
|
2613
|
+
( '#' annotationPathVariant[ $assignment.name ] )?
|
|
2605
2614
|
(
|
|
2606
2615
|
':' { this.meltKeywordToIdentifier(true); } // allow path as anno value start with reserved
|
|
2607
2616
|
val=annoValue[ $assignment ]
|
|
@@ -2627,11 +2636,7 @@ annotationAssignment_atn[ art ] locals[ assignment ]
|
|
|
2627
2636
|
// before an "expression" which can start with a '#' for an enum value
|
|
2628
2637
|
// -> used to introduce variant name if and only if in same line as previous token
|
|
2629
2638
|
{ this.setLocalToken( '#', 'HelperToken1', null, true ); }
|
|
2630
|
-
(
|
|
2631
|
-
hash=HelperToken1
|
|
2632
|
-
{ this.meltKeywordToIdentifier();; this.reportUnexpectedSpace( $hash ); }
|
|
2633
|
-
variant=ident['variant'] { $assignment.name.variant = $variant.id; }
|
|
2634
|
-
)?
|
|
2639
|
+
( hash=HelperToken1 annotationPathVariant[ $assignment.name ] )?
|
|
2635
2640
|
// ':' is in the follow set of this rule, as it is used in rule "selectItemDef"
|
|
2636
2641
|
// before an "expression" which can start with a ':' for a parameter reference
|
|
2637
2642
|
// -> used to introduce assignment value if and only if in same line as previous token
|
|
@@ -2643,11 +2648,7 @@ annotationAssignment_atn[ art ] locals[ assignment ]
|
|
|
2643
2648
|
|
|
|
2644
2649
|
atv='@'? annotationPath[ $assignment, 'ref', $atv ]
|
|
2645
2650
|
{ this.setLocalToken( '#', 'HelperToken1', null, true ); } // see above
|
|
2646
|
-
(
|
|
2647
|
-
hash=HelperToken1
|
|
2648
|
-
{ this.meltKeywordToIdentifier();; this.reportUnexpectedSpace( $hash ); }
|
|
2649
|
-
variant=ident['variant'] { $assignment.variant = $variant.id; }
|
|
2650
|
-
)?
|
|
2651
|
+
( hash=HelperToken1 annotationPathVariant[ $assignment ] )?
|
|
2651
2652
|
)
|
|
2652
2653
|
)?
|
|
2653
2654
|
)
|
|
@@ -2658,11 +2659,11 @@ annotationAssignment_paren[ art ]
|
|
|
2658
2659
|
'('
|
|
2659
2660
|
// allow completely useless `@()`; no warning anymore - who cares?
|
|
2660
2661
|
{
|
|
2661
|
-
this.meltKeywordToIdentifier();
|
|
2662
2662
|
if (this.isStraightBefore(')')) {
|
|
2663
2663
|
this.matchWildcard(); // we know it is the ')' - we do not reach the final match
|
|
2664
2664
|
return $ctx;
|
|
2665
2665
|
}
|
|
2666
|
+
this.meltKeywordToIdentifier();
|
|
2666
2667
|
}
|
|
2667
2668
|
annotationAssignment_1[ $art ]
|
|
2668
2669
|
( ','
|
|
@@ -2679,7 +2680,7 @@ annotationAssignment_1[ art ] locals[ assignment = { name: {} } ]
|
|
|
2679
2680
|
@after { this.assignAnnotation( $art, $assignment ); }
|
|
2680
2681
|
:
|
|
2681
2682
|
annotationPath[ $assignment.name, 'anno' ]
|
|
2682
|
-
annotationPathVariant[ $assignment.name ]?
|
|
2683
|
+
( '#' annotationPathVariant[ $assignment.name ] )?
|
|
2683
2684
|
(
|
|
2684
2685
|
':' { this.meltKeywordToIdentifier(true); } // allow path as anno value start with reserved
|
|
2685
2686
|
val=annoValue[ $assignment ]
|
|
@@ -2704,11 +2705,11 @@ annotationPath[ art, category, headat = null ] locals[ _sync = 'nop' ]
|
|
|
2704
2705
|
)*
|
|
2705
2706
|
;
|
|
2706
2707
|
|
|
2708
|
+
// Before calling this rule, match '#'
|
|
2707
2709
|
annotationPathVariant[ art ] locals[ variant = {} ]
|
|
2708
2710
|
@after { this.attachLocation($art); }
|
|
2709
2711
|
:
|
|
2710
|
-
|
|
2711
|
-
hash='#' { this.meltKeywordToIdentifier();; this.reportUnexpectedSpace( $hash ); }
|
|
2712
|
+
{ this.reportUnexpectedSpace();; this.meltKeywordToIdentifier(); }
|
|
2712
2713
|
simplePath[ $variant, 'variant' ] { $art.variant = $variant; }
|
|
2713
2714
|
;
|
|
2714
2715
|
|
|
@@ -2719,7 +2720,7 @@ annoValue[ assignment ]
|
|
|
2719
2720
|
// no docComment() here
|
|
2720
2721
|
// this alternative is done with token rewrite in rule "annotationAssignment_atn"
|
|
2721
2722
|
at='@'? annotationPath[ $assignment, 'ref', $at ]
|
|
2722
|
-
annotationPathVariant[ $assignment ]?
|
|
2723
|
+
( '#' annotationPathVariant[ $assignment ] )?
|
|
2723
2724
|
;
|
|
2724
2725
|
|
|
2725
2726
|
annoValueBase[ assignment ] locals [ seenEllipsis = false ]
|
|
@@ -2739,6 +2740,7 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
|
|
|
2739
2740
|
|
|
|
2740
2741
|
'[' // no need for createArray() here, $assignment.location is set
|
|
2741
2742
|
{ $assignment.val = []; $assignment.literal = 'array'; }
|
|
2743
|
+
{ this.meltKeywordToIdentifier(true); }
|
|
2742
2744
|
(
|
|
2743
2745
|
(
|
|
2744
2746
|
head=annoSubValue { $assignment.val.push( $head.val ); }
|
|
@@ -2753,6 +2755,7 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
|
|
|
2753
2755
|
)
|
|
2754
2756
|
(
|
|
2755
2757
|
',' { if (this.isStraightBefore(']')) break; } // allow ',' before ']'
|
|
2758
|
+
{ this.meltKeywordToIdentifier(true); }
|
|
2756
2759
|
(
|
|
2757
2760
|
tail=annoSubValue { $assignment.val.push( $tail.val ); }
|
|
2758
2761
|
|
|
|
@@ -2794,7 +2797,7 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
|
|
|
2794
2797
|
flattenedValue[ assignment ] locals[ val = { name: {} } ]
|
|
2795
2798
|
:
|
|
2796
2799
|
at='@'? annotationPath[ $val.name, 'name', $at ]
|
|
2797
|
-
( annotationPathVariant[ $val.name ] )?
|
|
2800
|
+
( '#' annotationPathVariant[ $val.name ] )?
|
|
2798
2801
|
(
|
|
2799
2802
|
':' { this.meltKeywordToIdentifier(true); } // allow path as anno value start with reserved
|
|
2800
2803
|
annoValue[ $val ]
|
|
@@ -2805,7 +2808,8 @@ flattenedValue[ assignment ] locals[ val = { name: {} } ]
|
|
|
2805
2808
|
namedValue[ struct ] locals[ val = { name: {} } ]
|
|
2806
2809
|
:
|
|
2807
2810
|
at='@'? annotationPath[ $val.name, 'name', $at ]
|
|
2808
|
-
( ':'
|
|
2811
|
+
( ':' { this.meltKeywordToIdentifier(true); }
|
|
2812
|
+
sub=annoSubValue { Object.assign( $val, $sub.val ); } )?
|
|
2809
2813
|
{
|
|
2810
2814
|
if (!$val.location) $val.location = $val.name.location;
|
|
2811
2815
|
this.addDef( $val, $struct, 'struct', null, $val.name ); // TODO: re-check name
|
|
@@ -2830,8 +2834,10 @@ annoSubValue returns[ val = {} ]
|
|
|
2830
2834
|
|
|
|
2831
2835
|
'[' // no need for createArray() here, $val.location is set
|
|
2832
2836
|
{ $val.val = []; $val.literal = 'array'; }
|
|
2837
|
+
{ this.meltKeywordToIdentifier(true); }
|
|
2833
2838
|
( head=annoSubValue { $val.val.push( $head.val ); }
|
|
2834
2839
|
( ',' { if (this.isStraightBefore(']')) break; } // allow ',' before ']'
|
|
2840
|
+
{ this.meltKeywordToIdentifier(true); }
|
|
2835
2841
|
tail=annoSubValue { $val.val.push( $tail.val ); }
|
|
2836
2842
|
)*
|
|
2837
2843
|
)?
|
|
@@ -2843,7 +2849,7 @@ annoSubValue returns[ val = {} ]
|
|
|
2843
2849
|
{ Object.assign( $val, this.numberLiteral( $num, $plus||$min ) ); }
|
|
2844
2850
|
|
|
|
2845
2851
|
at='@'? annotationPath[ $val, 'ref', $at ]
|
|
2846
|
-
( annotationPathVariant[ $val ] )?
|
|
2852
|
+
( '#' annotationPathVariant[ $val ] )?
|
|
2847
2853
|
|
|
|
2848
2854
|
'('
|
|
2849
2855
|
cond=condition
|
|
@@ -2857,7 +2863,7 @@ literalValue returns[ val ] locals[ tok ]
|
|
|
2857
2863
|
@init{ $tok = this.getCurrentToken(); }
|
|
2858
2864
|
@after { this.attachLocation($val); }
|
|
2859
2865
|
:
|
|
2860
|
-
hash='#' { this.
|
|
2866
|
+
hash='#' { this.reportUnexpectedSpace( $hash );; this.meltKeywordToIdentifier(); }
|
|
2861
2867
|
name=ident['enumref'] // TODO v4: remove from this rule (not in enum!)
|
|
2862
2868
|
{ $val = { literal: 'enum', sym: $name.id } }
|
|
2863
2869
|
|
|
|
@@ -2959,6 +2965,7 @@ ident[ category ] returns[ id ]
|
|
|
2959
2965
|
| RIGHT
|
|
2960
2966
|
| ROW
|
|
2961
2967
|
| ROWS
|
|
2968
|
+
| STORED
|
|
2962
2969
|
| SERVICE
|
|
2963
2970
|
| THEN
|
|
2964
2971
|
| UNION
|
|
@@ -3158,6 +3165,7 @@ RIGHT : [rR][iI][gG][hH][tT] ;
|
|
|
3158
3165
|
ROW : [rR][oO][wW] ;
|
|
3159
3166
|
ROWS : [rR][oO][wW][sS] ;
|
|
3160
3167
|
SERVICE : [sS][eE][rR][vV][iI][cC][eE] ;
|
|
3168
|
+
STORED : [sS][tT][oO][rR][eE][dD] ;
|
|
3161
3169
|
THEN : [tT][hH][eE][nN] ;
|
|
3162
3170
|
TO : [tT][oO] ; // or make reserved? (is in SQL-92)
|
|
3163
3171
|
TYPE : [tT][yY][pP][eE] ;
|
package/lib/model/csnRefs.js
CHANGED
|
@@ -66,8 +66,11 @@
|
|
|
66
66
|
// - function `initColumnElement`: issue with column which is neither `*` nor
|
|
67
67
|
// a `ref` with sibling `inline`, but still has no corresponding element
|
|
68
68
|
|
|
69
|
-
// The functions in this module also use an internal cache
|
|
70
|
-
//
|
|
69
|
+
// The functions in this module also use an internal cache, which can be dropped
|
|
70
|
+
// for a single definition (main artifact) with function dropDefinitionCache().
|
|
71
|
+
|
|
72
|
+
// When modifying the CSN, caches might need to be invalidated. In the following
|
|
73
|
+
// example, the second call of inspectRef() might lead to a wrong result or an
|
|
71
74
|
// exception if the assignment to `inspectRef` is not uncommented:
|
|
72
75
|
//
|
|
73
76
|
// let { inspectRef } = csnRefs( csn );
|
|
@@ -517,7 +520,7 @@ function csnRefs( csn, universalReady ) {
|
|
|
517
520
|
/**
|
|
518
521
|
* @param {CSN.Path} csnPath
|
|
519
522
|
*
|
|
520
|
-
* - return value `art`: the “resulting” CSN of the reference
|
|
523
|
+
* - return value `art`: the “resulting” CSN node of the reference
|
|
521
524
|
*
|
|
522
525
|
* - return value `links`: array of { art, env } in length of ref.path where
|
|
523
526
|
* art = the definition or element reached by the ref path so far
|
|
@@ -620,6 +623,9 @@ function csnRefs( csn, universalReady ) {
|
|
|
620
623
|
return resolvePath( path, art, parent, 'parent' );
|
|
621
624
|
}
|
|
622
625
|
|
|
626
|
+
if (!qcache)
|
|
627
|
+
throw new CompilerAssertion( `Query not in cache at: ${ locationString(query.$location) }` );
|
|
628
|
+
|
|
623
629
|
if (semantics.dynamic === 'query')
|
|
624
630
|
// TODO: for ON condition in expand, would need to use cached _element
|
|
625
631
|
return resolvePath( path, qcache.elements[head], null, 'query' );
|
|
@@ -882,8 +888,8 @@ function traverseFrom( from, fromSelect, parentQuery, callback ) {
|
|
|
882
888
|
}
|
|
883
889
|
else if (from.args) { // join
|
|
884
890
|
from.args.forEach( arg => traverseFrom( arg, fromSelect, parentQuery, callback ) );
|
|
885
|
-
if (from.on) // join-on, potentially having a sub query
|
|
886
|
-
from.on.forEach(
|
|
891
|
+
if (from.on) // join-on, potentially having a sub query (in xpr)
|
|
892
|
+
from.on.forEach(arg => traverseExpr(arg, fromSelect, callback));
|
|
887
893
|
}
|
|
888
894
|
else { // sub query in FROM
|
|
889
895
|
traverseQuery( from, fromSelect, parentQuery, callback );
|
|
@@ -989,6 +995,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
989
995
|
/** @type {boolean|string|number} */
|
|
990
996
|
let isName = false;
|
|
991
997
|
let baseRef = null;
|
|
998
|
+
let baseCtx = null;
|
|
992
999
|
let baseEnv = null;
|
|
993
1000
|
let {
|
|
994
1001
|
index, main, parent, art,
|
|
@@ -1046,13 +1053,13 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
1046
1053
|
}
|
|
1047
1054
|
else if (prop === 'where' && refCtx === 'ref') {
|
|
1048
1055
|
if (resolve)
|
|
1049
|
-
baseEnv = resolve.ref_where( obj, baseRef,
|
|
1056
|
+
baseEnv = resolve.ref_where( obj, baseRef, baseCtx, main, query, parent, baseEnv );
|
|
1050
1057
|
refCtx = 'ref_where';
|
|
1051
1058
|
}
|
|
1052
1059
|
else if (prop === 'expand' || prop === 'inline') {
|
|
1053
1060
|
if (obj.ref) {
|
|
1054
1061
|
if (resolve)
|
|
1055
|
-
baseEnv = resolve.expandInline( obj,
|
|
1062
|
+
baseEnv = resolve.expandInline( obj, baseCtx, main, query, parent, baseEnv );
|
|
1056
1063
|
refCtx = prop;
|
|
1057
1064
|
}
|
|
1058
1065
|
isName = prop;
|
|
@@ -1067,6 +1074,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
1067
1074
|
}
|
|
1068
1075
|
else if (prop === 'ref') {
|
|
1069
1076
|
baseRef = obj; // needs to be inspected for filter conditions
|
|
1077
|
+
baseCtx = refCtx;
|
|
1070
1078
|
refCtx = prop;
|
|
1071
1079
|
}
|
|
1072
1080
|
else if (prop === 'orderBy') {
|
package/lib/model/csnUtils.js
CHANGED
|
@@ -47,13 +47,12 @@ function getUtils( model, universalReady ) {
|
|
|
47
47
|
const special$self = !model?.definitions?.$self && '$self';
|
|
48
48
|
const _csnRefs = csnRefs(model, universalReady);
|
|
49
49
|
const { artifactRef } = _csnRefs;
|
|
50
|
-
/** Cache for
|
|
50
|
+
/** Cache for getFinalTypeInfo(). Specific to the current model. */
|
|
51
51
|
const finalBaseTypeCache = Object.create(null);
|
|
52
52
|
|
|
53
53
|
return {
|
|
54
54
|
getCsnDef,
|
|
55
55
|
isStructured,
|
|
56
|
-
getFinalType,
|
|
57
56
|
isManagedAssociation,
|
|
58
57
|
isAssocOrComposition,
|
|
59
58
|
isAssociation,
|
|
@@ -63,7 +62,7 @@ function getUtils( model, universalReady ) {
|
|
|
63
62
|
addStringAnnotationTo,
|
|
64
63
|
getServiceName,
|
|
65
64
|
hasAnnotationValue,
|
|
66
|
-
|
|
65
|
+
getFinalTypeInfo,
|
|
67
66
|
get$combined,
|
|
68
67
|
getQueryPrimarySource,
|
|
69
68
|
..._csnRefs,
|
|
@@ -136,7 +135,7 @@ function getUtils( model, universalReady ) {
|
|
|
136
135
|
elements = mergeElementsIntoMap(elements, art.elements, art.$location, arg.as || implicitAs(arg.ref), implicitAs(arg.ref) || arg.as);
|
|
137
136
|
}
|
|
138
137
|
else if (arg.SELECT || arg.SET) {
|
|
139
|
-
elements = mergeElementMaps(elements, getSources(arg));
|
|
138
|
+
elements = mergeElementMaps(elements, getSources(arg, true));
|
|
140
139
|
}
|
|
141
140
|
}
|
|
142
141
|
|
|
@@ -224,18 +223,7 @@ function getUtils( model, universalReady ) {
|
|
|
224
223
|
* @returns {boolean}
|
|
225
224
|
*/
|
|
226
225
|
function isStructured( obj ) {
|
|
227
|
-
return !!(obj.elements || (obj.type &&
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Resolves typedefs to its final type (name) which is returned.
|
|
232
|
-
* @param {string|object} typeName Absolute type name or type ref ({ref: [...]}).
|
|
233
|
-
* @returns {string|object}
|
|
234
|
-
*/
|
|
235
|
-
function getFinalType( typeName ) {
|
|
236
|
-
if (!typeName)
|
|
237
|
-
return typeName;
|
|
238
|
-
return getFinalBaseTypeWithProps(typeName)?.type || null;
|
|
226
|
+
return !!(obj.elements || (obj.type && getFinalTypeInfo(obj.type)?.elements));
|
|
239
227
|
}
|
|
240
228
|
|
|
241
229
|
// Return true if 'node' is a managed association element
|
|
@@ -247,30 +235,30 @@ function getUtils( model, universalReady ) {
|
|
|
247
235
|
/**
|
|
248
236
|
* Returns if a type is an association or a composition (possibly via type chain).
|
|
249
237
|
*
|
|
250
|
-
* @param {
|
|
238
|
+
* @param {object} artifact Element or other artifact.
|
|
251
239
|
*/
|
|
252
|
-
function isAssocOrComposition(
|
|
253
|
-
const finalType =
|
|
240
|
+
function isAssocOrComposition( artifact ) {
|
|
241
|
+
const finalType = artifact && getFinalTypeInfo( artifact.type )?.type;
|
|
254
242
|
return (finalType === 'cds.Association' || finalType === 'cds.Composition');
|
|
255
243
|
}
|
|
256
244
|
|
|
257
245
|
/**
|
|
258
246
|
* Returns true if a type is an association (possibly via type chain).
|
|
259
247
|
*
|
|
260
|
-
* @param {
|
|
248
|
+
* @param {object} artifact Element or other artifact.
|
|
261
249
|
*/
|
|
262
|
-
function isAssociation(
|
|
263
|
-
const finalType =
|
|
250
|
+
function isAssociation( artifact ) {
|
|
251
|
+
const finalType = artifact && getFinalTypeInfo( artifact.type )?.type;
|
|
264
252
|
return (finalType === 'cds.Association');
|
|
265
253
|
}
|
|
266
254
|
|
|
267
255
|
/**
|
|
268
256
|
* Returns if a type is a composition (possibly via type chain).
|
|
269
257
|
*
|
|
270
|
-
* @param {
|
|
258
|
+
* @param {object} artifact Element or other artifact.
|
|
271
259
|
*/
|
|
272
|
-
function isComposition(
|
|
273
|
-
const finalType =
|
|
260
|
+
function isComposition( artifact ) {
|
|
261
|
+
const finalType = artifact && getFinalTypeInfo( artifact.type )?.type;
|
|
274
262
|
return (finalType === 'cds.Composition');
|
|
275
263
|
}
|
|
276
264
|
|
|
@@ -340,7 +328,7 @@ function getUtils( model, universalReady ) {
|
|
|
340
328
|
*
|
|
341
329
|
* Notes:
|
|
342
330
|
* - Caches type lookups. If the CSN changes drastically, you will need to re-call
|
|
343
|
-
* `getUtils()` and use the newly returned `
|
|
331
|
+
* `getUtils()` and use the newly returned `getFinalTypeInfo()`.
|
|
344
332
|
* - Does _not_ return the underlying type definition! It is an object with all relevant
|
|
345
333
|
* type properties collected while traversing the type chain!
|
|
346
334
|
*
|
|
@@ -349,7 +337,7 @@ function getUtils( model, universalReady ) {
|
|
|
349
337
|
* @param {string|object} type Type as string or type ref, i.e. `{ ref: [...] }`
|
|
350
338
|
* @returns {object|null}
|
|
351
339
|
*/
|
|
352
|
-
function
|
|
340
|
+
function getFinalTypeInfo( type ) {
|
|
353
341
|
type = normalizeTypeRef(type);
|
|
354
342
|
if (!type)
|
|
355
343
|
return null;
|
|
@@ -392,7 +380,7 @@ function getUtils( model, universalReady ) {
|
|
|
392
380
|
finalBaseTypeCache[resolvedKey] = true;
|
|
393
381
|
|
|
394
382
|
// Continue the search
|
|
395
|
-
const finalBase =
|
|
383
|
+
const finalBase = getFinalTypeInfo(type);
|
|
396
384
|
if (!finalBase) // Reference has no proper type, e.g. due to `type of View:calculated`.
|
|
397
385
|
return _cacheResolved(null);
|
|
398
386
|
|
|
@@ -561,28 +549,6 @@ function forEachMember( construct, callback, path = [], ignoreIgnore = true, ite
|
|
|
561
549
|
}
|
|
562
550
|
}
|
|
563
551
|
|
|
564
|
-
/**
|
|
565
|
-
* Call `forEachMember` and then apply `forEachMember` on queries.
|
|
566
|
-
*
|
|
567
|
-
* @param {CSN.Artifact} construct
|
|
568
|
-
* @param {genericCallback|genericCallback[]} callback
|
|
569
|
-
* @param {CSN.Path} [path]
|
|
570
|
-
* @param {boolean} [ignoreIgnore]
|
|
571
|
-
* @param {object} iterateOptions can be used to skip certain kinds from being iterated
|
|
572
|
-
* @param {constructCallback|constructCallback[]} callback
|
|
573
|
-
*/
|
|
574
|
-
function forEachMemberWithQuery( construct, callback, path = [], ignoreIgnore = true, iterateOptions = {},
|
|
575
|
-
constructCallback = (_construct, _prop, _path) => {} ) {
|
|
576
|
-
forEachMember(construct, callback, path, ignoreIgnore, iterateOptions, constructCallback);
|
|
577
|
-
if (construct.query) {
|
|
578
|
-
forAllQueries(construct.query, (q, p) => {
|
|
579
|
-
const s = q.SELECT;
|
|
580
|
-
if (s)
|
|
581
|
-
forEachMember(s, callback, p, ignoreIgnore, iterateOptions);
|
|
582
|
-
}, [ ...path, 'query' ]);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
552
|
/**
|
|
587
553
|
* Apply function `callback(member, memberName)` to each member in `construct`,
|
|
588
554
|
* recursively (i.e. also for sub-elements of elements).
|
|
@@ -606,30 +572,6 @@ function forEachMemberRecursively( construct, callback, path = [], ignoreIgnore
|
|
|
606
572
|
}, path, ignoreIgnore, iterateOptions, constructCallback);
|
|
607
573
|
}
|
|
608
574
|
|
|
609
|
-
/**
|
|
610
|
-
* Apply function `callback(member, memberName)` to each member in `construct`,
|
|
611
|
-
* recursively (i.e. also for sub-elements of elements).
|
|
612
|
-
* Recursively iterate over elements of `construct` query.
|
|
613
|
-
*
|
|
614
|
-
* @param {CSN.Artifact} construct
|
|
615
|
-
* @param {genericCallback|genericCallback[]} callback
|
|
616
|
-
* @param {CSN.Path} [path]
|
|
617
|
-
* @param {boolean} [ignoreIgnore]
|
|
618
|
-
* @param {object} iterateOptions can be used to skip certain kinds from being iterated
|
|
619
|
-
* @param {constructCallback|constructCallback[]} callback
|
|
620
|
-
*/
|
|
621
|
-
function forEachMemberRecursivelyWithQuery( construct, callback, path = [], ignoreIgnore = true, iterateOptions = {},
|
|
622
|
-
constructCallback = (_construct, _prop, _path) => {} ) {
|
|
623
|
-
forEachMemberRecursively(construct, callback, path, ignoreIgnore, iterateOptions, constructCallback);
|
|
624
|
-
if (construct.query) {
|
|
625
|
-
forAllQueries(construct.query, (q, p) => {
|
|
626
|
-
const s = q.SELECT;
|
|
627
|
-
if (s)
|
|
628
|
-
forEachMemberRecursively(s, callback, p, ignoreIgnore, iterateOptions);
|
|
629
|
-
}, [ ...path, 'query' ]);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
575
|
/**
|
|
634
576
|
* Apply function `callback` to all objects in dictionary `dict`, including all
|
|
635
577
|
* duplicates (found under the same name). Function `callback` is called with
|
|
@@ -767,6 +709,8 @@ function isEdmPropertyRendered( elementCsn, options ) {
|
|
|
767
709
|
// FKs are rendered in
|
|
768
710
|
// V2/V4 flat: always on
|
|
769
711
|
// V4 struct: on/off
|
|
712
|
+
if (elementCsn == null)
|
|
713
|
+
return false;
|
|
770
714
|
const renderForeignKey = (options.odataVersion === 'v4' && options.odataFormat === 'structured') ? !!options.odataForeignKeys : true;
|
|
771
715
|
const isNotIgnored = !elementCsn.target ? !elementCsn['@cds.api.ignore'] : true;
|
|
772
716
|
const isNavigable = elementCsn.target
|
|
@@ -1398,6 +1342,28 @@ function isDeepEqual( obj, other, noExtendedProps ) {
|
|
|
1398
1342
|
return true;
|
|
1399
1343
|
}
|
|
1400
1344
|
|
|
1345
|
+
/**
|
|
1346
|
+
* convert a cardinality object to string representation
|
|
1347
|
+
* @param {object} node
|
|
1348
|
+
* @param {boolean} withSrc
|
|
1349
|
+
* @returns {string} cardinality as string
|
|
1350
|
+
*/
|
|
1351
|
+
function cardinality2str( node, withSrc = true ) {
|
|
1352
|
+
const ofto = node.type === 'cds.Composition' ? 'of' : 'to';
|
|
1353
|
+
if (node.cardinality == null || (node.cardinality.src == null || !withSrc) && node.cardinality.min == null && node.cardinality.max === 1)
|
|
1354
|
+
return `${ ofto } one`;
|
|
1355
|
+
if ((node.cardinality.src == null || !withSrc) && node.cardinality.min == null && node.cardinality.max === '*')
|
|
1356
|
+
return `${ ofto } many`;
|
|
1357
|
+
let s = '[';
|
|
1358
|
+
if (node.cardinality.src != null && withSrc)
|
|
1359
|
+
s += `${ node.cardinality.src },`;
|
|
1360
|
+
if (node.cardinality.min != null)
|
|
1361
|
+
s += `${ node.cardinality.min }..`;
|
|
1362
|
+
if (node.cardinality.max != null)
|
|
1363
|
+
s += `${ node.cardinality.max }]`;
|
|
1364
|
+
return s;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1401
1367
|
/**
|
|
1402
1368
|
* Returns a function that, if called, calls all functions inside
|
|
1403
1369
|
* the given `functions` array with the same arguments.
|
|
@@ -1422,9 +1388,7 @@ module.exports = {
|
|
|
1422
1388
|
forEachGeneric,
|
|
1423
1389
|
forEachDefinition,
|
|
1424
1390
|
forEachMember,
|
|
1425
|
-
forEachMemberWithQuery,
|
|
1426
1391
|
forEachMemberRecursively,
|
|
1427
|
-
forEachMemberRecursivelyWithQuery,
|
|
1428
1392
|
forAllQueries,
|
|
1429
1393
|
hasAnnotationValue,
|
|
1430
1394
|
isEdmPropertyRendered,
|
|
@@ -1457,4 +1421,5 @@ module.exports = {
|
|
|
1457
1421
|
implicitAs,
|
|
1458
1422
|
isDeepEqual,
|
|
1459
1423
|
functionList,
|
|
1424
|
+
cardinality2str,
|
|
1460
1425
|
};
|