@sap/cds-compiler 6.1.0 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/bin/cdsc.js +17 -6
- package/bin/cdsse.js +1 -1
- package/bin/cdsv2m.js +1 -1
- package/lib/api/main.js +29 -7
- package/lib/api/options.js +1 -1
- package/lib/base/builtins.js +9 -0
- package/lib/base/keywords.js +1 -1
- package/lib/base/message-registry.js +41 -10
- package/lib/base/messages.js +13 -6
- package/lib/base/model.js +1 -1
- package/lib/base/optionProcessorHelper.js +7 -2
- package/lib/checks/assocOutsideService.js +17 -30
- package/lib/checks/checkForTypes.js +0 -18
- package/lib/checks/checkPathsInStoredCalcElement.js +2 -1
- package/lib/checks/featureFlags.js +4 -1
- package/lib/checks/onConditions.js +2 -2
- package/lib/checks/queryNoDbArtifacts.js +16 -15
- package/lib/checks/types.js +1 -1
- package/lib/checks/utils.js +30 -6
- package/lib/checks/validator.js +4 -5
- 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 +85 -39
- 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 +88 -6
- 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 +22 -21
- package/lib/compiler/shared.js +6 -6
- package/lib/compiler/tweak-assocs.js +53 -31
- 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 +1424 -1430
- package/lib/gen/Dictionary.json +1 -2
- 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/model/csnRefs.js +9 -4
- package/lib/model/csnUtils.js +67 -2
- package/lib/optionProcessor.js +9 -9
- package/lib/parsers/AstBuildingParser.js +28 -26
- package/lib/parsers/identifiers.js +2 -30
- package/lib/render/toCdl.js +73 -13
- package/lib/render/toSql.js +127 -108
- package/lib/render/utils/common.js +4 -2
- package/lib/render/utils/sql.js +67 -0
- package/lib/transform/addTenantFields.js +4 -4
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/associations.js +37 -1
- package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
- package/lib/transform/db/assocsToQueries/utils.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +37 -36
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +20 -2
- package/lib/transform/draft/db.js +20 -20
- package/lib/transform/draft/odata.js +38 -40
- package/lib/transform/effective/associations.js +1 -1
- package/lib/transform/effective/flattening.js +40 -47
- package/lib/transform/effective/main.js +6 -4
- package/lib/transform/forOdata.js +201 -92
- package/lib/transform/forRelationalDB.js +151 -142
- package/lib/transform/localized.js +116 -109
- package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
- package/lib/transform/odata/createForeignKeys.js +73 -70
- package/lib/transform/odata/flattening.js +216 -200
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
- package/lib/transform/odata/toFinalBaseType.js +40 -39
- package/lib/transform/odata/typesExposure.js +151 -133
- package/lib/transform/odata/utils.js +7 -6
- package/lib/transform/parseExpr.js +165 -162
- package/lib/transform/transformUtils.js +184 -551
- package/lib/transform/translateAssocsToJoins.js +511 -596
- package/lib/transform/tupleExpansion.js +495 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +2 -2
- package/lib/base/cleanSymbols.js +0 -17
- package/lib/checks/nonexpandableStructured.js +0 -39
package/lib/gen/Dictionary.json
CHANGED
|
@@ -808,7 +808,6 @@
|
|
|
808
808
|
"Type": "Core.Tag"
|
|
809
809
|
},
|
|
810
810
|
"Common.IsTimezone": {
|
|
811
|
-
"$experimental": true,
|
|
812
811
|
"AppliesTo": [
|
|
813
812
|
"Property",
|
|
814
813
|
"Parameter"
|
|
@@ -1035,7 +1034,6 @@
|
|
|
1035
1034
|
"Type": "Edm.DateTimeOffset"
|
|
1036
1035
|
},
|
|
1037
1036
|
"Common.Timezone": {
|
|
1038
|
-
"$experimental": true,
|
|
1039
1037
|
"AppliesTo": [
|
|
1040
1038
|
"Property",
|
|
1041
1039
|
"Parameter"
|
|
@@ -3506,6 +3504,7 @@
|
|
|
3506
3504
|
"Label": "Edm.String",
|
|
3507
3505
|
"Parameters": "Collection(Common.ValueListParameter)",
|
|
3508
3506
|
"PresentationVariantQualifier": "Core.SimpleIdentifier",
|
|
3507
|
+
"RelativeCollectionPath": "Edm.NavigationPropertyPath",
|
|
3509
3508
|
"SearchSupported": "Edm.Boolean",
|
|
3510
3509
|
"SelectionVariantQualifier": "Core.SimpleIdentifier"
|
|
3511
3510
|
}
|
|
@@ -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/model/csnRefs.js
CHANGED
|
@@ -250,10 +250,9 @@ function csnRefs( csn, universalReady ) {
|
|
|
250
250
|
// const { definitions } = csn;
|
|
251
251
|
const cache = new WeakMap();
|
|
252
252
|
setCache( BUILTIN_TYPE, '_origin', null );
|
|
253
|
-
if (universalReady === 'init-all')
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
253
|
+
if (universalReady === 'init-all')
|
|
254
|
+
initAllDefinitions();
|
|
255
|
+
|
|
257
256
|
// Functions which set the new `baseEnv`:
|
|
258
257
|
resolveRef.expandInline = function resolveExpandInline( ref, ...args ) {
|
|
259
258
|
return cached( ref, '_env', () => navigationEnv( resolveRef( ref, ...args ).art ) );
|
|
@@ -278,6 +277,7 @@ function csnRefs( csn, universalReady ) {
|
|
|
278
277
|
getColumnName: col => getCache( col, '$as' ),
|
|
279
278
|
$getQueries: def => getCache( def, '$queries' ), // unstable API
|
|
280
279
|
initDefinition,
|
|
280
|
+
initAllDefinitions,
|
|
281
281
|
dropDefinitionCache,
|
|
282
282
|
targetAspect,
|
|
283
283
|
msgLocations,
|
|
@@ -489,6 +489,11 @@ function csnRefs( csn, universalReady ) {
|
|
|
489
489
|
return query && cache.get( query.projection || query );
|
|
490
490
|
}
|
|
491
491
|
|
|
492
|
+
function initAllDefinitions() {
|
|
493
|
+
for (const name of Object.keys( csn.definitions || {}))
|
|
494
|
+
initDefinition( name );
|
|
495
|
+
}
|
|
496
|
+
|
|
492
497
|
function initDefinition( main ) {
|
|
493
498
|
const name = typeof main === 'string' && main;
|
|
494
499
|
if (name) {
|
package/lib/model/csnUtils.js
CHANGED
|
@@ -64,6 +64,7 @@ function getUtils( model, universalReady ) {
|
|
|
64
64
|
addStringAnnotationTo,
|
|
65
65
|
getServiceName,
|
|
66
66
|
getFinalTypeInfo,
|
|
67
|
+
resolvePath,
|
|
67
68
|
get$combined,
|
|
68
69
|
getQueryPrimarySource,
|
|
69
70
|
..._csnRefs,
|
|
@@ -448,6 +449,71 @@ function getUtils( model, universalReady ) {
|
|
|
448
449
|
return target;
|
|
449
450
|
}
|
|
450
451
|
}
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Resolve the type of an artifact
|
|
456
|
+
* If art is undefined, stop
|
|
457
|
+
* If art has elements or items.elements, stop
|
|
458
|
+
* If art has a type and the type is scalar, stop
|
|
459
|
+
* If art has a named type or a type ref, resolve it
|
|
460
|
+
*/
|
|
461
|
+
function resolveType( art ) {
|
|
462
|
+
while (art &&
|
|
463
|
+
!((art.items && art.items.elements) || art.elements) &&
|
|
464
|
+
(art.type &&
|
|
465
|
+
((!art.type.ref && !isBuiltinType(art.type)) || art.type.ref))) {
|
|
466
|
+
if (art.type.ref)
|
|
467
|
+
art = resolvePath(art.type);
|
|
468
|
+
else
|
|
469
|
+
art = model.definitions[art.type];
|
|
470
|
+
}
|
|
471
|
+
return art;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Path resolution, attach artifact to each path step, if found,
|
|
476
|
+
* Dereference types and follow associations.
|
|
477
|
+
*
|
|
478
|
+
* @param {any} path ref object
|
|
479
|
+
* @param {any} art start environment
|
|
480
|
+
* @returns {any} path with resolved artifacts or artifact
|
|
481
|
+
* (if called with simple ref paths)
|
|
482
|
+
*/
|
|
483
|
+
function resolvePath( path, art = undefined ) {
|
|
484
|
+
let notFound = false;
|
|
485
|
+
for (let i = 0; i < path.ref.length && !notFound; i++) {
|
|
486
|
+
const ps = path.ref[i];
|
|
487
|
+
const id = ps.id || ps;
|
|
488
|
+
if (art) {
|
|
489
|
+
if (art.target)
|
|
490
|
+
art = model.definitions[art.target].elements[id];
|
|
491
|
+
else if (art.items && art.items.elements || art.elements)
|
|
492
|
+
art = (art.items && art.items.elements || art.elements)[id];
|
|
493
|
+
|
|
494
|
+
else
|
|
495
|
+
art = undefined;
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
art = model.definitions[id];
|
|
499
|
+
}
|
|
500
|
+
art = resolveType(art);
|
|
501
|
+
|
|
502
|
+
// if path step has id, store art
|
|
503
|
+
if (ps.id && art)
|
|
504
|
+
ps._art = art;
|
|
505
|
+
notFound = !art;
|
|
506
|
+
}
|
|
507
|
+
// if resolve was called on constraint path, path has id.
|
|
508
|
+
// Store art and return path, if called recursively for model ref paths,
|
|
509
|
+
// return artifact only
|
|
510
|
+
if (path.ref[0].id) {
|
|
511
|
+
if (art)
|
|
512
|
+
path._art = art;
|
|
513
|
+
return path;
|
|
514
|
+
}
|
|
515
|
+
return art;
|
|
516
|
+
}
|
|
451
517
|
}
|
|
452
518
|
|
|
453
519
|
|
|
@@ -1248,7 +1314,6 @@ function walkCsnPath( csn, path ) {
|
|
|
1248
1314
|
for (const segment of path)
|
|
1249
1315
|
obj = obj[segment];
|
|
1250
1316
|
|
|
1251
|
-
|
|
1252
1317
|
return obj;
|
|
1253
1318
|
}
|
|
1254
1319
|
|
|
@@ -1356,7 +1421,7 @@ function functionList( functions, thisArg ) {
|
|
|
1356
1421
|
* @returns {boolean}
|
|
1357
1422
|
*/
|
|
1358
1423
|
function isDollarSelfOrProjectionOperand( arg ) {
|
|
1359
|
-
return arg.ref
|
|
1424
|
+
return arg.ref?.length === 1 &&
|
|
1360
1425
|
(arg.ref[0] === '$self' || arg.ref[0] === '$projection');
|
|
1361
1426
|
}
|
|
1362
1427
|
|
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.
|
|
@@ -264,7 +265,7 @@ optionProcessor.command('O, toOdata')
|
|
|
264
265
|
.option(' --odata-vocabularies <list>')
|
|
265
266
|
.option(' --odata-no-creator')
|
|
266
267
|
.option(' --draft-messages')
|
|
267
|
-
.option(' --
|
|
268
|
+
.option(' --draft-user-description')
|
|
268
269
|
.option('-c, --csn')
|
|
269
270
|
.option('-f, --odata-format <format>', { valid: [ 'flat', 'structured' ] })
|
|
270
271
|
.option('-n, --sql-mapping <style>', { valid: [ 'plain', 'quoted', 'hdbcds' ], aliases: [ '--names' ] })
|
|
@@ -300,8 +301,7 @@ optionProcessor.command('O, toOdata')
|
|
|
300
301
|
{ prefix: { alias, ns, uri }, ... }
|
|
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
|
-
containing draft enabled entitties
|
|
304
|
+
--draft-user-description Add user description fields to the DraftAdministrativeData entity
|
|
305
305
|
-n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is
|
|
306
306
|
the corresponding database name (see "--sql-mapping" for "toSql")
|
|
307
307
|
plain : (default) Names in uppercase and flattened with underscores
|
|
@@ -534,15 +534,15 @@ optionProcessor.command('toCsn')
|
|
|
534
534
|
--with-localized Add localized convenience views to the CSN output.
|
|
535
535
|
`);
|
|
536
536
|
|
|
537
|
-
optionProcessor.command('parseCdl')
|
|
537
|
+
optionProcessor.command('parse', { aliases: [ 'parseCdl' ] })
|
|
538
538
|
.option('-h, --help')
|
|
539
539
|
.positionalArgument('<file>')
|
|
540
540
|
.option(' --with-locations')
|
|
541
541
|
.help(`
|
|
542
|
-
Usage: cdsc
|
|
542
|
+
Usage: cdsc parse [options] <file>
|
|
543
543
|
|
|
544
|
-
Only parse the CDL
|
|
545
|
-
resolve imports, apply extensions or expand any queries.
|
|
544
|
+
Only parse the input file. For CDL input, output a CSN that is close to the source.
|
|
545
|
+
Does not resolve imports, apply extensions or expand any queries.
|
|
546
546
|
|
|
547
547
|
Options
|
|
548
548
|
--with-locations Add $location to CSN artifacts.
|
|
@@ -47,7 +47,10 @@ 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
|
+
fail: true,
|
|
53
|
+
};
|
|
51
54
|
|
|
52
55
|
constructor( lexer, keywords, table, options, messageFunctions ) {
|
|
53
56
|
super( lexer, keywords, table ); // lexer has file
|
|
@@ -115,13 +118,16 @@ class AstBuildingParser extends BaseParser {
|
|
|
115
118
|
|
|
116
119
|
// Guards, Prepare Commands and Lookahead Methods -----------------------------
|
|
117
120
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
queryOnLeftSloppyAlias( _arg, mode ) {
|
|
122
|
+
if (mode === 'M' || this.isNoKeywordInRuleFollow( _arg, mode ))
|
|
123
|
+
return true;
|
|
124
|
+
// TODO TOOL: have a base parser method for the test
|
|
125
|
+
if (this.conditionTokenIdx === this.tokenIdx && // tested on same
|
|
126
|
+
this.conditionStackLength == null) // after error recovery
|
|
127
|
+
return false;
|
|
128
|
+
if (this.constructor.tracingParser)
|
|
129
|
+
this._traceSubPush( 'queryOnLeft' );
|
|
130
|
+
return this.queryOnLeft( 'table', mode );
|
|
125
131
|
}
|
|
126
132
|
|
|
127
133
|
/**
|
|
@@ -134,15 +140,9 @@ class AstBuildingParser extends BaseParser {
|
|
|
134
140
|
* - <guard=queryOnLeft> tests whether the expression on the left is a query
|
|
135
141
|
* - <guard=queryOnLeft, arg=‹SomeVal›>: tests whether the expression on the
|
|
136
142
|
* 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
143
|
*/
|
|
140
144
|
queryOnLeft( arg, test ) {
|
|
141
|
-
if (arg
|
|
142
|
-
if (this.tableWithoutAs())
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
else if (!arg && !test) {
|
|
145
|
+
if (!arg && !test) {
|
|
146
146
|
// provide new dynamic parentheses context, except with direct
|
|
147
147
|
// recursive call:
|
|
148
148
|
if (this.inSameRule_( this.s, this.stack.at( -1 ).followState ))
|
|
@@ -202,6 +202,10 @@ class AstBuildingParser extends BaseParser {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
addTokenToSet_( set, tokenName, val, collectKeywordsOnly ) {
|
|
205
|
+
if (!this.dynamic_.call) {
|
|
206
|
+
super.addTokenToSet_( set, tokenName, val, collectKeywordsOnly );
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
205
209
|
const token = parserTokens[tokenName];
|
|
206
210
|
// TODO: use lower-case in specialFunctions
|
|
207
211
|
const realTokens = token && this.dynamic_.generic?.[token];
|
|
@@ -214,7 +218,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
214
218
|
// Works, since `DeleteStarFromSet` comes after `*` (length-sorted):
|
|
215
219
|
delete set['*'];
|
|
216
220
|
}
|
|
217
|
-
else {
|
|
221
|
+
else if (!token) {
|
|
218
222
|
super.addTokenToSet_( set, tokenName, val, collectKeywordsOnly );
|
|
219
223
|
}
|
|
220
224
|
}
|
|
@@ -488,10 +492,6 @@ class AstBuildingParser extends BaseParser {
|
|
|
488
492
|
// token is not on a new line?
|
|
489
493
|
}
|
|
490
494
|
|
|
491
|
-
atRightParen() {
|
|
492
|
-
return this.l() !== ')';
|
|
493
|
-
}
|
|
494
|
-
|
|
495
495
|
/**
|
|
496
496
|
* For annotations at the beginning of columns outside parentheses
|
|
497
497
|
*/
|
|
@@ -570,7 +570,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
570
570
|
if (this.l() === ':') {
|
|
571
571
|
this.warning( 'syntax-missing-parens', name,
|
|
572
572
|
{ code: '@‹anno›', op: ':', newcode: '@(‹anno›…)' },
|
|
573
|
-
// eslint-disable-next-line @stylistic/
|
|
573
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
574
574
|
'When $(CODE) is followed by $(OP), use $(NEWCODE) for annotation assignments at this position' );
|
|
575
575
|
}
|
|
576
576
|
}
|
|
@@ -582,7 +582,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
582
582
|
// do not report error if the '@' is not correct:
|
|
583
583
|
this.s !== null && this.tokenIdx > this.recoverTokenIdx) {
|
|
584
584
|
this.warning( 'syntax-missing-semicolon', next, { code: ';' },
|
|
585
|
-
// eslint-disable-next-line @stylistic/
|
|
585
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
586
586
|
'Add a $(CODE) and/or newline before the annotation assignment to indicate that it belongs to the next statement' );
|
|
587
587
|
}
|
|
588
588
|
}
|
|
@@ -737,7 +737,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
737
737
|
}
|
|
738
738
|
else if (text.charAt(0) !== '!') {
|
|
739
739
|
this.message( 'syntax-deprecated-ident', location, { delimited: id },
|
|
740
|
-
// eslint-disable-next-line @stylistic/
|
|
740
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
741
741
|
'Deprecated delimited identifier syntax, use $(DELIMITED) - strings are delimited by single quotes' );
|
|
742
742
|
}
|
|
743
743
|
}
|
|
@@ -948,7 +948,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
948
948
|
{
|
|
949
949
|
std: 'Annotation number $(RAWVALUE) is put as $(VALUE) into the CSN',
|
|
950
950
|
rounded: 'Annotation number $(RAWVALUE) is rounded to $(VALUE)',
|
|
951
|
-
// eslint-disable-next-line @stylistic/
|
|
951
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
952
952
|
infinite: 'Annotation value $(RAWVALUE) is infinite as number and put as string into the CSN',
|
|
953
953
|
} );
|
|
954
954
|
}
|
|
@@ -1000,7 +1000,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
1000
1000
|
|
|
1001
1001
|
// TODO: can we remove `;`/EOF from the expected-set for `annotate Foo with ⎀`?
|
|
1002
1002
|
checkWith( keyword ) {
|
|
1003
|
-
if (this.lb() !== keyword)
|
|
1003
|
+
if (this.lb() !== keyword || ![ ';', '}', 'EOF' ].includes( this.l() ))
|
|
1004
1004
|
return;
|
|
1005
1005
|
const tok = this.la();
|
|
1006
1006
|
const docTokenIndex = this.docCommentIndex &&
|
|
@@ -1012,7 +1012,7 @@ class AstBuildingParser extends BaseParser {
|
|
|
1012
1012
|
const expecting = this.expectingArray().filter( t => t !== '<EOF>' && t !== '\'}\'' );
|
|
1013
1013
|
const msg = this.warning( 'syntax-unexpected-semicolon', tok,
|
|
1014
1014
|
{ offending: this.antlrName( tok ), expecting, keyword: 'with' },
|
|
1015
|
-
// eslint-disable-next-line @stylistic/
|
|
1015
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1016
1016
|
'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous $(KEYWORD)' );
|
|
1017
1017
|
msg.expectedTokens = expecting;
|
|
1018
1018
|
}
|
|
@@ -1523,6 +1523,8 @@ class AstBuildingParser extends BaseParser {
|
|
|
1523
1523
|
}
|
|
1524
1524
|
}
|
|
1525
1525
|
|
|
1526
|
+
AstBuildingParser.prototype.queryOnLeftSloppyAlias.afterError = true;
|
|
1527
|
+
|
|
1526
1528
|
function addOneForDefinition( count, ext ) {
|
|
1527
1529
|
return (ext.kind === 'extend') ? count : count + 1;
|
|
1528
1530
|
}
|
|
@@ -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
|
@@ -796,10 +796,15 @@ class CsnToCdl {
|
|
|
796
796
|
}
|
|
797
797
|
|
|
798
798
|
// Top-level annotations of the artifact
|
|
799
|
-
|
|
799
|
+
const topLevelAnnotations = this.renderAnnotationAssignmentsAndDocComment(ext, env.withIncreasedIndent());
|
|
800
|
+
|
|
800
801
|
// Note: Not renderDefinitionReference, because we don't care if there
|
|
801
802
|
// are annotations to unknown things. That's allowed!
|
|
802
|
-
result
|
|
803
|
+
let result = `${ env.indent }annotate ${ this.renderArtifactName(ext.annotate, env) }`;
|
|
804
|
+
|
|
805
|
+
if (topLevelAnnotations)
|
|
806
|
+
result += ` with\n${ topLevelAnnotations }`;
|
|
807
|
+
|
|
803
808
|
|
|
804
809
|
if (ext.params)
|
|
805
810
|
result += this.renderAnnotateParamsInParentheses(ext, env);
|
|
@@ -1368,7 +1373,7 @@ class CsnToCdl {
|
|
|
1368
1373
|
}
|
|
1369
1374
|
|
|
1370
1375
|
/**
|
|
1371
|
-
* Render the given columns.
|
|
1376
|
+
* Render the given columns / select items.
|
|
1372
1377
|
*
|
|
1373
1378
|
* @param {CSN.Extension | CSN.QuerySelect} art
|
|
1374
1379
|
* @param {object} elements
|
|
@@ -1439,7 +1444,7 @@ class CsnToCdl {
|
|
|
1439
1444
|
if (col.cast.target && !col.cast.type)
|
|
1440
1445
|
result += ` : ${ this.renderRedirectedTo(col.cast, env) }`;
|
|
1441
1446
|
else
|
|
1442
|
-
result += ` : ${ this.renderTypeReferenceAndProps(col.cast, env, {
|
|
1447
|
+
result += ` : ${ this.renderTypeReferenceAndProps(col.cast, env, { noAnnoCollect: true }) }`;
|
|
1443
1448
|
env.path.length -= 1;
|
|
1444
1449
|
}
|
|
1445
1450
|
return result;
|
|
@@ -1459,6 +1464,7 @@ class CsnToCdl {
|
|
|
1459
1464
|
|
|
1460
1465
|
const isAnonymousExpand = (obj.expand && !obj.ref);
|
|
1461
1466
|
|
|
1467
|
+
|
|
1462
1468
|
// s as alias { * }
|
|
1463
1469
|
if (obj.as && !isAnonymousExpand)
|
|
1464
1470
|
result += this.renderAlias(obj.as, env);
|
|
@@ -1483,7 +1489,7 @@ class CsnToCdl {
|
|
|
1483
1489
|
const childEnv = env.withIncreasedIndent();
|
|
1484
1490
|
const expandInline = obj.expand || obj.inline;
|
|
1485
1491
|
result += expandInline //
|
|
1486
|
-
.map(elm => this.
|
|
1492
|
+
.map(elm => this.renderViewColumn(elm, childEnv))
|
|
1487
1493
|
.join(',\n');
|
|
1488
1494
|
result += `\n${ env.indent }}`;
|
|
1489
1495
|
|
|
@@ -1869,9 +1875,12 @@ class CsnToCdl {
|
|
|
1869
1875
|
if (artifact.on)
|
|
1870
1876
|
result += ` on ${ this.exprRenderer.renderExpr(artifact.on, env.withSubPath([ 'on' ])) }`;
|
|
1871
1877
|
|
|
1872
|
-
// Foreign keys (if any, unless we also have an
|
|
1873
|
-
if (artifact.keys && !artifact.on)
|
|
1874
|
-
|
|
1878
|
+
// Foreign keys (if any, unless we also have an ON-condition (which means we have been transformed from managed to unmanaged)
|
|
1879
|
+
if (artifact.keys && !artifact.on) {
|
|
1880
|
+
const keys = this.renderForeignKeys(artifact, env, false);
|
|
1881
|
+
if (keys)
|
|
1882
|
+
result += ` ${ keys }`;
|
|
1883
|
+
}
|
|
1875
1884
|
|
|
1876
1885
|
if (!artifact.on) {
|
|
1877
1886
|
// unmanaged associations can't be followed by "not null" or "default"
|
|
@@ -1923,10 +1932,14 @@ class CsnToCdl {
|
|
|
1923
1932
|
*/
|
|
1924
1933
|
renderRedirectedTo( art, env ) {
|
|
1925
1934
|
let result = `redirected to ${ this.renderDefinitionReference(art.target, env) }`;
|
|
1926
|
-
if (art.on)
|
|
1935
|
+
if (art.on) {
|
|
1927
1936
|
result += ` on ${ this.exprRenderer.renderExpr(art.on, env.withSubPath([ 'on' ])) }`;
|
|
1928
|
-
|
|
1929
|
-
|
|
1937
|
+
}
|
|
1938
|
+
else if (art.keys) {
|
|
1939
|
+
const keys = this.renderForeignKeys(art, env, true);
|
|
1940
|
+
if (keys)
|
|
1941
|
+
result += ` ${ keys }`;
|
|
1942
|
+
}
|
|
1930
1943
|
return result;
|
|
1931
1944
|
}
|
|
1932
1945
|
|
|
@@ -2274,13 +2287,16 @@ class CsnToCdl {
|
|
|
2274
2287
|
}
|
|
2275
2288
|
|
|
2276
2289
|
/**
|
|
2277
|
-
* Render foreign keys.
|
|
2290
|
+
* Render foreign keys. We only render foreign keys if necessary or if we can't say for sure
|
|
2291
|
+
* that the foreign keys would match the implicitly generated ones.
|
|
2278
2292
|
*
|
|
2279
2293
|
* @param {object} art
|
|
2280
2294
|
* @param {CdlRenderEnvironment} env
|
|
2295
|
+
* @param {boolean} alwaysRenderForeignKeys
|
|
2296
|
+
* If false, only render foreign keys if necessary (i.e. can't be inferred by compiler again).
|
|
2281
2297
|
* @return {string}
|
|
2282
2298
|
*/
|
|
2283
|
-
renderForeignKeys( art, env ) {
|
|
2299
|
+
renderForeignKeys( art, env, alwaysRenderForeignKeys = false ) {
|
|
2284
2300
|
const renderedKeys = [];
|
|
2285
2301
|
let hasAnnotations = false;
|
|
2286
2302
|
env = env.withSubPath([ 'keys', -1 ]);
|
|
@@ -2301,18 +2317,62 @@ class CsnToCdl {
|
|
|
2301
2317
|
renderedKeys.push(`${ key }${ alias },`);
|
|
2302
2318
|
}
|
|
2303
2319
|
|
|
2320
|
+
// Annotations on foreign keys always require rendering of explicit keys.
|
|
2321
|
+
// Otherwise, we'd have to use annotate statements here.
|
|
2304
2322
|
if (hasAnnotations) {
|
|
2305
2323
|
const sep = `\n${ env.indent }`;
|
|
2306
2324
|
env.decreaseIndent();
|
|
2307
2325
|
return `{${ sep }${ renderedKeys.join(sep) }\n${ env.indent }}`;
|
|
2308
2326
|
}
|
|
2309
2327
|
|
|
2328
|
+
if (!alwaysRenderForeignKeys && this.foreignKeysMatchImplicitOnes( art ))
|
|
2329
|
+
return '';
|
|
2330
|
+
|
|
2310
2331
|
let result = renderedKeys.join(' ');
|
|
2311
2332
|
if (result[result.length - 1] === ',') // remove trailing comma
|
|
2312
2333
|
result = result.slice(0, -1);
|
|
2313
2334
|
return `{ ${ result } }`;
|
|
2314
2335
|
}
|
|
2315
2336
|
|
|
2337
|
+
/**
|
|
2338
|
+
* Returns true, if `to.cdl()` could leave out explicit foreign keys in the rendered output
|
|
2339
|
+
* without changing recompilation. We do so, because explicit foreign keys are not promoted
|
|
2340
|
+
* on CAPire.
|
|
2341
|
+
*
|
|
2342
|
+
* @param {CSN.Artifact} art
|
|
2343
|
+
* @returns {boolean}
|
|
2344
|
+
*/
|
|
2345
|
+
foreignKeysMatchImplicitOnes( art ) {
|
|
2346
|
+
if (art.cardinality && art.cardinality.max !== 1)
|
|
2347
|
+
return false; // e.g. to-many assocs
|
|
2348
|
+
|
|
2349
|
+
const target = this.csn.definitions[art.target];
|
|
2350
|
+
if (!art.keys?.length || !art.target || !target?.elements)
|
|
2351
|
+
return false;
|
|
2352
|
+
|
|
2353
|
+
// We could improve to.cdl() to properly check if keys match, but then we'd have to look
|
|
2354
|
+
// at sub-elements, structures, etc.; for now, we only check “simple” foreign keys that
|
|
2355
|
+
// don't have any alias.
|
|
2356
|
+
const hasComplexKeys = art.keys.some(key => key.ref?.length !== 1 || key.as !== undefined);
|
|
2357
|
+
if (hasComplexKeys)
|
|
2358
|
+
return false;
|
|
2359
|
+
|
|
2360
|
+
const targetKeys = Object.keys(target.elements).filter(name => target.elements[name]?.key);
|
|
2361
|
+
if (targetKeys.length === 0)
|
|
2362
|
+
return false; // always require explicit keys if there are no target side keys
|
|
2363
|
+
|
|
2364
|
+
const foreignKeys = art.keys.map(key => key.ref[0]);
|
|
2365
|
+
if (foreignKeys.length !== targetKeys.length)
|
|
2366
|
+
return false;
|
|
2367
|
+
|
|
2368
|
+
// We require the _same_ order! Otherwise, recompilation would change the generated foreign key order!
|
|
2369
|
+
for (let i = 0; i < foreignKeys.length; ++i) {
|
|
2370
|
+
if (foreignKeys[i] !== targetKeys[i])
|
|
2371
|
+
return false;
|
|
2372
|
+
}
|
|
2373
|
+
return true;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2316
2376
|
/**
|
|
2317
2377
|
* Render an explicit alias, e.g. for columns.
|
|
2318
2378
|
*
|