@sap/cds-compiler 6.0.14 → 6.2.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 +6 -2
- package/bin/cdsse.js +1 -1
- package/bin/cdsv2m.js +1 -1
- package/lib/api/main.js +29 -7
- package/lib/api/options.js +2 -0
- package/lib/base/builtins.js +9 -0
- package/lib/base/keywords.js +1 -1
- package/lib/base/message-registry.js +5 -3
- package/lib/base/messages.js +3 -3
- package/lib/base/model.js +1 -0
- package/lib/base/node-helpers.js +10 -2
- package/lib/base/optionProcessorHelper.js +7 -2
- package/lib/checks/assocOutsideService.js +3 -1
- package/lib/checks/featureFlags.js +4 -1
- package/lib/compiler/assert-consistency.js +3 -1
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/checks.js +38 -21
- package/lib/compiler/define.js +24 -5
- package/lib/compiler/extend.js +1 -1
- package/lib/compiler/finalize-parse-cdl.js +9 -1
- package/lib/compiler/generate.js +4 -4
- package/lib/compiler/index.js +10 -1
- package/lib/compiler/lsp-api.js +2 -0
- package/lib/compiler/populate.js +8 -8
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +15 -14
- package/lib/compiler/shared.js +6 -7
- package/lib/compiler/tweak-assocs.js +6 -6
- package/lib/compiler/utils.js +9 -16
- package/lib/compiler/xpr-rewrite.js +2 -2
- package/lib/gen/BaseParser.js +43 -37
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1424 -1433
- package/lib/gen/Dictionary.json +1 -7
- package/lib/gen/cdlKeywords.json +26 -0
- package/lib/inspect/inspectPropagation.js +1 -1
- package/lib/json/from-csn.js +2 -2
- package/lib/json/to-csn.js +9 -5
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/main.d.ts +10 -2
- package/lib/model/cloneCsn.js +1 -0
- package/lib/optionProcessor.js +13 -7
- package/lib/parsers/AstBuildingParser.js +24 -21
- package/lib/parsers/identifiers.js +2 -30
- package/lib/render/toCdl.js +63 -9
- package/lib/render/toSql.js +127 -108
- package/lib/render/utils/sql.js +67 -0
- package/lib/transform/addTenantFields.js +4 -4
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +20 -2
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/draft/odata.js +14 -4
- package/lib/transform/forOdata.js +91 -2
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/odata/flattening.js +1 -1
- package/lib/transform/transformUtils.js +2 -2
- package/lib/transform/translateAssocsToJoins.js +2 -26
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +2 -2
package/lib/gen/Dictionary.json
CHANGED
|
@@ -3231,13 +3231,6 @@
|
|
|
3231
3231
|
"ValidationFunction": "Common.QualifiedName"
|
|
3232
3232
|
}
|
|
3233
3233
|
},
|
|
3234
|
-
"Common.DraftUserAccessType": {
|
|
3235
|
-
"$kind": "ComplexType",
|
|
3236
|
-
"Properties": {
|
|
3237
|
-
"UserAccessRole": "Edm.String",
|
|
3238
|
-
"UserID": "Edm.String"
|
|
3239
|
-
}
|
|
3240
|
-
},
|
|
3241
3234
|
"Common.EffectType": {
|
|
3242
3235
|
"$deprecated": true,
|
|
3243
3236
|
"$deprecationText": "All side effects are essentially value changes, differentiation not needed.",
|
|
@@ -3513,6 +3506,7 @@
|
|
|
3513
3506
|
"Label": "Edm.String",
|
|
3514
3507
|
"Parameters": "Collection(Common.ValueListParameter)",
|
|
3515
3508
|
"PresentationVariantQualifier": "Core.SimpleIdentifier",
|
|
3509
|
+
"RelativeCollectionPath": "Edm.NavigationPropertyPath",
|
|
3516
3510
|
"SearchSupported": "Edm.Boolean",
|
|
3517
3511
|
"SelectionVariantQualifier": "Core.SimpleIdentifier"
|
|
3518
3512
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reserved": [
|
|
3
|
+
"ALL",
|
|
4
|
+
"ANY",
|
|
5
|
+
"AS",
|
|
6
|
+
"BY",
|
|
7
|
+
"CASE",
|
|
8
|
+
"CAST",
|
|
9
|
+
"DISTINCT",
|
|
10
|
+
"EXISTS",
|
|
11
|
+
"FALSE",
|
|
12
|
+
"FROM",
|
|
13
|
+
"IN",
|
|
14
|
+
"KEY",
|
|
15
|
+
"NOT",
|
|
16
|
+
"NULL",
|
|
17
|
+
"OF",
|
|
18
|
+
"ON",
|
|
19
|
+
"SELECT",
|
|
20
|
+
"SOME",
|
|
21
|
+
"TRUE",
|
|
22
|
+
"WHEN",
|
|
23
|
+
"WHERE",
|
|
24
|
+
"WITH"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -36,7 +36,7 @@ function inspectPropagation( xsn, options, artifactName ) {
|
|
|
36
36
|
|
|
37
37
|
if (!artifactXsn) {
|
|
38
38
|
error(null, null, { name: artifactName },
|
|
39
|
-
// eslint-disable-next-line @stylistic/
|
|
39
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
40
40
|
'Artifact $(NAME) not found, only top-level artifacts and their elements are supported for now');
|
|
41
41
|
return null;
|
|
42
42
|
}
|
package/lib/json/from-csn.js
CHANGED
|
@@ -137,7 +137,7 @@ const typeProperties = [
|
|
|
137
137
|
// do not include CSN v0.1.0 properties here:
|
|
138
138
|
'target', 'elements', 'enum', 'items',
|
|
139
139
|
'cardinality', // for association publishing in views
|
|
140
|
-
'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull',
|
|
140
|
+
'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull', 'default',
|
|
141
141
|
'keys', 'on', // only with 'target'
|
|
142
142
|
];
|
|
143
143
|
const exprProperties = [
|
|
@@ -1142,7 +1142,7 @@ function elementsDict( def, spec, xsn ) {
|
|
|
1142
1142
|
return elements;
|
|
1143
1143
|
warning( 'syntax-expecting-returns', elements[$location],
|
|
1144
1144
|
{ prop: 'elements', parentprop: 'returns' },
|
|
1145
|
-
// eslint-disable-next-line @stylistic/
|
|
1145
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1146
1146
|
'Expecting property $(PROP) to be put into an object for property $(PARENTPROP) when annotating action return structures' );
|
|
1147
1147
|
xsn.returns = { kind: 'annotate', elements, location: elements[$location] };
|
|
1148
1148
|
return undefined;
|
package/lib/json/to-csn.js
CHANGED
|
@@ -201,7 +201,7 @@ const propertyOrder = (function orderPositions() {
|
|
|
201
201
|
const typeProperties = [
|
|
202
202
|
'target', 'elements', 'enum', 'items',
|
|
203
203
|
'cardinality', // for association publishing in views
|
|
204
|
-
'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull',
|
|
204
|
+
'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull', 'default',
|
|
205
205
|
'foreignKeys', 'on', // for explicit ON/keys with REDIRECTED
|
|
206
206
|
'$typeArgs', // for unresolved type arguments, e.g. through parseCql
|
|
207
207
|
];
|
|
@@ -1319,11 +1319,15 @@ function flattenInternalXpr( array, xprOp ) {
|
|
|
1319
1319
|
function ternaryOperator( node ) {
|
|
1320
1320
|
const rargs = [
|
|
1321
1321
|
'case',
|
|
1322
|
-
'when', exprInternal(node.args[0]),
|
|
1323
|
-
'then', exprInternal(node.args[2]),
|
|
1324
|
-
'else', exprInternal(node.args[4]),
|
|
1325
|
-
'end',
|
|
1322
|
+
'when', exprInternal( node.args[0] ),
|
|
1323
|
+
'then', exprInternal( node.args[2] ),
|
|
1326
1324
|
];
|
|
1325
|
+
let right = node.args[4];
|
|
1326
|
+
for (; right.op?.val === '?:' && !right.$parens?.length; right = right.args[4]) {
|
|
1327
|
+
rargs.push( 'when', exprInternal( right.args[0] ),
|
|
1328
|
+
'then', exprInternal( right.args[2] ) );
|
|
1329
|
+
}
|
|
1330
|
+
rargs.push( 'else', exprInternal( right ), 'end' );
|
|
1327
1331
|
|
|
1328
1332
|
if (node.$parens?.length)
|
|
1329
1333
|
return { xpr: flattenInternalXpr( rargs, 'xpr' ) };
|
|
@@ -77,7 +77,7 @@ class MultiLineStringParser {
|
|
|
77
77
|
this.str = token.text; // Copy because .text is a getter
|
|
78
78
|
|
|
79
79
|
if (this.str[0] !== '`' || this.str[this.str.length - 1] !== '`')
|
|
80
|
-
// eslint-disable-next-line @stylistic/
|
|
80
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
81
81
|
throw new CompilerAssertion('Invalid multi-line string sequence: Require string to be surrounded by back-ticks!');
|
|
82
82
|
|
|
83
83
|
this.output = [];
|
package/lib/main.d.ts
CHANGED
|
@@ -158,6 +158,16 @@ declare namespace compiler {
|
|
|
158
158
|
* @since v4.2.0
|
|
159
159
|
*/
|
|
160
160
|
moduleLookupDirectories?: string[]
|
|
161
|
+
/**
|
|
162
|
+
* An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
|
|
163
|
+
* that can be used to abort the compilation.
|
|
164
|
+
* Used for any `async` task, i.e. at the moment for reading/parsing files.
|
|
165
|
+
*
|
|
166
|
+
* Note that this flag has no effect on _synchronous_ compilation functions.
|
|
167
|
+
*
|
|
168
|
+
* @since v6.1
|
|
169
|
+
*/
|
|
170
|
+
abortSignal?: AbortSignal
|
|
161
171
|
/**
|
|
162
172
|
* Option for {@link compileSources}. If set, all objects inside the
|
|
163
173
|
* provided sources dictionary are interpreted as XSN structures instead
|
|
@@ -1558,8 +1568,6 @@ declare namespace compiler {
|
|
|
1558
1568
|
constructor(location: Location, msg: string, severity?: MessageSeverity, id?: string | null, home?: string | null, moduleName?: string | null);
|
|
1559
1569
|
/**
|
|
1560
1570
|
* Optional ID of the message. Can be used to reclassify messages.
|
|
1561
|
-
*
|
|
1562
|
-
* @note This property is non-enumerable as message IDs are not finalized, yet.
|
|
1563
1571
|
*/
|
|
1564
1572
|
messageId?: string
|
|
1565
1573
|
|
package/lib/model/cloneCsn.js
CHANGED
package/lib/optionProcessor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Compiler options
|
|
2
2
|
|
|
3
|
-
/* eslint @stylistic/
|
|
3
|
+
/* eslint @stylistic/max-len: 0 */
|
|
4
4
|
|
|
5
5
|
// Remarks:
|
|
6
6
|
// - The specification is client-tool centric (bin/cdsc.js):
|
|
@@ -169,7 +169,8 @@ optionProcessor
|
|
|
169
169
|
Q, toSql [options] <files...> Generate SQL DDL statements
|
|
170
170
|
J, forJava [options] <files...> Generate CSN for the Java Runtime
|
|
171
171
|
toCsn [options] <files...> (default) Generate original model as CSN
|
|
172
|
-
|
|
172
|
+
parse [options] <file> Parse the input file. For CDL input, generate a CSN that is
|
|
173
|
+
close to the CDL source.
|
|
173
174
|
explain <message-id> Explain a compiler message.
|
|
174
175
|
parseOnly [options] <files...> (internal) Stop compilation after parsing, write messages to <stderr>,
|
|
175
176
|
per default no output.
|
|
@@ -263,6 +264,8 @@ optionProcessor.command('O, toOdata')
|
|
|
263
264
|
.option(' --odata-v2-partial-constr')
|
|
264
265
|
.option(' --odata-vocabularies <list>')
|
|
265
266
|
.option(' --odata-no-creator')
|
|
267
|
+
.option(' --draft-messages')
|
|
268
|
+
.option(' --add-annotation-AddressViaNavigationPath')
|
|
266
269
|
.option('-c, --csn')
|
|
267
270
|
.option('-f, --odata-format <format>', { valid: [ 'flat', 'structured' ] })
|
|
268
271
|
.option('-n, --sql-mapping <style>', { valid: [ 'plain', 'quoted', 'hdbcds' ], aliases: [ '--names' ] })
|
|
@@ -297,7 +300,10 @@ optionProcessor.command('O, toOdata')
|
|
|
297
300
|
--odata-vocabularies <list> JSON array of adhoc vocabulary definitions
|
|
298
301
|
{ prefix: { alias, ns, uri }, ... }
|
|
299
302
|
--odata-no-creator Omit creator identification in API
|
|
300
|
-
|
|
303
|
+
--draft-messages Add draft messages as part of the draft creation
|
|
304
|
+
--add-annotation-AddressViaNavigationPath Add annotation "@Common.AddressViaNavigationPath" to the services
|
|
305
|
+
containing draft enabled entitties
|
|
306
|
+
-n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is
|
|
301
307
|
the corresponding database name (see "--sql-mapping" for "toSql")
|
|
302
308
|
plain : (default) Names in uppercase and flattened with underscores
|
|
303
309
|
quoted : Names in original case as in CDL. Entity names with dots,
|
|
@@ -529,15 +535,15 @@ optionProcessor.command('toCsn')
|
|
|
529
535
|
--with-localized Add localized convenience views to the CSN output.
|
|
530
536
|
`);
|
|
531
537
|
|
|
532
|
-
optionProcessor.command('parseCdl')
|
|
538
|
+
optionProcessor.command('parse', { aliases: [ 'parseCdl' ] })
|
|
533
539
|
.option('-h, --help')
|
|
534
540
|
.positionalArgument('<file>')
|
|
535
541
|
.option(' --with-locations')
|
|
536
542
|
.help(`
|
|
537
|
-
Usage: cdsc
|
|
543
|
+
Usage: cdsc parse [options] <file>
|
|
538
544
|
|
|
539
|
-
Only parse the CDL
|
|
540
|
-
resolve imports, apply extensions or expand any queries.
|
|
545
|
+
Only parse the input file. For CDL input, output a CSN that is close to the source.
|
|
546
|
+
Does not resolve imports, apply extensions or expand any queries.
|
|
541
547
|
|
|
542
548
|
Options
|
|
543
549
|
--with-locations Add $location to CSN artifacts.
|
|
@@ -47,7 +47,11 @@ const extensionsCode = {
|
|
|
47
47
|
const PRECEDENCE_OF_EQUAL = 10;
|
|
48
48
|
|
|
49
49
|
class AstBuildingParser extends BaseParser {
|
|
50
|
-
leanConditions = {
|
|
50
|
+
leanConditions = {
|
|
51
|
+
afterBrace: true,
|
|
52
|
+
atRightParen: true,
|
|
53
|
+
fail: true,
|
|
54
|
+
};
|
|
51
55
|
|
|
52
56
|
constructor( lexer, keywords, table, options, messageFunctions ) {
|
|
53
57
|
super( lexer, keywords, table ); // lexer has file
|
|
@@ -115,13 +119,16 @@ class AstBuildingParser extends BaseParser {
|
|
|
115
119
|
|
|
116
120
|
// Guards, Prepare Commands and Lookahead Methods -----------------------------
|
|
117
121
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
queryOnLeftSloppyAlias( _arg, mode ) {
|
|
123
|
+
if (mode === 'M' || this.isNoKeywordInRuleFollow( _arg, mode ))
|
|
124
|
+
return true;
|
|
125
|
+
// TODO TOOL: have a base parser method for the test
|
|
126
|
+
if (this.conditionTokenIdx === this.tokenIdx && // tested on same
|
|
127
|
+
this.conditionStackLength == null) // after error recovery
|
|
128
|
+
return false;
|
|
129
|
+
if (this.constructor.tracingParser)
|
|
130
|
+
this._traceSubPush( 'queryOnLeft' );
|
|
131
|
+
return this.queryOnLeft( 'table', mode );
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
/**
|
|
@@ -134,15 +141,9 @@ class AstBuildingParser extends BaseParser {
|
|
|
134
141
|
* - <guard=queryOnLeft> tests whether the expression on the left is a query
|
|
135
142
|
* - <guard=queryOnLeft, arg=‹SomeVal›>: tests whether the expression on the
|
|
136
143
|
* left is a query, then make the current context to be not a query anymore
|
|
137
|
-
* - <guard=queryOnLeft, arg=tableWithoutAs>: …after having checked
|
|
138
|
-
* whether the next token is no (reserved or unreserved) keyword
|
|
139
144
|
*/
|
|
140
145
|
queryOnLeft( arg, test ) {
|
|
141
|
-
if (arg
|
|
142
|
-
if (this.tableWithoutAs())
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
else if (!arg && !test) {
|
|
146
|
+
if (!arg && !test) {
|
|
146
147
|
// provide new dynamic parentheses context, except with direct
|
|
147
148
|
// recursive call:
|
|
148
149
|
if (this.inSameRule_( this.s, this.stack.at( -1 ).followState ))
|
|
@@ -570,7 +571,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
570
571
|
if (this.l() === ':') {
|
|
571
572
|
this.warning( 'syntax-missing-parens', name,
|
|
572
573
|
{ code: '@‹anno›', op: ':', newcode: '@(‹anno›…)' },
|
|
573
|
-
// eslint-disable-next-line @stylistic/
|
|
574
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
574
575
|
'When $(CODE) is followed by $(OP), use $(NEWCODE) for annotation assignments at this position' );
|
|
575
576
|
}
|
|
576
577
|
}
|
|
@@ -582,7 +583,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
582
583
|
// do not report error if the '@' is not correct:
|
|
583
584
|
this.s !== null && this.tokenIdx > this.recoverTokenIdx) {
|
|
584
585
|
this.warning( 'syntax-missing-semicolon', next, { code: ';' },
|
|
585
|
-
// eslint-disable-next-line @stylistic/
|
|
586
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
586
587
|
'Add a $(CODE) and/or newline before the annotation assignment to indicate that it belongs to the next statement' );
|
|
587
588
|
}
|
|
588
589
|
}
|
|
@@ -737,7 +738,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
737
738
|
}
|
|
738
739
|
else if (text.charAt(0) !== '!') {
|
|
739
740
|
this.message( 'syntax-deprecated-ident', location, { delimited: id },
|
|
740
|
-
// eslint-disable-next-line @stylistic/
|
|
741
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
741
742
|
'Deprecated delimited identifier syntax, use $(DELIMITED) - strings are delimited by single quotes' );
|
|
742
743
|
}
|
|
743
744
|
}
|
|
@@ -948,7 +949,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
948
949
|
{
|
|
949
950
|
std: 'Annotation number $(RAWVALUE) is put as $(VALUE) into the CSN',
|
|
950
951
|
rounded: 'Annotation number $(RAWVALUE) is rounded to $(VALUE)',
|
|
951
|
-
// eslint-disable-next-line @stylistic/
|
|
952
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
952
953
|
infinite: 'Annotation value $(RAWVALUE) is infinite as number and put as string into the CSN',
|
|
953
954
|
} );
|
|
954
955
|
}
|
|
@@ -1000,7 +1001,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
1000
1001
|
|
|
1001
1002
|
// TODO: can we remove `;`/EOF from the expected-set for `annotate Foo with ⎀`?
|
|
1002
1003
|
checkWith( keyword ) {
|
|
1003
|
-
if (this.lb() !== keyword)
|
|
1004
|
+
if (this.lb() !== keyword || ![ ';', '}', 'EOF' ].includes( this.l() ))
|
|
1004
1005
|
return;
|
|
1005
1006
|
const tok = this.la();
|
|
1006
1007
|
const docTokenIndex = this.docCommentIndex &&
|
|
@@ -1012,7 +1013,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
1012
1013
|
const expecting = this.expectingArray().filter( t => t !== '<EOF>' && t !== '\'}\'' );
|
|
1013
1014
|
const msg = this.warning( 'syntax-unexpected-semicolon', tok,
|
|
1014
1015
|
{ offending: this.antlrName( tok ), expecting, keyword: 'with' },
|
|
1015
|
-
// eslint-disable-next-line @stylistic/
|
|
1016
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1016
1017
|
'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous $(KEYWORD)' );
|
|
1017
1018
|
msg.expectedTokens = expecting;
|
|
1018
1019
|
}
|
|
@@ -1523,6 +1524,8 @@ class AstBuildingParser extends BaseParser {
|
|
|
1523
1524
|
}
|
|
1524
1525
|
}
|
|
1525
1526
|
|
|
1527
|
+
AstBuildingParser.prototype.queryOnLeftSloppyAlias.afterError = true;
|
|
1528
|
+
|
|
1526
1529
|
function addOneForDefinition( count, ext ) {
|
|
1527
1530
|
return (ext.kind === 'extend') ? count : count + 1;
|
|
1528
1531
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const cdlKeywords = require('../gen/cdlKeywords.json').reserved;
|
|
4
|
+
|
|
3
5
|
/** RegEx identifying undelimited identifiers in CDL */
|
|
4
6
|
const undelimitedIdentifierRegex = /^[$_\p{ID_Start}][$\p{ID_Continue}\u200C\u200D]*$/u;
|
|
5
7
|
|
|
@@ -13,36 +15,6 @@ const functionsWithoutParentheses = [
|
|
|
13
15
|
'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
|
|
14
16
|
];
|
|
15
17
|
|
|
16
|
-
// CDL reserved keywords, used for automatic quoting in 'toCdl' renderer
|
|
17
|
-
// TODO: Use `parser.keywords` from our generated CdlParser.js (#13856)
|
|
18
|
-
const cdlKeywords = [
|
|
19
|
-
'ALL',
|
|
20
|
-
'ANY',
|
|
21
|
-
'AS',
|
|
22
|
-
'BY',
|
|
23
|
-
'CASE',
|
|
24
|
-
'CAST',
|
|
25
|
-
'DISTINCT',
|
|
26
|
-
'EXISTS',
|
|
27
|
-
'EXTRACT',
|
|
28
|
-
'FALSE', // boolean
|
|
29
|
-
'FROM',
|
|
30
|
-
'IN',
|
|
31
|
-
'KEY',
|
|
32
|
-
'NEW',
|
|
33
|
-
'NOT',
|
|
34
|
-
'NULL',
|
|
35
|
-
'OF',
|
|
36
|
-
'ON',
|
|
37
|
-
'SELECT',
|
|
38
|
-
'SOME',
|
|
39
|
-
'TRIM',
|
|
40
|
-
'TRUE', // boolean
|
|
41
|
-
'WHEN',
|
|
42
|
-
'WHERE',
|
|
43
|
-
'WITH',
|
|
44
|
-
];
|
|
45
|
-
|
|
46
18
|
function isSimpleCdlIdentifier( id ) {
|
|
47
19
|
if (undelimitedIdentifierRegex.test(id))
|
|
48
20
|
return true;
|
package/lib/render/toCdl.js
CHANGED
|
@@ -1439,7 +1439,7 @@ class CsnToCdl {
|
|
|
1439
1439
|
if (col.cast.target && !col.cast.type)
|
|
1440
1440
|
result += ` : ${ this.renderRedirectedTo(col.cast, env) }`;
|
|
1441
1441
|
else
|
|
1442
|
-
result += ` : ${ this.renderTypeReferenceAndProps(col.cast, env, {
|
|
1442
|
+
result += ` : ${ this.renderTypeReferenceAndProps(col.cast, env, { noAnnoCollect: true }) }`;
|
|
1443
1443
|
env.path.length -= 1;
|
|
1444
1444
|
}
|
|
1445
1445
|
return result;
|
|
@@ -1869,9 +1869,12 @@ class CsnToCdl {
|
|
|
1869
1869
|
if (artifact.on)
|
|
1870
1870
|
result += ` on ${ this.exprRenderer.renderExpr(artifact.on, env.withSubPath([ 'on' ])) }`;
|
|
1871
1871
|
|
|
1872
|
-
// Foreign keys (if any, unless we also have an
|
|
1873
|
-
if (artifact.keys && !artifact.on)
|
|
1874
|
-
|
|
1872
|
+
// Foreign keys (if any, unless we also have an ON-condition (which means we have been transformed from managed to unmanaged)
|
|
1873
|
+
if (artifact.keys && !artifact.on) {
|
|
1874
|
+
const keys = this.renderForeignKeys(artifact, env, false);
|
|
1875
|
+
if (keys)
|
|
1876
|
+
result += ` ${ keys }`;
|
|
1877
|
+
}
|
|
1875
1878
|
|
|
1876
1879
|
if (!artifact.on) {
|
|
1877
1880
|
// unmanaged associations can't be followed by "not null" or "default"
|
|
@@ -1923,10 +1926,14 @@ class CsnToCdl {
|
|
|
1923
1926
|
*/
|
|
1924
1927
|
renderRedirectedTo( art, env ) {
|
|
1925
1928
|
let result = `redirected to ${ this.renderDefinitionReference(art.target, env) }`;
|
|
1926
|
-
if (art.on)
|
|
1929
|
+
if (art.on) {
|
|
1927
1930
|
result += ` on ${ this.exprRenderer.renderExpr(art.on, env.withSubPath([ 'on' ])) }`;
|
|
1928
|
-
|
|
1929
|
-
|
|
1931
|
+
}
|
|
1932
|
+
else if (art.keys) {
|
|
1933
|
+
const keys = this.renderForeignKeys(art, env, true);
|
|
1934
|
+
if (keys)
|
|
1935
|
+
result += ` ${ keys }`;
|
|
1936
|
+
}
|
|
1930
1937
|
return result;
|
|
1931
1938
|
}
|
|
1932
1939
|
|
|
@@ -2274,13 +2281,16 @@ class CsnToCdl {
|
|
|
2274
2281
|
}
|
|
2275
2282
|
|
|
2276
2283
|
/**
|
|
2277
|
-
* Render foreign keys.
|
|
2284
|
+
* Render foreign keys. We only render foreign keys if necessary or if we can't say for sure
|
|
2285
|
+
* that the foreign keys would match the implicitly generated ones.
|
|
2278
2286
|
*
|
|
2279
2287
|
* @param {object} art
|
|
2280
2288
|
* @param {CdlRenderEnvironment} env
|
|
2289
|
+
* @param {boolean} alwaysRenderForeignKeys
|
|
2290
|
+
* If false, only render foreign keys if necessary (i.e. can't be inferred by compiler again).
|
|
2281
2291
|
* @return {string}
|
|
2282
2292
|
*/
|
|
2283
|
-
renderForeignKeys( art, env ) {
|
|
2293
|
+
renderForeignKeys( art, env, alwaysRenderForeignKeys = false ) {
|
|
2284
2294
|
const renderedKeys = [];
|
|
2285
2295
|
let hasAnnotations = false;
|
|
2286
2296
|
env = env.withSubPath([ 'keys', -1 ]);
|
|
@@ -2301,18 +2311,62 @@ class CsnToCdl {
|
|
|
2301
2311
|
renderedKeys.push(`${ key }${ alias },`);
|
|
2302
2312
|
}
|
|
2303
2313
|
|
|
2314
|
+
// Annotations on foreign keys always require rendering of explicit keys.
|
|
2315
|
+
// Otherwise, we'd have to use annotate statements here.
|
|
2304
2316
|
if (hasAnnotations) {
|
|
2305
2317
|
const sep = `\n${ env.indent }`;
|
|
2306
2318
|
env.decreaseIndent();
|
|
2307
2319
|
return `{${ sep }${ renderedKeys.join(sep) }\n${ env.indent }}`;
|
|
2308
2320
|
}
|
|
2309
2321
|
|
|
2322
|
+
if (!alwaysRenderForeignKeys && this.foreignKeysMatchImplicitOnes( art ))
|
|
2323
|
+
return '';
|
|
2324
|
+
|
|
2310
2325
|
let result = renderedKeys.join(' ');
|
|
2311
2326
|
if (result[result.length - 1] === ',') // remove trailing comma
|
|
2312
2327
|
result = result.slice(0, -1);
|
|
2313
2328
|
return `{ ${ result } }`;
|
|
2314
2329
|
}
|
|
2315
2330
|
|
|
2331
|
+
/**
|
|
2332
|
+
* Returns true, if `to.cdl()` could leave out explicit foreign keys in the rendered output
|
|
2333
|
+
* without changing recompilation. We do so, because explicit foreign keys are not promoted
|
|
2334
|
+
* on CAPire.
|
|
2335
|
+
*
|
|
2336
|
+
* @param {CSN.Artifact} art
|
|
2337
|
+
* @returns {boolean}
|
|
2338
|
+
*/
|
|
2339
|
+
foreignKeysMatchImplicitOnes( art ) {
|
|
2340
|
+
if (art.cardinality && art.cardinality.max !== 1)
|
|
2341
|
+
return false; // e.g. to-many assocs
|
|
2342
|
+
|
|
2343
|
+
const target = this.csn.definitions[art.target];
|
|
2344
|
+
if (!art.keys?.length || !art.target || !target?.elements)
|
|
2345
|
+
return false;
|
|
2346
|
+
|
|
2347
|
+
// We could improve to.cdl() to properly check if keys match, but then we'd have to look
|
|
2348
|
+
// at sub-elements, structures, etc.; for now, we only check “simple” foreign keys that
|
|
2349
|
+
// don't have any alias.
|
|
2350
|
+
const hasComplexKeys = art.keys.some(key => key.ref?.length !== 1 || key.as !== undefined);
|
|
2351
|
+
if (hasComplexKeys)
|
|
2352
|
+
return false;
|
|
2353
|
+
|
|
2354
|
+
const targetKeys = Object.keys(target.elements).filter(name => target.elements[name]?.key);
|
|
2355
|
+
if (targetKeys.length === 0)
|
|
2356
|
+
return false; // always require explicit keys if there are no target side keys
|
|
2357
|
+
|
|
2358
|
+
const foreignKeys = art.keys.map(key => key.ref[0]);
|
|
2359
|
+
if (foreignKeys.length !== targetKeys.length)
|
|
2360
|
+
return false;
|
|
2361
|
+
|
|
2362
|
+
// We require the _same_ order! Otherwise, recompilation would change the generated foreign key order!
|
|
2363
|
+
for (let i = 0; i < foreignKeys.length; ++i) {
|
|
2364
|
+
if (foreignKeys[i] !== targetKeys[i])
|
|
2365
|
+
return false;
|
|
2366
|
+
}
|
|
2367
|
+
return true;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2316
2370
|
/**
|
|
2317
2371
|
* Render an explicit alias, e.g. for columns.
|
|
2318
2372
|
*
|