@sap/cds-compiler 4.0.2 → 4.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 +200 -5
- package/bin/cdsc.js +18 -15
- package/doc/CHANGELOG_BETA.md +16 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +33 -13
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +25 -25
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +123 -42
- package/lib/base/messages.js +18 -10
- package/lib/base/model.js +43 -10
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/elements.js +11 -10
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +22 -14
- package/lib/checks/queryNoDbArtifacts.js +132 -73
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +4 -3
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +71 -40
- package/lib/compiler/base.js +7 -2
- package/lib/compiler/builtins.js +40 -41
- package/lib/compiler/checks.js +415 -367
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +9 -9
- package/lib/compiler/define.js +124 -90
- package/lib/compiler/extend.js +115 -88
- package/lib/compiler/finalize-parse-cdl.js +26 -25
- package/lib/compiler/generate.js +57 -49
- package/lib/compiler/index.js +56 -56
- package/lib/compiler/kick-start.js +10 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +180 -144
- package/lib/compiler/propagator.js +10 -9
- package/lib/compiler/resolve.js +321 -246
- package/lib/compiler/shared.js +812 -433
- package/lib/compiler/tweak-assocs.js +114 -50
- package/lib/compiler/utils.js +241 -46
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +679 -770
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +689 -648
- package/lib/edm/edmUtils.js +279 -300
- package/lib/gen/Dictionary.json +34 -10
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2857 -2856
- package/lib/json/from-csn.js +77 -51
- package/lib/json/to-csn.js +15 -15
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +61 -64
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +65 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +51 -18
- package/lib/model/revealInternalProperties.js +30 -22
- package/lib/modelCompare/compare.js +149 -41
- package/lib/modelCompare/utils/filter.js +55 -25
- package/lib/optionProcessor.js +21 -9
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +63 -23
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +82 -35
- package/lib/render/utils/common.js +11 -9
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +62 -21
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +9 -9
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +138 -68
- package/lib/transform/db/flattening.js +98 -30
- package/lib/transform/db/rewriteCalculatedElements.js +20 -14
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +8 -7
- package/lib/transform/db/views.js +73 -33
- package/lib/transform/draft/db.js +11 -9
- package/lib/transform/draft/odata.js +1 -1
- package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
- package/lib/transform/forRelationalDB.js +148 -136
- package/lib/transform/localized.js +92 -54
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
- package/lib/transform/translateAssocsToJoins.js +14 -28
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
- package/share/messages/message-explanations.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Base classes used as prototypes for XSN definitions, elements, etc.
|
|
2
|
+
// The goal is to have named classes that can be seen in performance analyses, e.g.
|
|
3
|
+
// by using the [DeOpt Explorer][1].
|
|
4
|
+
// All classes should also be constructible using `{ __proto__: Class, …}`, i.e.
|
|
5
|
+
// their constructors must not do anything besides assigning properties.
|
|
6
|
+
//
|
|
7
|
+
// Refer to these resources:
|
|
8
|
+
// - <https://mathiasbynens.be/notes/shapes-ics>
|
|
9
|
+
// - <https://v8.dev/blog/fast-properties>
|
|
10
|
+
//
|
|
11
|
+
// Before adding new properties, evaluate whether it has any performance
|
|
12
|
+
// impact. Too many properties that are rarely used could reduce performance,
|
|
13
|
+
// but too few could lead to inconsistent object shapes for commonly
|
|
14
|
+
// used properties.
|
|
15
|
+
//
|
|
16
|
+
// Use [DeOpt Explorer][1] to see the different object Maps by v8.
|
|
17
|
+
//
|
|
18
|
+
// [1]: https://devblogs.microsoft.com/typescript/introducing-deopt-explorer/
|
|
19
|
+
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
class XsnSource {
|
|
23
|
+
kind = 'source';
|
|
24
|
+
location;
|
|
25
|
+
usings = [];
|
|
26
|
+
dependencies = [];
|
|
27
|
+
artifacts = Object.create( null );
|
|
28
|
+
vocabularies = Object.create( null );
|
|
29
|
+
extensions = [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class XsnArtifact {
|
|
33
|
+
location;
|
|
34
|
+
name;
|
|
35
|
+
kind;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class XsnName {
|
|
39
|
+
location;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class CsnLocation {
|
|
43
|
+
file;
|
|
44
|
+
line;
|
|
45
|
+
col;
|
|
46
|
+
endLine;
|
|
47
|
+
endCol;
|
|
48
|
+
constructor(file, line, col, endLine, endCol) {
|
|
49
|
+
this.file = file;
|
|
50
|
+
this.line = line;
|
|
51
|
+
this.col = col;
|
|
52
|
+
this.endLine = endLine;
|
|
53
|
+
this.endCol = endCol;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
XsnSource,
|
|
59
|
+
XsnArtifact,
|
|
60
|
+
XsnName,
|
|
61
|
+
CsnLocation,
|
|
62
|
+
};
|
|
@@ -30,15 +30,15 @@ function detectCycles( definitions, reportCycle, cbScc ) {
|
|
|
30
30
|
|
|
31
31
|
for (const name in definitions) {
|
|
32
32
|
const a = definitions[name];
|
|
33
|
-
if (Array.isArray(a))
|
|
33
|
+
if (Array.isArray( a ))
|
|
34
34
|
a.forEach( strongConnectRec );
|
|
35
35
|
else
|
|
36
36
|
strongConnectRec( a );
|
|
37
37
|
}
|
|
38
38
|
// now the cleanup
|
|
39
|
-
let nodes = Object.getOwnPropertyNames(definitions).map( n => definitions[n] );
|
|
39
|
+
let nodes = Object.getOwnPropertyNames( definitions ).map( n => definitions[n] );
|
|
40
40
|
while (nodes.length) { // still nodes to cleaned
|
|
41
|
-
nodes = cleanup(nodes);
|
|
41
|
+
nodes = cleanup( nodes );
|
|
42
42
|
}
|
|
43
43
|
return;
|
|
44
44
|
|
|
@@ -46,7 +46,7 @@ function detectCycles( definitions, reportCycle, cbScc ) {
|
|
|
46
46
|
if (a._scc) // already processed
|
|
47
47
|
return;
|
|
48
48
|
while (a)
|
|
49
|
-
a = strongConnect(a);
|
|
49
|
+
a = strongConnect( a );
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// Try to build a SCC starting from the node `v`.
|
|
@@ -60,18 +60,18 @@ function detectCycles( definitions, reportCycle, cbScc ) {
|
|
|
60
60
|
onStack: true,
|
|
61
61
|
depIndex: 0,
|
|
62
62
|
} );
|
|
63
|
-
stack.push(v);
|
|
63
|
+
stack.push( v );
|
|
64
64
|
// console.log('PUSH: ', v.kind,v.name)
|
|
65
65
|
}
|
|
66
66
|
if (!v._deps) // builtins, otherwise forgotten (TODO: assert in --test-mode)
|
|
67
|
-
setProp(v, '_deps', []);
|
|
67
|
+
setProp( v, '_deps', [] );
|
|
68
68
|
// assert( v._scc.onStack );
|
|
69
69
|
|
|
70
70
|
// Now consider successors of v (called w):
|
|
71
71
|
while (v._scc.depIndex < v._deps.length) {
|
|
72
72
|
const w = v._deps[v._scc.depIndex++].art;
|
|
73
73
|
if (!w._scc) { // node has not yet been visited
|
|
74
|
-
setProp(w, '_sccCaller', v);
|
|
74
|
+
setProp( w, '_sccCaller', v );
|
|
75
75
|
// console.log('CALL: ', v._scc.depIndex )
|
|
76
76
|
return w; // recursive call with w in recursive algorithm
|
|
77
77
|
}
|
|
@@ -112,7 +112,7 @@ function detectCycles( definitions, reportCycle, cbScc ) {
|
|
|
112
112
|
function defaultCb( w, v, r ) {
|
|
113
113
|
for (const dep of w._deps) {
|
|
114
114
|
if (dep.art._scc.lowlink === w._scc.lowlink) // in same SCC
|
|
115
|
-
reportCycle( w, dep.art, dep.location );
|
|
115
|
+
reportCycle( w, dep.art, dep.location, dep.semanticLoc );
|
|
116
116
|
}
|
|
117
117
|
return r;
|
|
118
118
|
}
|
|
@@ -127,7 +127,7 @@ function detectCycles( definitions, reportCycle, cbScc ) {
|
|
|
127
127
|
delete v._sccCaller;
|
|
128
128
|
for (const w of v._deps) {
|
|
129
129
|
if (w.art._scc)
|
|
130
|
-
todos.push(w.art);
|
|
130
|
+
todos.push( w.art );
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
}
|
package/lib/compiler/define.js
CHANGED
|
@@ -141,7 +141,8 @@ const {
|
|
|
141
141
|
const { compareLayer } = require('./moduleLayers');
|
|
142
142
|
const { initBuiltins, isInReservedNamespace } = require('./builtins');
|
|
143
143
|
|
|
144
|
-
const $location = Symbol.for('cds.$location');
|
|
144
|
+
const $location = Symbol.for( 'cds.$location' );
|
|
145
|
+
const $inferred = Symbol.for( 'cds.$inferred' );
|
|
145
146
|
|
|
146
147
|
/**
|
|
147
148
|
* Export function of this file. Transform argument `sources` = dictionary of
|
|
@@ -172,6 +173,7 @@ function define( model ) {
|
|
|
172
173
|
initArtifact,
|
|
173
174
|
initMembers,
|
|
174
175
|
checkDefinitions, // TODO: remove
|
|
176
|
+
initSelectItems,
|
|
175
177
|
} );
|
|
176
178
|
|
|
177
179
|
let boundSelfParamType = true; // special `$self` for binding param must still be initialised
|
|
@@ -192,11 +194,11 @@ function define( model ) {
|
|
|
192
194
|
beta: 'With option $(PROP), beta features and many other newer features are disabled',
|
|
193
195
|
} );
|
|
194
196
|
}
|
|
195
|
-
model.definitions = Object.create(null);
|
|
197
|
+
model.definitions = Object.create( null );
|
|
196
198
|
setLink( model, '_entities', [] ); // for entities with includes
|
|
197
199
|
model.$entity = 0;
|
|
198
|
-
model.$compositionTargets = Object.create(null);
|
|
199
|
-
model.$collectedExtensions = Object.create(null);
|
|
200
|
+
model.$compositionTargets = Object.create( null );
|
|
201
|
+
model.$collectedExtensions = Object.create( null );
|
|
200
202
|
|
|
201
203
|
initBuiltins( model );
|
|
202
204
|
const sourceNames = shuffleArray( Object.keys( model.sources ) );
|
|
@@ -226,7 +228,7 @@ function define( model ) {
|
|
|
226
228
|
|
|
227
229
|
let namespace = src.namespace && src.namespace.path;
|
|
228
230
|
let prefix = namespace ? `${ pathName( namespace ) }.` : '';
|
|
229
|
-
if (isInReservedNamespace(prefix)) {
|
|
231
|
+
if (isInReservedNamespace( prefix )) {
|
|
230
232
|
error( 'reserved-namespace-cds', [ src.namespace.location, src.namespace ], { name: 'cds' },
|
|
231
233
|
'The namespace $(NAME) is reserved for CDS builtins' );
|
|
232
234
|
namespace = null;
|
|
@@ -239,7 +241,7 @@ function define( model ) {
|
|
|
239
241
|
addPathPrefixes( src.artifacts, prefix ); // before addUsing
|
|
240
242
|
}
|
|
241
243
|
else if (src.usings || src.namespace) {
|
|
242
|
-
src.artifacts = Object.create(null);
|
|
244
|
+
src.artifacts = Object.create( null );
|
|
243
245
|
}
|
|
244
246
|
if (src.usings)
|
|
245
247
|
shuffleArray( src.usings ).forEach( u => addUsing( u, src ) );
|
|
@@ -252,11 +254,11 @@ function define( model ) {
|
|
|
252
254
|
}
|
|
253
255
|
else if (src.definitions) { // CSN input
|
|
254
256
|
prefix = '';
|
|
255
|
-
dictForEach( shuffleDict( src.definitions ),
|
|
257
|
+
dictForEach( shuffleDict( src.definitions ), def => addDefinition( def, src, prefix ) );
|
|
256
258
|
}
|
|
257
259
|
if (src.vocabularies) {
|
|
258
260
|
if (!model.vocabularies)
|
|
259
|
-
model.vocabularies = Object.create(null);
|
|
261
|
+
model.vocabularies = Object.create( null );
|
|
260
262
|
dictForEach( shuffleDict( src.vocabularies ), v => addVocabulary( v, src, prefix ) );
|
|
261
263
|
}
|
|
262
264
|
if (src.extensions) { // requires using to be known!
|
|
@@ -264,10 +266,14 @@ function define( model ) {
|
|
|
264
266
|
}
|
|
265
267
|
}
|
|
266
268
|
|
|
267
|
-
function addDefinition( art, block ) {
|
|
269
|
+
function addDefinition( art, block, prefix ) {
|
|
270
|
+
if (!art.name.absolute) {
|
|
271
|
+
// TODO: art.name.absolute = art.name.id || …
|
|
272
|
+
art.name.absolute = (!art.name.path) ? art.name.id : prefix + pathName( art.name.path );
|
|
273
|
+
}
|
|
268
274
|
const { absolute } = art.name;
|
|
269
275
|
// TODO: check reserved, see checkName()/checkLocalizedObjects() of checks.js
|
|
270
|
-
if (absolute === 'cds' || isInReservedNamespace(absolute)) {
|
|
276
|
+
if (absolute === 'cds' || isInReservedNamespace( absolute )) {
|
|
271
277
|
error( 'reserved-namespace-cds', [ art.name.location, art ], { name: 'cds' },
|
|
272
278
|
'The namespace $(NAME) is reserved for CDS builtins' );
|
|
273
279
|
const builtin = model.definitions[absolute];
|
|
@@ -292,7 +298,7 @@ function define( model ) {
|
|
|
292
298
|
function addPathPrefixes( artifacts, prefix ) {
|
|
293
299
|
for (const name in artifacts) {
|
|
294
300
|
const d = artifacts[name];
|
|
295
|
-
const a = Array.isArray(d) ? d[0] : d;
|
|
301
|
+
const a = Array.isArray( d ) ? d[0] : d;
|
|
296
302
|
if (!a.name.absolute)
|
|
297
303
|
a.name.absolute = prefix + name;
|
|
298
304
|
const index = name.indexOf( '.' );
|
|
@@ -357,7 +363,7 @@ function define( model ) {
|
|
|
357
363
|
// create using for own namespace:
|
|
358
364
|
const last = path[path.length - 1];
|
|
359
365
|
const { id } = last;
|
|
360
|
-
if (src.artifacts[id] || last.id.includes('.'))
|
|
366
|
+
if (src.artifacts[id] || last.id.includes( '.' ))
|
|
361
367
|
// not used as we have a definition/using with that name, or dotted last path id
|
|
362
368
|
return;
|
|
363
369
|
src.artifacts[id] = {
|
|
@@ -373,8 +379,7 @@ function define( model ) {
|
|
|
373
379
|
function addArtifact( art, block, prefix ) {
|
|
374
380
|
if (art.kind === 'using')
|
|
375
381
|
return;
|
|
376
|
-
art
|
|
377
|
-
addDefinition( art, block );
|
|
382
|
+
addDefinition( art, block, prefix );
|
|
378
383
|
if (art.artifacts) {
|
|
379
384
|
const p = `${ art.name.absolute }.`;
|
|
380
385
|
// path prefixes (usings) must be added before extensions in artifacts:
|
|
@@ -444,8 +449,12 @@ function define( model ) {
|
|
|
444
449
|
function addVocabulary( vocab, block, prefix ) {
|
|
445
450
|
setLink( vocab, '_block', block );
|
|
446
451
|
const { name } = vocab;
|
|
447
|
-
if (!name.absolute)
|
|
448
|
-
name.absolute =
|
|
452
|
+
if (!name.absolute) {
|
|
453
|
+
// TODO: art.name.absolute = vocab.name.id || …
|
|
454
|
+
vocab.name.absolute = (!vocab.name.path)
|
|
455
|
+
? vocab.name.id
|
|
456
|
+
: prefix + pathName( vocab.name.path );
|
|
457
|
+
}
|
|
449
458
|
dictAdd( model.vocabularies, name.absolute, vocab );
|
|
450
459
|
}
|
|
451
460
|
|
|
@@ -454,8 +463,8 @@ function define( model ) {
|
|
|
454
463
|
*/
|
|
455
464
|
function addI18nBlocks() {
|
|
456
465
|
// TODO: the sequence should be in sync with extend / annotate / future $sources
|
|
457
|
-
const sortedSources = Object.keys(model.sources)
|
|
458
|
-
.filter(name => !!model.sources[name].i18n)
|
|
466
|
+
const sortedSources = Object.keys( model.sources )
|
|
467
|
+
.filter( name => !!model.sources[name].i18n )
|
|
459
468
|
.sort( (a, b) => compareLayer( model.sources[a], model.sources[b] ) );
|
|
460
469
|
|
|
461
470
|
if (sortedSources.length === 0)
|
|
@@ -550,7 +559,7 @@ function define( model ) {
|
|
|
550
559
|
return;
|
|
551
560
|
for (const name in src.artifacts) {
|
|
552
561
|
const entry = src.artifacts[name];
|
|
553
|
-
if (!Array.isArray(entry)) // no local name duplicate
|
|
562
|
+
if (!Array.isArray( entry )) // no local name duplicate
|
|
554
563
|
continue;
|
|
555
564
|
for (const decl of entry) {
|
|
556
565
|
if (!decl.$duplicates) { // do not have two duplicate messages
|
|
@@ -574,7 +583,7 @@ function define( model ) {
|
|
|
574
583
|
if (!art.query)
|
|
575
584
|
return;
|
|
576
585
|
art.$queries = [];
|
|
577
|
-
setLink( art, '_from', [] ); // for sequence of resolve steps
|
|
586
|
+
setLink( art, '_from', [] ); // for sequence of resolve steps - TODO: remove
|
|
578
587
|
if (!setLink( art, '_leadingQuery', initQueryExpression( art.query, art ) ) )
|
|
579
588
|
return; // null or undefined in case of parse error
|
|
580
589
|
// if (art._leadingQuery !== art.$queries [0]) throw Error('FOO');
|
|
@@ -596,7 +605,7 @@ function define( model ) {
|
|
|
596
605
|
function initArtifactParentLink( art, definitions ) {
|
|
597
606
|
setLink( art, '_parent', null );
|
|
598
607
|
const { absolute } = art.name;
|
|
599
|
-
const dot = absolute.lastIndexOf('.');
|
|
608
|
+
const dot = absolute.lastIndexOf( '.' );
|
|
600
609
|
if (dot < 0)
|
|
601
610
|
return;
|
|
602
611
|
art.name.id = absolute.substring( dot + 1 ); // XSN TODO: remove name.id for artifacts
|
|
@@ -610,7 +619,7 @@ function define( model ) {
|
|
|
610
619
|
}
|
|
611
620
|
setLink( art, '_parent', parent );
|
|
612
621
|
if (!parent._subArtifacts)
|
|
613
|
-
setLink( parent, '_subArtifacts', Object.create(null) );
|
|
622
|
+
setLink( parent, '_subArtifacts', Object.create( null ) );
|
|
614
623
|
if (art.$duplicates !== true) // no redef or "first def"
|
|
615
624
|
parent._subArtifacts[absolute.substring( dot + 1 )] = art; // not dictAdd()
|
|
616
625
|
}
|
|
@@ -631,7 +640,7 @@ function define( model ) {
|
|
|
631
640
|
setLink( self, '_parent', art );
|
|
632
641
|
setLink( self, '_main', art ); // used on main artifact
|
|
633
642
|
setLink( self, '_origin', art );
|
|
634
|
-
art.$tableAliases = Object.create(null);
|
|
643
|
+
art.$tableAliases = Object.create( null );
|
|
635
644
|
art.$tableAliases[selfname] = self;
|
|
636
645
|
}
|
|
637
646
|
|
|
@@ -695,13 +704,17 @@ function define( model ) {
|
|
|
695
704
|
for (const q of query.args.slice(1))
|
|
696
705
|
initQueryExpression( q, art );
|
|
697
706
|
setLink( query, '_leadingQuery', leading );
|
|
698
|
-
if (leading
|
|
699
|
-
if (
|
|
707
|
+
if (leading) {
|
|
708
|
+
if (query.orderBy) {
|
|
709
|
+
leading.$orderBy ??= [ ];
|
|
700
710
|
leading.$orderBy.push( ...query.orderBy );
|
|
701
|
-
|
|
702
|
-
|
|
711
|
+
}
|
|
712
|
+
if (query.limit) {
|
|
713
|
+
leading.$limit ??= [ ];
|
|
714
|
+
leading.$limit.push( query.limit );
|
|
715
|
+
}
|
|
703
716
|
}
|
|
704
|
-
// ORDER BY to be evaluated in leading query
|
|
717
|
+
// ORDER BY and LIMIT to be evaluated in leading query
|
|
705
718
|
}
|
|
706
719
|
else { // with parse error (`select from <EOF>`, `select from E { *, ( select }`)
|
|
707
720
|
return undefined;
|
|
@@ -711,7 +724,7 @@ function define( model ) {
|
|
|
711
724
|
function initQuery() {
|
|
712
725
|
const main = art._main || art;
|
|
713
726
|
setLink( query, '_$next',
|
|
714
|
-
(art.kind === '$tableAlias' ? art._parent._$next : art) );
|
|
727
|
+
(art.kind === '$tableAlias' ? art._parent._$next : art ) );
|
|
715
728
|
setLink( query, '_block', art._block );
|
|
716
729
|
query.kind = 'select';
|
|
717
730
|
query.name = { location: query.location };
|
|
@@ -735,7 +748,7 @@ function define( model ) {
|
|
|
735
748
|
return;
|
|
736
749
|
if (!table.name) {
|
|
737
750
|
const last = table.path[table.path.length - 1];
|
|
738
|
-
const dot = last?.id?.lastIndexOf('.');
|
|
751
|
+
const dot = last?.id?.lastIndexOf( '.' );
|
|
739
752
|
const id = (dot >= 0) ? last.id.substring( dot + 1 ) : last.id || '';
|
|
740
753
|
// TODO: if we have too much time, we can calculate the real location with '.'
|
|
741
754
|
table.name = { $inferred: 'as', id, location: last.location };
|
|
@@ -788,6 +801,7 @@ function define( model ) {
|
|
|
788
801
|
// (internal) query, should only be relevant for --raw-output, not for
|
|
789
802
|
// user messages or references - TODO: correct if join on left?
|
|
790
803
|
table.name.param = aliases[1] || aliases[0] || '<unknown>';
|
|
804
|
+
setLink( table, '_user', query ); // TODO: do not set kind/name
|
|
791
805
|
setLink( table, '_$next', query._$next );
|
|
792
806
|
// TODO: probably set this to query if we switch to name restriction in JOIN
|
|
793
807
|
}
|
|
@@ -802,7 +816,7 @@ function define( model ) {
|
|
|
802
816
|
if (tableAlias.$inferred === '$internal') {
|
|
803
817
|
const semanticLoc = tableAlias.query?.name ? tableAlias.query : tableAlias;
|
|
804
818
|
error( 'name-missing-alias', [ tableAlias.location, semanticLoc ],
|
|
805
|
-
{ '#': 'duplicate', code: 'as ‹alias›' });
|
|
819
|
+
{ '#': 'duplicate', code: 'as ‹alias›' } );
|
|
806
820
|
}
|
|
807
821
|
else {
|
|
808
822
|
error( 'duplicate-definition', [ loc, table ], { name, '#': 'alias' } );
|
|
@@ -838,7 +852,7 @@ function define( model ) {
|
|
|
838
852
|
|
|
839
853
|
function initExprForQuery( expr, query ) {
|
|
840
854
|
// TODO: use traverseExpr()
|
|
841
|
-
if (Array.isArray(expr)) { // TODO: old-style $parens ?
|
|
855
|
+
if (Array.isArray( expr )) { // TODO: old-style $parens ?
|
|
842
856
|
expr.forEach( e => initExprForQuery( e, query ) );
|
|
843
857
|
}
|
|
844
858
|
else if (!expr) {
|
|
@@ -848,13 +862,13 @@ function define( model ) {
|
|
|
848
862
|
initQueryExpression( expr.query, query );
|
|
849
863
|
}
|
|
850
864
|
else if (expr.args) {
|
|
851
|
-
const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
|
|
865
|
+
const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
|
|
852
866
|
args.forEach( e => initExprForQuery( e, query ) );
|
|
853
867
|
}
|
|
854
868
|
else if (expr.path && expr.$expected === 'exists') {
|
|
855
869
|
// TODO: does really the parser has to set $expected?
|
|
856
870
|
expr.$expected = 'approved-exists';
|
|
857
|
-
approveExistsInChildren(expr);
|
|
871
|
+
approveExistsInChildren( expr );
|
|
858
872
|
}
|
|
859
873
|
}
|
|
860
874
|
|
|
@@ -880,9 +894,9 @@ function define( model ) {
|
|
|
880
894
|
}
|
|
881
895
|
}
|
|
882
896
|
|
|
883
|
-
function initSelectItems( parent, columns, user ) {
|
|
884
|
-
|
|
885
|
-
|
|
897
|
+
function initSelectItems( parent, columns, user, inExtension = false ) {
|
|
898
|
+
let wildcard = !!inExtension; // no `extend … with columns { * }`
|
|
899
|
+
// TODO: forbid expand/inline in ref-where, outside queries (CSN), ...
|
|
886
900
|
let hasItems = false;
|
|
887
901
|
for (const col of columns || parent.expand || parent.inline || []) {
|
|
888
902
|
if (!col) // parse error
|
|
@@ -898,6 +912,11 @@ function define( model ) {
|
|
|
898
912
|
if (!wildcard) {
|
|
899
913
|
wildcard = col;
|
|
900
914
|
}
|
|
915
|
+
else if (wildcard === true) { // in `extend … with columns {…}`
|
|
916
|
+
error( 'ext-unexpected-wildcard', [ col.location, parent ], { code: '*' },
|
|
917
|
+
'Unexpected $(CODE) (wildcard) in an extension' );
|
|
918
|
+
col.val = null; // do not consider it for expandWildcard()
|
|
919
|
+
}
|
|
901
920
|
else {
|
|
902
921
|
// a late syntax error (this code also runs with parse-cdl), i.e.
|
|
903
922
|
// no semantic loc (wouldn't be available for expand/inline anyway)
|
|
@@ -917,7 +936,8 @@ function define( model ) {
|
|
|
917
936
|
}
|
|
918
937
|
// Either expression (value), expand or new association (target && type)
|
|
919
938
|
else if (col.value || col.expand || (col.target && col.type)) {
|
|
920
|
-
|
|
939
|
+
if (!col._block)
|
|
940
|
+
setLink( col, '_block', parent._block );
|
|
921
941
|
if (col.inline) { // `@anno elem.{ * }` does not work
|
|
922
942
|
if (col.doc) {
|
|
923
943
|
warning( 'syntax-ignoring-anno', [ col.doc.location, col ],
|
|
@@ -925,30 +945,25 @@ function define( model ) {
|
|
|
925
945
|
}
|
|
926
946
|
// col.$annotations no available for CSN input, have to search.
|
|
927
947
|
// Warning about first annotation should be enough to avoid spam.
|
|
928
|
-
const firstAnno = Object.keys(col).find(key => key.startsWith('@'));
|
|
948
|
+
const firstAnno = Object.keys( col ).find( key => key.startsWith( '@' ) );
|
|
929
949
|
if (firstAnno) {
|
|
930
950
|
warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ],
|
|
931
951
|
{ code: '.{ ‹inline› }' } );
|
|
932
952
|
}
|
|
933
953
|
}
|
|
934
954
|
// TODO: allow sub queries? at least in top-level expand without parallel ref
|
|
935
|
-
if (columns)
|
|
955
|
+
if (columns && !inExtension) // not (yet) in `extend … with columns {…}`
|
|
936
956
|
initExprForQuery( col.value, parent );
|
|
937
|
-
initSelectItems( col, null, user );
|
|
957
|
+
initSelectItems( col, null, user ); // TODO: use col as user (i.e. remove param)
|
|
938
958
|
}
|
|
939
959
|
}
|
|
940
960
|
|
|
941
|
-
if (hasItems && !wildcard && parent.excludingDict) {
|
|
942
|
-
// TODO:
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
if (block.$frontend === 'cdl') {
|
|
948
|
-
warning('query-ignoring-exclude', [ parent.excludingDict[$location], user ],
|
|
949
|
-
{ prop: '*' },
|
|
950
|
-
'Excluding elements without wildcard $(PROP) has no effect');
|
|
951
|
-
}
|
|
961
|
+
if (hasItems && !wildcard && parent.excludingDict && !options.$recompile) {
|
|
962
|
+
// TODO: the SQL backend should probably delete `excluding` when expanding `*`
|
|
963
|
+
// TODO: use `parent` for semantic location, use `-excluding`
|
|
964
|
+
warning( 'query-ignoring-exclude', [ parent.excludingDict[$location], user ],
|
|
965
|
+
{ prop: '*' },
|
|
966
|
+
'Excluding elements without wildcard $(PROP) has no effect' );
|
|
952
967
|
}
|
|
953
968
|
}
|
|
954
969
|
|
|
@@ -975,11 +990,11 @@ function define( model ) {
|
|
|
975
990
|
exprOrPathElement.$expected = 'approved-exists';
|
|
976
991
|
// Drill down
|
|
977
992
|
if (exprOrPathElement.args)
|
|
978
|
-
exprOrPathElement.args.forEach(elem => approveExistsInChildren(elem));
|
|
993
|
+
exprOrPathElement.args.forEach( elem => approveExistsInChildren( elem ) );
|
|
979
994
|
else if (exprOrPathElement.where && exprOrPathElement.where.args)
|
|
980
|
-
exprOrPathElement.where.args.forEach(elem => approveExistsInChildren(elem));
|
|
995
|
+
exprOrPathElement.where.args.forEach( elem => approveExistsInChildren( elem ) );
|
|
981
996
|
else if (exprOrPathElement.path)
|
|
982
|
-
exprOrPathElement.path.forEach(elem => approveExistsInChildren(elem));
|
|
997
|
+
exprOrPathElement.path.forEach( elem => approveExistsInChildren( elem ) );
|
|
983
998
|
}
|
|
984
999
|
// TODO: we might issue 'expr-unexpected-exists' and 'expr-no-subquery' already in
|
|
985
1000
|
// define.js (using a to-be-written expression traversal function in utils.js)
|
|
@@ -1016,13 +1031,13 @@ function define( model ) {
|
|
|
1016
1031
|
const { targetAspect } = obj;
|
|
1017
1032
|
if (targetAspect) {
|
|
1018
1033
|
if (obj.foreignKeys) {
|
|
1019
|
-
error( 'unexpected-keys
|
|
1034
|
+
error( 'type-unexpected-foreign-keys', [ obj.foreignKeys[$location], construct ],
|
|
1020
1035
|
{},
|
|
1021
1036
|
'A managed aspect composition can\'t have a foreign keys specification' );
|
|
1022
1037
|
delete obj.foreignKeys; // continuation semantics: not specified
|
|
1023
1038
|
}
|
|
1024
1039
|
if (obj.on && !obj.target) {
|
|
1025
|
-
error( 'unexpected-on-
|
|
1040
|
+
error( 'type-unexpected-on-condition', [ obj.on.location, construct ],
|
|
1026
1041
|
{},
|
|
1027
1042
|
'A managed aspect composition can\'t have a specified ON-condition' );
|
|
1028
1043
|
delete obj.on; // continuation semantics: not specified
|
|
@@ -1056,30 +1071,33 @@ function define( model ) {
|
|
|
1056
1071
|
|
|
1057
1072
|
function initElementsAsEnum() {
|
|
1058
1073
|
// in extensions, extended enums are represented as elements
|
|
1059
|
-
let
|
|
1074
|
+
let hasElement = false;
|
|
1060
1075
|
for (const n in obj.elements) {
|
|
1061
1076
|
const e = obj.elements[n];
|
|
1062
|
-
if (e.kind === '
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1077
|
+
if (e.kind === 'extend')
|
|
1078
|
+
continue;
|
|
1079
|
+
const noVal = e.value?.val === undefined && e.value?.sym === undefined;
|
|
1080
|
+
// TODO: forbid #symbol as enum value
|
|
1081
|
+
if (e.$syntax === 'element' || // `extend … with elements` or `extend with { element … }`
|
|
1082
|
+
noVal && e.$syntax !== 'enum' || // no value in CDL input
|
|
1083
|
+
e.virtual || e.key || e.masked || e.type || e.elements || e.items || e.stored) {
|
|
1084
|
+
// We do not want to complain separately about all element properties:
|
|
1085
|
+
error( 'ext-unexpected-element', [ e.location, construct ],
|
|
1086
|
+
{ name: e.name.id, code: 'extend … with enum' },
|
|
1087
|
+
// eslint-disable-next-line max-len
|
|
1088
|
+
'Unexpected elements like $(NAME) in an extension for an enum. Additionally, use $(CODE) when extending enums' );
|
|
1089
|
+
// Don't emit 'ext-expecting-enum' if this error is emitted.
|
|
1090
|
+
return;
|
|
1076
1091
|
}
|
|
1092
|
+
e.kind = 'enum';
|
|
1093
|
+
if (noVal || e.$syntax !== 'enum')
|
|
1094
|
+
hasElement = true; // warning with CDL input or `name: {}` in CSN input
|
|
1077
1095
|
}
|
|
1078
|
-
if (
|
|
1079
|
-
//
|
|
1080
|
-
//
|
|
1081
|
-
// and
|
|
1082
|
-
warning( 'ext-expecting-enum', [
|
|
1096
|
+
if (hasElement) {
|
|
1097
|
+
// This message is similar to the one above. In v5/6, we could probably
|
|
1098
|
+
// turn this warning into an error, remove `$syntax: 'element' (also in
|
|
1099
|
+
// language.g4), and use the above `ext-unexpected-element` only for CSN input.
|
|
1100
|
+
warning( 'ext-expecting-enum', [ obj.elements[$location], construct ],
|
|
1083
1101
|
{ code: 'extend … with enum' }, 'Use $(CODE) when extending enums' );
|
|
1084
1102
|
}
|
|
1085
1103
|
forEachGeneric( { enum: obj.elements }, 'enum', init );
|
|
@@ -1208,18 +1226,25 @@ function define( model ) {
|
|
|
1208
1226
|
// - artifacts (CDL-only anyway) only inside [extend] context|service
|
|
1209
1227
|
if (!dict)
|
|
1210
1228
|
return false;
|
|
1211
|
-
const names = Object.keys( dict );
|
|
1212
|
-
if (!names.length) // TODO: re-check - really allow empty dict if no other?
|
|
1213
|
-
return false;
|
|
1214
1229
|
const feature = kindProperties[parent.kind][prop];
|
|
1215
1230
|
if (feature &&
|
|
1216
1231
|
(feature === true || construct.kind !== 'extend' || feature( prop, parent )))
|
|
1217
1232
|
return true;
|
|
1218
1233
|
const location = dict[$location];
|
|
1234
|
+
|
|
1235
|
+
// TODO: a bit inconsistent = not a simple switch on `prop`…
|
|
1219
1236
|
if (prop === 'actions') {
|
|
1220
|
-
|
|
1221
|
-
|
|
1237
|
+
if (Object.keys( dict ).length) {
|
|
1238
|
+
error( 'def-unexpected-actions', [ location, construct ], {}, // TODO: ext-
|
|
1239
|
+
'Actions and functions only exist top-level and for entities' ); // or aspects
|
|
1240
|
+
}
|
|
1241
|
+
else {
|
|
1242
|
+
warning( 'ext-ignoring-actions', [ location, construct ], {},
|
|
1243
|
+
'Actions and functions only exist top-level and for entities' );
|
|
1244
|
+
return false;
|
|
1245
|
+
}
|
|
1222
1246
|
}
|
|
1247
|
+
//
|
|
1223
1248
|
else if (parent.kind === 'action' || parent.kind === 'function') {
|
|
1224
1249
|
error( 'ext-unexpected-action', [ construct.location, construct ], { '#': parent.kind }, {
|
|
1225
1250
|
std: 'Actions and functions can\'t be extended, only annotated',
|
|
@@ -1231,7 +1256,7 @@ function define( model ) {
|
|
|
1231
1256
|
if (!feature) {
|
|
1232
1257
|
// Note: This error can't be triggered at the moment. But as we likely want to
|
|
1233
1258
|
// allow extensions with params in the future, we keep the code.
|
|
1234
|
-
error( 'unexpected-params', [ location, construct ], {},
|
|
1259
|
+
error( 'def-unexpected-params', [ location, construct ], {},
|
|
1235
1260
|
'Parameters only exist for entities, actions or functions' );
|
|
1236
1261
|
}
|
|
1237
1262
|
else {
|
|
@@ -1241,10 +1266,15 @@ function define( model ) {
|
|
|
1241
1266
|
}
|
|
1242
1267
|
}
|
|
1243
1268
|
else if (feature) { // allowed in principle, but not with extend
|
|
1244
|
-
if (
|
|
1269
|
+
if (!Object.keys( dict ).length) {
|
|
1270
|
+
warning( 'ext-ignoring-elements', [ location, construct ], {},
|
|
1271
|
+
'Only structures with directly specified elements can be extended by elements' );
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1274
|
+
else if (parent.$inferred === 'include') { // special case for better error message
|
|
1245
1275
|
const variant = (construct.enum || construct.elements) ? 'elements' : 'std';
|
|
1246
1276
|
error( 'ref-expected-direct-structure', [ location, construct ],
|
|
1247
|
-
{ '#': variant, art: parent });
|
|
1277
|
+
{ '#': variant, art: parent } );
|
|
1248
1278
|
}
|
|
1249
1279
|
else {
|
|
1250
1280
|
error( 'extend-type', [ location, construct ], {},
|
|
@@ -1252,7 +1282,7 @@ function define( model ) {
|
|
|
1252
1282
|
}
|
|
1253
1283
|
}
|
|
1254
1284
|
else if (prop === 'elements') {
|
|
1255
|
-
error( 'unexpected-elements', [ location, construct ], {},
|
|
1285
|
+
error( 'def-unexpected-elements', [ location, construct ], {},
|
|
1256
1286
|
'Elements only exist in entities, types or typed constructs' );
|
|
1257
1287
|
}
|
|
1258
1288
|
else if (prop === 'columns') {
|
|
@@ -1265,19 +1295,23 @@ function define( model ) {
|
|
|
1265
1295
|
return construct === parent;
|
|
1266
1296
|
}
|
|
1267
1297
|
|
|
1268
|
-
|
|
1269
|
-
|
|
1298
|
+
/**
|
|
1299
|
+
* Return whether the `target` is actually a `targetAspect`
|
|
1300
|
+
* TODO: really do that here and not in kick-start.js?
|
|
1301
|
+
*/
|
|
1270
1302
|
function targetIsTargetAspect( elem ) {
|
|
1271
1303
|
const { target } = elem;
|
|
1272
1304
|
if (target.elements) {
|
|
1273
|
-
// TODO: error if CSN has both target.elements and targetAspect.elements
|
|
1305
|
+
// TODO: error if CSN has both target.elements and targetAspect.elements
|
|
1306
|
+
// -> delete target
|
|
1274
1307
|
return true;
|
|
1275
1308
|
}
|
|
1276
1309
|
if (elem.targetAspect || options.parseCdl || !isDirectComposition( elem ))
|
|
1277
1310
|
return false;
|
|
1278
1311
|
const name = resolveUncheckedPath( target, 'target', elem );
|
|
1279
1312
|
const aspect = name && model.definitions[name];
|
|
1280
|
-
return
|
|
1313
|
+
return (aspect?.kind === 'aspect' || aspect?.kind === 'type') && // type is sloppy
|
|
1314
|
+
aspect.elements && !aspect.elements[$inferred];
|
|
1281
1315
|
}
|
|
1282
1316
|
}
|
|
1283
1317
|
|