@sap/cds-compiler 6.1.0 → 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 +43 -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/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 +2 -2
- package/lib/base/model.js +1 -0
- package/lib/base/optionProcessorHelper.js +7 -2
- 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/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 -6
- 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 +35 -29
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1423 -1432
- package/lib/gen/Dictionary.json +1 -0
- 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 +1 -1
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/model/cloneCsn.js +1 -0
- package/lib/optionProcessor.js +8 -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/forOdata.js +91 -2
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/odata/flattening.js +1 -1
- 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
|
@@ -3506,6 +3506,7 @@
|
|
|
3506
3506
|
"Label": "Edm.String",
|
|
3507
3507
|
"Parameters": "Collection(Common.ValueListParameter)",
|
|
3508
3508
|
"PresentationVariantQualifier": "Core.SimpleIdentifier",
|
|
3509
|
+
"RelativeCollectionPath": "Edm.NavigationPropertyPath",
|
|
3509
3510
|
"SearchSupported": "Edm.Boolean",
|
|
3510
3511
|
"SelectionVariantQualifier": "Core.SimpleIdentifier"
|
|
3511
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
|
];
|
|
@@ -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/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.
|
|
@@ -301,7 +302,7 @@ optionProcessor.command('O, toOdata')
|
|
|
301
302
|
--odata-no-creator Omit creator identification in API
|
|
302
303
|
--draft-messages Add draft messages as part of the draft creation
|
|
303
304
|
--add-annotation-AddressViaNavigationPath Add annotation "@Common.AddressViaNavigationPath" to the services
|
|
304
|
-
containing draft enabled entitties
|
|
305
|
+
containing draft enabled entitties
|
|
305
306
|
-n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is
|
|
306
307
|
the corresponding database name (see "--sql-mapping" for "toSql")
|
|
307
308
|
plain : (default) Names in uppercase and flattened with underscores
|
|
@@ -534,15 +535,15 @@ optionProcessor.command('toCsn')
|
|
|
534
535
|
--with-localized Add localized convenience views to the CSN output.
|
|
535
536
|
`);
|
|
536
537
|
|
|
537
|
-
optionProcessor.command('parseCdl')
|
|
538
|
+
optionProcessor.command('parse', { aliases: [ 'parseCdl' ] })
|
|
538
539
|
.option('-h, --help')
|
|
539
540
|
.positionalArgument('<file>')
|
|
540
541
|
.option(' --with-locations')
|
|
541
542
|
.help(`
|
|
542
|
-
Usage: cdsc
|
|
543
|
+
Usage: cdsc parse [options] <file>
|
|
543
544
|
|
|
544
|
-
Only parse the CDL
|
|
545
|
-
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.
|
|
546
547
|
|
|
547
548
|
Options
|
|
548
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
|
*
|