@sap/cds-compiler 3.8.2 → 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 +53 -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 +7 -2
- package/lib/compiler/base.js +8 -4
- 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 +910 -1711
- 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 +3 -1
- 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 +3564 -3493
- package/lib/json/from-csn.js +26 -4
- package/lib/json/to-csn.js +10 -6
- package/lib/language/antlrParser.js +11 -3
- package/lib/language/genericAntlrParser.js +2 -1
- package/lib/language/language.g4 +14 -3
- package/lib/model/csnRefs.js +10 -5
- 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) {
|
|
@@ -1725,8 +1748,7 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
|
|
|
1725
1748
|
{
|
|
1726
1749
|
'#': variant,
|
|
1727
1750
|
prop,
|
|
1728
|
-
parentprop:
|
|
1729
|
-
parentSpec.msgProp,
|
|
1751
|
+
parentprop: parentSpec.msgProp,
|
|
1730
1752
|
kind,
|
|
1731
1753
|
} );
|
|
1732
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();
|
|
@@ -1095,7 +1095,8 @@ function addExtension( ext, parent, kind, artName, elemPath ) {
|
|
|
1095
1095
|
}
|
|
1096
1096
|
|
|
1097
1097
|
function aspectWithoutElements( art ) {
|
|
1098
|
-
// 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!
|
|
1099
1100
|
art.elements = this.createDict();
|
|
1100
1101
|
if (!isBetaEnabled( this.options, 'aspectWithoutElements' )) {
|
|
1101
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 ]
|
|
@@ -2956,6 +2965,7 @@ ident[ category ] returns[ id ]
|
|
|
2956
2965
|
| RIGHT
|
|
2957
2966
|
| ROW
|
|
2958
2967
|
| ROWS
|
|
2968
|
+
| STORED
|
|
2959
2969
|
| SERVICE
|
|
2960
2970
|
| THEN
|
|
2961
2971
|
| UNION
|
|
@@ -3155,6 +3165,7 @@ RIGHT : [rR][iI][gG][hH][tT] ;
|
|
|
3155
3165
|
ROW : [rR][oO][wW] ;
|
|
3156
3166
|
ROWS : [rR][oO][wW][sS] ;
|
|
3157
3167
|
SERVICE : [sS][eE][rR][vV][iI][cC][eE] ;
|
|
3168
|
+
STORED : [sS][tT][oO][rR][eE][dD] ;
|
|
3158
3169
|
THEN : [tT][hH][eE][nN] ;
|
|
3159
3170
|
TO : [tT][oO] ; // or make reserved? (is in SQL-92)
|
|
3160
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
|
|
@@ -992,6 +995,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
992
995
|
/** @type {boolean|string|number} */
|
|
993
996
|
let isName = false;
|
|
994
997
|
let baseRef = null;
|
|
998
|
+
let baseCtx = null;
|
|
995
999
|
let baseEnv = null;
|
|
996
1000
|
let {
|
|
997
1001
|
index, main, parent, art,
|
|
@@ -1049,13 +1053,13 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
1049
1053
|
}
|
|
1050
1054
|
else if (prop === 'where' && refCtx === 'ref') {
|
|
1051
1055
|
if (resolve)
|
|
1052
|
-
baseEnv = resolve.ref_where( obj, baseRef,
|
|
1056
|
+
baseEnv = resolve.ref_where( obj, baseRef, baseCtx, main, query, parent, baseEnv );
|
|
1053
1057
|
refCtx = 'ref_where';
|
|
1054
1058
|
}
|
|
1055
1059
|
else if (prop === 'expand' || prop === 'inline') {
|
|
1056
1060
|
if (obj.ref) {
|
|
1057
1061
|
if (resolve)
|
|
1058
|
-
baseEnv = resolve.expandInline( obj,
|
|
1062
|
+
baseEnv = resolve.expandInline( obj, baseCtx, main, query, parent, baseEnv );
|
|
1059
1063
|
refCtx = prop;
|
|
1060
1064
|
}
|
|
1061
1065
|
isName = prop;
|
|
@@ -1070,6 +1074,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
|
|
|
1070
1074
|
}
|
|
1071
1075
|
else if (prop === 'ref') {
|
|
1072
1076
|
baseRef = obj; // needs to be inspected for filter conditions
|
|
1077
|
+
baseCtx = refCtx;
|
|
1073
1078
|
refCtx = prop;
|
|
1074
1079
|
}
|
|
1075
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
|
};
|
package/lib/optionProcessor.js
CHANGED
|
@@ -108,7 +108,6 @@ optionProcessor
|
|
|
108
108
|
enableUniversalCsn
|
|
109
109
|
postgres
|
|
110
110
|
aspectWithoutElements
|
|
111
|
-
odataOpenType
|
|
112
111
|
odataTerms
|
|
113
112
|
optionalActionFunctionParameters
|
|
114
113
|
--deprecated <list> Comma separated list of deprecated options.
|
|
@@ -222,11 +221,12 @@ optionProcessor.command('O, toOdata')
|
|
|
222
221
|
.option('-j, --json')
|
|
223
222
|
.option(' --odata-containment')
|
|
224
223
|
.option(' --odata-capabilities-pullup')
|
|
224
|
+
.option(' --odata-openapi-hints')
|
|
225
225
|
.option(' --odata-proxies')
|
|
226
226
|
.option(' --odata-x-service-refs')
|
|
227
227
|
.option(' --odata-foreign-keys')
|
|
228
228
|
.option(' --odata-v2-partial-constr')
|
|
229
|
-
.option(' --odata-
|
|
229
|
+
.option(' --odata-vocabularies <list>')
|
|
230
230
|
.option('-c, --csn')
|
|
231
231
|
.option('-f, --odata-format <format>', ['flat', 'structured'])
|
|
232
232
|
.option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
|
|
@@ -250,13 +250,14 @@ optionProcessor.command('O, toOdata')
|
|
|
250
250
|
structured : (V4 only) Render structured metadata
|
|
251
251
|
--odata-containment Generate Containment Navigation Properties for compositions (V4 only)
|
|
252
252
|
--odata-capabilities-pullup Rewrite @Capabilities annotations (V4 containment only).
|
|
253
|
+
--odata-openapi-hints Add various annotations to JSON API as input for OpenAPI generation.
|
|
253
254
|
--odata-proxies Generate Proxies for out-of-service navigation targets (V4 only).
|
|
254
255
|
--odata-x-service-refs Generate schema references (V4 only).
|
|
255
256
|
--odata-foreign-keys Render foreign keys in structured format (V4 only)
|
|
256
257
|
--odata-v2-partial-constr Render referential constraints also for partial principal key tuple
|
|
257
258
|
(Not spec compliant and V2 only)
|
|
258
|
-
--odata-
|
|
259
|
-
|
|
259
|
+
--odata-vocabularies <list> JSON array of adhoc vocabulary definitions
|
|
260
|
+
{ prefix: { alias, ns, uri }, ... }
|
|
260
261
|
-n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is
|
|
261
262
|
the corresponding database name (see "--sql-mapping" for "toHana or "toSql")
|
|
262
263
|
plain : (default) Names in uppercase and flattened with underscores
|
|
@@ -297,6 +298,7 @@ optionProcessor.command('Q, toSql')
|
|
|
297
298
|
.option(' --pre2134ReferentialConstraintNames')
|
|
298
299
|
.option(' --disable-hana-comments')
|
|
299
300
|
.option(' --generated-by-comment')
|
|
301
|
+
.option(' --better-sqlite-session-variables')
|
|
300
302
|
.help(`
|
|
301
303
|
Usage: cdsc toSql [options] <files...>
|
|
302
304
|
|
|
@@ -348,6 +350,7 @@ optionProcessor.command('Q, toSql')
|
|
|
348
350
|
--pre2134ReferentialConstraintNames Do not prefix the constraint identifier with "c__"
|
|
349
351
|
--disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
|
|
350
352
|
--generated-by-comment Enable rendering of the initial SQL comment for HDI-based artifacts
|
|
353
|
+
--better-sqlite-session-variables Enable better-sqlite compatible rendering of $user. Only active if sqlDialect is \`sqlite\`
|
|
351
354
|
|
|
352
355
|
`);
|
|
353
356
|
|