@sap/cds-compiler 6.4.2 → 6.5.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 +87 -1159
- package/README.md +1 -10
- package/doc/IncompatibleChanges_v5.md +436 -0
- package/doc/IncompatibleChanges_v6.md +659 -0
- package/doc/Versioning.md +3 -7
- package/lib/api/main.js +1 -0
- package/lib/api/options.js +5 -0
- package/lib/api/validate.js +3 -0
- package/lib/base/message-registry.js +25 -2
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +3 -2
- package/lib/checks/actionsFunctions.js +6 -3
- package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
- package/lib/checks/existsInForbiddenPlaces.js +32 -0
- package/lib/checks/existsMustEndInAssoc.js +1 -1
- package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
- package/lib/checks/validator.js +6 -2
- package/lib/compiler/assert-consistency.js +5 -7
- package/lib/compiler/builtins.js +5 -6
- package/lib/compiler/checks.js +4 -8
- package/lib/compiler/define.js +244 -459
- package/lib/compiler/extend.js +297 -11
- package/lib/compiler/finalize-parse-cdl.js +2 -10
- package/lib/compiler/generate.js +29 -1
- package/lib/compiler/populate.js +21 -63
- package/lib/compiler/propagator.js +1 -2
- package/lib/compiler/resolve.js +2 -12
- package/lib/compiler/shared.js +145 -114
- package/lib/compiler/tweak-assocs.js +14 -10
- package/lib/compiler/utils.js +97 -0
- package/lib/compiler/xpr-rewrite.js +113 -140
- package/lib/edm/annotations/edmJson.js +9 -6
- package/lib/edm/annotations/genericTranslation.js +8 -4
- package/lib/edm/csn2edm.js +3 -4
- package/lib/edm/edmInboundChecks.js +1 -2
- package/lib/edm/edmPreprocessor.js +3 -3
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +4 -3
- package/lib/gen/Dictionary.json +16 -1
- package/lib/json/from-csn.js +4 -6
- package/lib/json/to-csn.js +3 -3
- package/lib/model/csnRefs.js +13 -4
- package/lib/model/enrichCsn.js +4 -2
- package/lib/optionProcessor.js +8 -4
- package/lib/parsers/AstBuildingParser.js +1 -1
- package/lib/render/utils/sql.js +3 -2
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +3 -3
- package/lib/transform/db/assocsToQueries/normalizeFrom.js +33 -0
- package/lib/transform/db/assocsToQueries/transformExists.js +17 -13
- package/lib/transform/db/assocsToQueries/utils.js +1 -6
- package/lib/transform/db/backlinks.js +4 -4
- package/lib/transform/db/cdsPersistence.js +4 -4
- package/lib/transform/db/constraints.js +4 -4
- package/lib/transform/db/expansion.js +5 -5
- package/lib/transform/db/flattening.js +4 -5
- package/lib/transform/db/rewriteCalculatedElements.js +3 -3
- package/lib/transform/db/temporal.js +11 -11
- package/lib/transform/draft/db.js +2 -0
- package/lib/transform/draft/odata.js +5 -7
- package/lib/transform/effective/flattening.js +1 -2
- package/lib/transform/forOdata.js +3 -3
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/localized.js +13 -20
- package/lib/transform/odata/createForeignKeys.js +1 -2
- package/lib/transform/odata/flattening.js +1 -2
- package/lib/transform/odata/toFinalBaseType.js +52 -55
- package/lib/transform/transformUtils.js +3 -4
- package/package.json +3 -3
- package/doc/CHANGELOG_BETA.md +0 -464
- package/doc/CHANGELOG_DEPRECATED.md +0 -235
package/lib/compiler/define.js
CHANGED
|
@@ -87,7 +87,8 @@
|
|
|
87
87
|
// --------- TODO: end in extra markdown document -------------------------------
|
|
88
88
|
|
|
89
89
|
// Sub phase 1 (addXYZ) - only for main artifacts
|
|
90
|
-
// - set _block links for main definitions, vocabulary and
|
|
90
|
+
// - set _block links for main definitions, vocabulary, extensions and
|
|
91
|
+
// annotations on those
|
|
91
92
|
// - store definitions (including context extensions), NO duplicate check
|
|
92
93
|
// - artifact name check
|
|
93
94
|
// - Note: the only allow name resolving is resolveUncheckedPath(),
|
|
@@ -132,8 +133,13 @@ const {
|
|
|
132
133
|
const { kindProperties, dictKinds } = require('./base');
|
|
133
134
|
const {
|
|
134
135
|
setLink,
|
|
136
|
+
initItemsLinks,
|
|
135
137
|
setMemberParent,
|
|
136
138
|
createAndLinkCalcDepElement,
|
|
139
|
+
initExprAnnoBlock,
|
|
140
|
+
initDollarSelf,
|
|
141
|
+
initDollarParameters,
|
|
142
|
+
initBoundSelfParam,
|
|
137
143
|
storeExtension,
|
|
138
144
|
dependsOnSilent,
|
|
139
145
|
pathName,
|
|
@@ -173,12 +179,11 @@ function define( model ) {
|
|
|
173
179
|
shuffleDict,
|
|
174
180
|
shuffleArray,
|
|
175
181
|
initArtifact,
|
|
176
|
-
initMembers,
|
|
182
|
+
initMembers, // for finalize-parser-cdl.js
|
|
183
|
+
targetIsTargetAspect,
|
|
184
|
+
checkRedefinition,
|
|
177
185
|
initSelectItems,
|
|
178
186
|
} );
|
|
179
|
-
|
|
180
|
-
let boundSelfParamType = true; // special `$self` for binding param must still be initialised
|
|
181
|
-
|
|
182
187
|
return doDefine();
|
|
183
188
|
|
|
184
189
|
/**
|
|
@@ -204,16 +209,18 @@ function define( model ) {
|
|
|
204
209
|
const sourceNames = shuffleArray( Object.keys( model.sources ) );
|
|
205
210
|
for (const name of sourceNames)
|
|
206
211
|
addSource( model.sources[name] );
|
|
212
|
+
|
|
213
|
+
// Phase 2:
|
|
207
214
|
for (const name of sourceNames)
|
|
208
215
|
initNamespaceAndUsing( model.sources[name] );
|
|
209
216
|
dictForEach( model.definitions, initArtifact );
|
|
210
217
|
dictForEach( model.vocabularies, initVocabulary );
|
|
211
218
|
dictForEach( model.$collectedExtensions, e => e._extensions.forEach( initExtension ) );
|
|
212
219
|
|
|
213
|
-
addI18nBlocks();
|
|
220
|
+
addI18nBlocks(); // TODO: part of extend.js?
|
|
214
221
|
|
|
215
222
|
const { $self } = model.definitions;
|
|
216
|
-
if ($self) {
|
|
223
|
+
if ($self && $self.kind !== 'namespace') { // TODO v7: non-config error
|
|
217
224
|
message( 'name-deprecated-$self', [ $self.name.location, $self ], { name: '$self' },
|
|
218
225
|
'Do not use $(NAME) as name for an artifact definition' );
|
|
219
226
|
}
|
|
@@ -234,7 +241,7 @@ function define( model ) {
|
|
|
234
241
|
|
|
235
242
|
let namespace = src.namespace?.name;
|
|
236
243
|
let prefix = '';
|
|
237
|
-
if (namespace?.path
|
|
244
|
+
if (namespace?.path) {
|
|
238
245
|
namespace.id = pathName( namespace.path );
|
|
239
246
|
prefix = `${ namespace.id }.`;
|
|
240
247
|
}
|
|
@@ -242,7 +249,7 @@ function define( model ) {
|
|
|
242
249
|
error( 'reserved-namespace-cds', [ src.namespace.name.location, src.namespace.name ],
|
|
243
250
|
{ name: 'cds' },
|
|
244
251
|
'The namespace $(NAME) is reserved for CDS builtins' );
|
|
245
|
-
namespace = null;
|
|
252
|
+
namespace = null; // TODO: reconsider
|
|
246
253
|
}
|
|
247
254
|
if (src.$frontend !== 'json') { // CDL input
|
|
248
255
|
// TODO: set _block to builtin
|
|
@@ -278,10 +285,12 @@ function define( model ) {
|
|
|
278
285
|
}
|
|
279
286
|
|
|
280
287
|
function addDefinition( art, block, prefix ) {
|
|
288
|
+
setLink( art, '_block', block );
|
|
289
|
+
initExprAnnoBlock( art, block );
|
|
281
290
|
art.name.id ??= prefix + pathName( art.name.path );
|
|
282
291
|
const absolute = art.name.id;
|
|
283
292
|
// TODO: check reserved, see checkName()/checkLocalizedObjects() of checks.js
|
|
284
|
-
if (
|
|
293
|
+
if (isInReservedNamespace( absolute )) {
|
|
285
294
|
error( 'reserved-namespace-cds', [ art.name.location, art ], { name: 'cds' },
|
|
286
295
|
'The namespace $(NAME) is reserved for CDS builtins' );
|
|
287
296
|
const builtin = model.definitions[absolute];
|
|
@@ -297,8 +306,6 @@ function define( model ) {
|
|
|
297
306
|
}
|
|
298
307
|
return;
|
|
299
308
|
}
|
|
300
|
-
setLink( art, '_block', block );
|
|
301
|
-
initExprAnnoBlock( art, block );
|
|
302
309
|
// dictAdd might set $duplicates
|
|
303
310
|
dictAdd( model.definitions, absolute, art );
|
|
304
311
|
}
|
|
@@ -345,7 +352,7 @@ function define( model ) {
|
|
|
345
352
|
return;
|
|
346
353
|
}
|
|
347
354
|
const path = decl.extern?.path;
|
|
348
|
-
if (!path ||
|
|
355
|
+
if (!path || !path[0]) // syntax error
|
|
349
356
|
return;
|
|
350
357
|
decl.extern.id = pathName( path );
|
|
351
358
|
if (!decl.name)
|
|
@@ -354,7 +361,7 @@ function define( model ) {
|
|
|
354
361
|
// TODO: check name: no "."
|
|
355
362
|
const found = src.artifacts[name];
|
|
356
363
|
// a real `using` declaration is “nicer” than a compiler-generated one:
|
|
357
|
-
if (found
|
|
364
|
+
if (found?.$inferred === 'path-prefix' && found.extern.id === decl.extern.id)
|
|
358
365
|
src.artifacts[name] = decl;
|
|
359
366
|
else
|
|
360
367
|
dictAddArray( src.artifacts, name, decl );
|
|
@@ -418,57 +425,10 @@ function define( model ) {
|
|
|
418
425
|
// Set block number for debugging (--raw-output):
|
|
419
426
|
// eslint-disable-next-line no-multi-assign
|
|
420
427
|
ext.$effectiveSeqNo = model.$blocks[absolute] = (model.$blocks[absolute] || 0) + 1;
|
|
421
|
-
// add "namespace" for the case that ext.artifacts is empty (TODO: later)
|
|
422
|
-
// now add all definitions in ext.artifacts:
|
|
423
428
|
const prefix = `${ absolute }.`;
|
|
424
429
|
dictForEach( ext.artifacts, a => addArtifact( a, ext, prefix ) );
|
|
425
430
|
}
|
|
426
431
|
|
|
427
|
-
function initExtension( parent ) {
|
|
428
|
-
forEachMember( parent, function initExtensionMember( sub, name, prop ) {
|
|
429
|
-
if (sub.kind !== 'extend' && sub.kind !== 'annotate')
|
|
430
|
-
return; // for defs inside, set somewhere else - TODO: rethink
|
|
431
|
-
if (prop === 'params' && name === '') // RETURNS
|
|
432
|
-
sub.name = { id: '', location: sub.location };
|
|
433
|
-
setLink( sub, '_block', parent._block );
|
|
434
|
-
initExprAnnoBlock( sub, parent._block );
|
|
435
|
-
setLink( sub, '_parent', parent );
|
|
436
|
-
setLink( sub, '_main', parent._main || parent );
|
|
437
|
-
initExtension( sub );
|
|
438
|
-
} );
|
|
439
|
-
if (parent.kind !== 'extend')
|
|
440
|
-
return;
|
|
441
|
-
// TODO: sub queries? expand/inline?
|
|
442
|
-
parent.columns?.forEach( c => setLink( c, '_block', parent._block ) );
|
|
443
|
-
if (parent.scale && !parent.precision) {
|
|
444
|
-
// TODO: where could we store the location of the name?
|
|
445
|
-
error( 'syntax-missing-type-property', [ parent.scale.location ],
|
|
446
|
-
{ prop: 'scale', otherprop: 'precision' },
|
|
447
|
-
'Type extension with property $(PROP) must also have property $(OTHERPROP)' );
|
|
448
|
-
parent.scale = undefined; // no consequential error
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function initExprAnnoBlock( art, block ) {
|
|
453
|
-
// remark: `art` could also be the extension
|
|
454
|
-
for (const prop in art) {
|
|
455
|
-
if (prop.charAt(0) !== '@')
|
|
456
|
-
continue;
|
|
457
|
-
const anno = art[prop];
|
|
458
|
-
// _block links needed for `cast( … as Type )`, `[ ..., cast( … as Type ) ]`
|
|
459
|
-
if (anno.literal === 'array') {
|
|
460
|
-
anno.kind = '$annotation';
|
|
461
|
-
for (const item of anno.val)
|
|
462
|
-
setLink( item, '_block', block );
|
|
463
|
-
}
|
|
464
|
-
else if (anno.$tokenTexts) {
|
|
465
|
-
// remark: it wouldn't hurt to set it always...
|
|
466
|
-
anno.kind = '$annotation';
|
|
467
|
-
setLink( anno, '_block', block );
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
432
|
function addVocabulary( vocab, block, prefix ) {
|
|
473
433
|
setLink( vocab, '_block', block );
|
|
474
434
|
const { name } = vocab;
|
|
@@ -476,6 +436,8 @@ function define( model ) {
|
|
|
476
436
|
dictAdd( model.vocabularies, name.id, vocab );
|
|
477
437
|
}
|
|
478
438
|
|
|
439
|
+
// I18n code (eventually part of extend.js) -----------------------------------
|
|
440
|
+
|
|
479
441
|
/**
|
|
480
442
|
* Add (optional) translations into the XSN model.
|
|
481
443
|
*/
|
|
@@ -525,29 +487,6 @@ function define( model ) {
|
|
|
525
487
|
// Functions called from top-level: initNamespaceAndUsing(), initArtifact(),
|
|
526
488
|
// initVocabulary(), initExtension()
|
|
527
489
|
|
|
528
|
-
// TODO: message ids
|
|
529
|
-
function checkRedefinition( art ) {
|
|
530
|
-
if (!art.$duplicates || !art.name.id ||
|
|
531
|
-
art.$errorReported === 'syntax-duplicate-extend')
|
|
532
|
-
return;
|
|
533
|
-
if (art.kind === 'annotate' || art.kind === 'extend')
|
|
534
|
-
return; // extensions are merged into a super-annotate; $duplicates are only kept for LSP
|
|
535
|
-
if (art._main) {
|
|
536
|
-
error( 'duplicate-definition', [ art.name.location, art ], {
|
|
537
|
-
name: art.name.id,
|
|
538
|
-
'#': kindProperties[art.kind].normalized || art.kind,
|
|
539
|
-
} );
|
|
540
|
-
}
|
|
541
|
-
else if (!art.builtin) {
|
|
542
|
-
// TODO: better messages with definitions with the same name as builtin,
|
|
543
|
-
// especially if there is just one
|
|
544
|
-
error( 'duplicate-definition', [ art.name.location, art ], {
|
|
545
|
-
name: art.name.id,
|
|
546
|
-
'#': (art.kind === 'annotation' ? 'annotation' : 'absolute' ),
|
|
547
|
-
} );
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
490
|
function initNamespaceAndUsing( src ) {
|
|
552
491
|
if (src.$frontend && src.$frontend !== 'cdl')
|
|
553
492
|
return;
|
|
@@ -587,12 +526,11 @@ function define( model ) {
|
|
|
587
526
|
}
|
|
588
527
|
|
|
589
528
|
function initArtifact( art, reInit = false ) {
|
|
590
|
-
if (!reInit)
|
|
529
|
+
if (!reInit) // not for auto-exposed entity
|
|
591
530
|
initArtifactParentLink( art, model.definitions );
|
|
592
|
-
const block = art._block;
|
|
593
531
|
checkRedefinition( art );
|
|
594
532
|
initDollarSelf( art ); // $self
|
|
595
|
-
initMembers( art
|
|
533
|
+
initMembers( art );
|
|
596
534
|
if (art.params)
|
|
597
535
|
initDollarParameters( art ); // $parameters
|
|
598
536
|
if (art.query) {
|
|
@@ -601,9 +539,105 @@ function define( model ) {
|
|
|
601
539
|
}
|
|
602
540
|
}
|
|
603
541
|
|
|
542
|
+
function initVocabulary( art ) {
|
|
543
|
+
initArtifactParentLink( art, model.vocabularies );
|
|
544
|
+
checkRedefinition( art );
|
|
545
|
+
initMembers( art );
|
|
546
|
+
|
|
547
|
+
if (art.query) {
|
|
548
|
+
initArtifactQuery( art );
|
|
549
|
+
error( 'def-unsupported-projection', [ art.location, art ], null,
|
|
550
|
+
'Projections for annotation definitions are not supported' );
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function initExtension( parent ) {
|
|
555
|
+
// TODO: re-think
|
|
556
|
+
forEachMember( parent, function initExtensionMember( sub, name, prop ) {
|
|
557
|
+
if (sub.kind !== 'extend' && sub.kind !== 'annotate')
|
|
558
|
+
return; // for defs inside, set somewhere else - TODO: rethink
|
|
559
|
+
if (prop === 'params' && name === '') // RETURNS
|
|
560
|
+
sub.name = { id: '', location: sub.location };
|
|
561
|
+
setLink( sub, '_block', parent._block );
|
|
562
|
+
initExprAnnoBlock( sub, parent._block );
|
|
563
|
+
setLink( sub, '_parent', parent );
|
|
564
|
+
setLink( sub, '_main', parent._main || parent );
|
|
565
|
+
initExtension( sub );
|
|
566
|
+
} );
|
|
567
|
+
if (parent.kind !== 'extend')
|
|
568
|
+
return;
|
|
569
|
+
// TODO: sub queries? expand/inline?
|
|
570
|
+
parent.columns?.forEach( c => setLink( c, '_block', parent._block ) );
|
|
571
|
+
if (parent.scale && !parent.precision) {
|
|
572
|
+
// TODO: where could we store the location of the name?
|
|
573
|
+
error( 'syntax-missing-type-property', [ parent.scale.location ],
|
|
574
|
+
{ prop: 'scale', otherprop: 'precision' },
|
|
575
|
+
'Type extension with property $(PROP) must also have property $(OTHERPROP)' );
|
|
576
|
+
parent.scale = undefined; // no consequential error
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Make the `_parent` pointer of artifact `A.B.C` point to `A.B`. If no `A.B`
|
|
582
|
+
* exists in XSN.definitions, create an artifact with kind `namespace` (better
|
|
583
|
+
* would be `$gap`). Add `A.B.C` to the `_subArtifacts` dictionary of `A.B`.
|
|
584
|
+
* Do recursively if necessary, `_parent` of `A.B` is `A` in this example.
|
|
585
|
+
*/
|
|
586
|
+
function initArtifactParentLink( art, definitions, path, pathIndex ) {
|
|
587
|
+
setLink( art, '_parent', null );
|
|
588
|
+
const { id } = art.name;
|
|
589
|
+
const dot = id.lastIndexOf( '.' );
|
|
590
|
+
if (dot < 0)
|
|
591
|
+
return;
|
|
592
|
+
const prefix = id.substring( 0, dot );
|
|
593
|
+
let parent = definitions[prefix];
|
|
594
|
+
if (!parent) {
|
|
595
|
+
path ??= art.name.path;
|
|
596
|
+
pathIndex ??= path?.length - 1;
|
|
597
|
+
const pathItemOrName = (path && pathIndex) ? path[--pathIndex] : art.name;
|
|
598
|
+
const location = weakLocation( pathItemOrName.location );
|
|
599
|
+
parent = { kind: 'namespace', name: { id: prefix, location }, location };
|
|
600
|
+
definitions[prefix] = parent;
|
|
601
|
+
initArtifactParentLink( parent, definitions, path, pathIndex );
|
|
602
|
+
}
|
|
603
|
+
setLink( art, '_parent', parent );
|
|
604
|
+
if (!parent._subArtifacts)
|
|
605
|
+
setLink( parent, '_subArtifacts', Object.create( null ) );
|
|
606
|
+
if (art.$duplicates !== true) // no redef or "first def"
|
|
607
|
+
parent._subArtifacts[id.substring( dot + 1 )] = art; // not dictAdd()
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// TODO: message ids
|
|
611
|
+
// Called by initArtifact() and initVocabulary() and for members
|
|
612
|
+
function checkRedefinition( art ) {
|
|
613
|
+
if (art.kind === 'annotate' || art.kind === 'extend') // move this check to call in extend.js?
|
|
614
|
+
return; // extensions are merged into a super-annotate; $duplicates are only kept for LSP
|
|
615
|
+
if (!art.$duplicates || !art.name.id ||
|
|
616
|
+
art.$errorReported === 'syntax-duplicate-extend')
|
|
617
|
+
return;
|
|
618
|
+
if (art._main) {
|
|
619
|
+
error( 'duplicate-definition', [ art.name.location, art ], {
|
|
620
|
+
name: art.name.id,
|
|
621
|
+
'#': kindProperties[art.kind].normalized || art.kind,
|
|
622
|
+
} );
|
|
623
|
+
}
|
|
624
|
+
else if (!art.builtin) {
|
|
625
|
+
// TODO: better messages with definitions with the same name as builtin,
|
|
626
|
+
// especially if there is just one
|
|
627
|
+
error( 'duplicate-definition', [ art.name.location, art ], {
|
|
628
|
+
name: art.name.id,
|
|
629
|
+
'#': (art.kind === 'annotation' ? 'annotation' : 'absolute' ),
|
|
630
|
+
} );
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Phase 2, init queries ------------------------------------------------------
|
|
635
|
+
|
|
604
636
|
/**
|
|
605
637
|
* Restrict the query of `art` to only simple projections, i.e. those without 'group by', etc.
|
|
606
638
|
*
|
|
639
|
+
* TODO v8 and for `type`: do in parsers
|
|
640
|
+
*
|
|
607
641
|
* @param {XSN.Artifact} art
|
|
608
642
|
*/
|
|
609
643
|
function restrictToSimpleProjection( art ) {
|
|
@@ -659,88 +693,6 @@ function define( model ) {
|
|
|
659
693
|
}
|
|
660
694
|
}
|
|
661
695
|
|
|
662
|
-
function initVocabulary( art ) {
|
|
663
|
-
initArtifactParentLink( art, model.vocabularies );
|
|
664
|
-
checkRedefinition( art );
|
|
665
|
-
const block = art._block;
|
|
666
|
-
initMembers( art, art, block );
|
|
667
|
-
|
|
668
|
-
if (art.query) {
|
|
669
|
-
initArtifactQuery( art );
|
|
670
|
-
error( 'def-unsupported-projection', [ art.location, art ], null,
|
|
671
|
-
'Projections for annotation definitions are not supported' );
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
function initArtifactParentLink( art, definitions, path, pathIndex ) {
|
|
676
|
-
setLink( art, '_parent', null );
|
|
677
|
-
const { id } = art.name;
|
|
678
|
-
const dot = id.lastIndexOf( '.' );
|
|
679
|
-
if (dot < 0)
|
|
680
|
-
return;
|
|
681
|
-
const prefix = id.substring( 0, dot );
|
|
682
|
-
let parent = definitions[prefix];
|
|
683
|
-
if (!parent) {
|
|
684
|
-
path ??= art.name.path;
|
|
685
|
-
pathIndex ??= path?.length - 1;
|
|
686
|
-
const pathItemOrName = (path && pathIndex) ? path[--pathIndex] : art.name;
|
|
687
|
-
const location = weakLocation( pathItemOrName.location );
|
|
688
|
-
parent = { kind: 'namespace', name: { id: prefix, location }, location };
|
|
689
|
-
definitions[prefix] = parent;
|
|
690
|
-
initArtifactParentLink( parent, definitions, path, pathIndex );
|
|
691
|
-
}
|
|
692
|
-
setLink( art, '_parent', parent );
|
|
693
|
-
if (!parent._subArtifacts)
|
|
694
|
-
setLink( parent, '_subArtifacts', Object.create( null ) );
|
|
695
|
-
if (art.$duplicates !== true) // no redef or "first def"
|
|
696
|
-
parent._subArtifacts[id.substring( dot + 1 )] = art; // not dictAdd()
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// Init special things: -------------------------------------------------------
|
|
700
|
-
|
|
701
|
-
function initDollarSelf( art ) {
|
|
702
|
-
// TODO: use setMemberParent() ?
|
|
703
|
-
const self = {
|
|
704
|
-
name: { id: '$self', location: art.location },
|
|
705
|
-
kind: '$self',
|
|
706
|
-
location: art.location,
|
|
707
|
-
};
|
|
708
|
-
setLink( self, '_parent', art );
|
|
709
|
-
setLink( self, '_main', art ); // used on main artifact
|
|
710
|
-
setLink( self, '_origin', art );
|
|
711
|
-
art.$tableAliases = Object.create( null );
|
|
712
|
-
art.$tableAliases.$self = self;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
function initDollarParameters( art ) {
|
|
716
|
-
// TODO: use setMemberParent() ?
|
|
717
|
-
const parameters = {
|
|
718
|
-
name: { id: '$parameters' },
|
|
719
|
-
kind: '$parameters',
|
|
720
|
-
location: art.location,
|
|
721
|
-
deprecated: true, // hide in code completion
|
|
722
|
-
};
|
|
723
|
-
setLink( parameters, '_parent', art );
|
|
724
|
-
setLink( parameters, '_main', art );
|
|
725
|
-
// Search for :const after :param. If there will be a possibility in the
|
|
726
|
-
// future that we can extend <query>.columns, we must be sure to use
|
|
727
|
-
// _block of that new column after :param (or just allow $parameters there).
|
|
728
|
-
setLink( parameters, '_block', art._block );
|
|
729
|
-
if (art.params) {
|
|
730
|
-
parameters.elements = art.params;
|
|
731
|
-
parameters.$tableAliases = art.params; // TODO: find better name - $lexical?
|
|
732
|
-
}
|
|
733
|
-
art.$tableAliases.$parameters = parameters;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// From here til EOF, reexamine code ---------------------------------------
|
|
737
|
-
// See populate:
|
|
738
|
-
// - userQuery() or _query property?
|
|
739
|
-
// - initFromColumns()
|
|
740
|
-
// - ensureColumnName()
|
|
741
|
-
|
|
742
|
-
// Init queries: --------------------------------------------------------------
|
|
743
|
-
|
|
744
696
|
// art is:
|
|
745
697
|
// - entity/event/type for top-level queries (including UNION args)
|
|
746
698
|
// - $tableAlias for sub query in FROM - TODO: what about UNION there?
|
|
@@ -749,7 +701,7 @@ function define( model ) {
|
|
|
749
701
|
if (!query) // parse error
|
|
750
702
|
return query;
|
|
751
703
|
if (query.from) { // select
|
|
752
|
-
initQuery();
|
|
704
|
+
initQuery( query, art );
|
|
753
705
|
initTableExpression( query.from, query, [] );
|
|
754
706
|
if (query.mixin)
|
|
755
707
|
initMixins( query, art );
|
|
@@ -794,21 +746,21 @@ function define( model ) {
|
|
|
794
746
|
return undefined;
|
|
795
747
|
}
|
|
796
748
|
return query._leadingQuery || query;
|
|
749
|
+
}
|
|
797
750
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
}
|
|
751
|
+
function initQuery( query, art ) {
|
|
752
|
+
setLink( query, '_block', art._block );
|
|
753
|
+
const main = art._main || art;
|
|
754
|
+
setLink( query, '_$next',
|
|
755
|
+
(art.kind === '$tableAlias' ? art._parent._$next : art ) );
|
|
756
|
+
query.kind = 'select';
|
|
757
|
+
query.name = { location: query.location, id: main.$queries.length + 1 };
|
|
758
|
+
setMemberParent( query, null, main );
|
|
759
|
+
// console.log(art.kind,art.name,query.name,query._$next.name)
|
|
760
|
+
main.$queries.push( query );
|
|
761
|
+
setLink( query, '_parent', art ); // _parent should point to alias/main/query
|
|
762
|
+
query.$tableAliases = Object.create( null ); // table aliases and mixin definitions
|
|
763
|
+
dependsOnSilent( main, query );
|
|
812
764
|
}
|
|
813
765
|
|
|
814
766
|
// table is table expression in FROM, becomes an alias
|
|
@@ -816,7 +768,7 @@ function define( model ) {
|
|
|
816
768
|
if (!table) // parse error
|
|
817
769
|
return;
|
|
818
770
|
if (table.path) { // path in FROM
|
|
819
|
-
if (!table.path.length
|
|
771
|
+
if (!table.path.length)
|
|
820
772
|
// parse error (e.g. final ',' in FROM), projection on <eof>
|
|
821
773
|
return;
|
|
822
774
|
if (!table.name) {
|
|
@@ -826,7 +778,7 @@ function define( model ) {
|
|
|
826
778
|
// TODO: if we have too much time, we can calculate the real location with '.'
|
|
827
779
|
table.name = { $inferred: 'as', id, location: last.location };
|
|
828
780
|
}
|
|
829
|
-
addAsAlias();
|
|
781
|
+
addAsAlias( table, query, joinParents );
|
|
830
782
|
// _origin is set when we resolve the ref
|
|
831
783
|
if (query._parent.kind !== 'select')
|
|
832
784
|
query._main._from.push( table ); // store tabref if outside "real" subquery
|
|
@@ -838,7 +790,7 @@ function define( model ) {
|
|
|
838
790
|
const id = `$_select_${ query._main.$queries.length + 1 }__`;
|
|
839
791
|
table.name = { id, location: table.location, $inferred: '$internal' };
|
|
840
792
|
}
|
|
841
|
-
addAsAlias();
|
|
793
|
+
addAsAlias( table, query, joinParents );
|
|
842
794
|
// Store _origin to leading query of table.query for name resolution
|
|
843
795
|
setLink( table, '_origin', initQueryExpression( table.query, table ) );
|
|
844
796
|
}
|
|
@@ -878,37 +830,36 @@ function define( model ) {
|
|
|
878
830
|
// TODO: probably set this to query if we switch to name restriction in JOIN
|
|
879
831
|
}
|
|
880
832
|
}
|
|
881
|
-
|
|
833
|
+
}
|
|
882
834
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
}
|
|
895
|
-
else {
|
|
896
|
-
error( 'duplicate-definition', [ loc, table ], { name, '#': 'alias' } );
|
|
897
|
-
}
|
|
898
|
-
} );
|
|
899
|
-
// also add to JOIN nodes for name restrictions:
|
|
900
|
-
for (const p of joinParents) {
|
|
901
|
-
// for JOIN alias restriction, we cannot use $duplicates, as it is
|
|
902
|
-
// already used for duplicate aliases of queries:
|
|
903
|
-
dictAddArray( p.$tableAliases, table.name.id, table );
|
|
835
|
+
function addAsAlias( table, query, joinParents ) {
|
|
836
|
+
table.kind = '$tableAlias';
|
|
837
|
+
setLink( table, '_block', query._block );
|
|
838
|
+
setMemberParent( table, table.name.id, query );
|
|
839
|
+
dictAdd( query.$tableAliases, table.name.id, table, ( name, loc, tableAlias ) => {
|
|
840
|
+
if (tableAlias.name.$inferred === '$internal') {
|
|
841
|
+
const semanticLoc = tableAlias.query?.name ? tableAlias.query : tableAlias;
|
|
842
|
+
// TODO: the semanticLoc query is not initialized yet, and thus cannot
|
|
843
|
+
// be used here
|
|
844
|
+
error( 'name-missing-alias', [ tableAlias.location, semanticLoc ],
|
|
845
|
+
{ '#': 'duplicate', code: 'as ‹alias›' } );
|
|
904
846
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
'#': (table.name.$inferred ? '$tableImplicit' : '$tableAlias'),
|
|
908
|
-
name: '$',
|
|
909
|
-
keyword: 'as',
|
|
910
|
-
} );
|
|
847
|
+
else {
|
|
848
|
+
error( 'duplicate-definition', [ loc, table ], { name, '#': 'alias' } );
|
|
911
849
|
}
|
|
850
|
+
} );
|
|
851
|
+
// also add to JOIN nodes for name restrictions:
|
|
852
|
+
for (const p of joinParents) {
|
|
853
|
+
// for JOIN alias restriction, we cannot use $duplicates, as it is
|
|
854
|
+
// already used for duplicate aliases of queries:
|
|
855
|
+
dictAddArray( p.$tableAliases, table.name.id, table );
|
|
856
|
+
}
|
|
857
|
+
if (table.name?.id[0] === '$' && table.name.$inferred !== '$internal') {
|
|
858
|
+
message( 'name-invalid-dollar-alias', [ table.name.location, table ], {
|
|
859
|
+
'#': (table.name.$inferred ? '$tableImplicit' : '$tableAlias'),
|
|
860
|
+
name: '$',
|
|
861
|
+
keyword: 'as',
|
|
862
|
+
} );
|
|
912
863
|
}
|
|
913
864
|
}
|
|
914
865
|
|
|
@@ -921,7 +872,7 @@ function define( model ) {
|
|
|
921
872
|
initExprForQuery( query.where, query );
|
|
922
873
|
if (query.having)
|
|
923
874
|
initExprForQuery( query.having, query );
|
|
924
|
-
initMembers( query
|
|
875
|
+
initMembers( query );
|
|
925
876
|
}
|
|
926
877
|
|
|
927
878
|
function initExprForQuery( expr, query ) {
|
|
@@ -939,11 +890,6 @@ function define( model ) {
|
|
|
939
890
|
const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
|
|
940
891
|
args.forEach( e => initExprForQuery( e, query ) );
|
|
941
892
|
}
|
|
942
|
-
else if (expr.path && expr.$expected === 'exists') {
|
|
943
|
-
// TODO: does really the parser has to set $expected?
|
|
944
|
-
expr.$expected = 'approved-exists';
|
|
945
|
-
approveExistsInChildren( expr );
|
|
946
|
-
}
|
|
947
893
|
}
|
|
948
894
|
|
|
949
895
|
function initMixins( query, art ) {
|
|
@@ -972,15 +918,18 @@ function define( model ) {
|
|
|
972
918
|
let wildcard = !!inExtension; // no `extend … with columns { * }`
|
|
973
919
|
// TODO: forbid expand/inline in ref-where, outside queries (CSN), ...
|
|
974
920
|
let hasItems = false;
|
|
921
|
+
let colIndex = 0;
|
|
975
922
|
for (const col of columns || parent.expand || parent.inline || []) {
|
|
923
|
+
++colIndex;
|
|
976
924
|
if (!col) // parse error
|
|
977
925
|
continue;
|
|
978
926
|
hasItems = true;
|
|
979
927
|
if (!columns) { // expand or inline
|
|
980
928
|
if (parent.value)
|
|
981
|
-
setLink( col, '_columnParent', parent ); // also set for '*' in expand/inline
|
|
929
|
+
setLink( col, '_columnParent', parent ); // TODO?: also set for '*' in expand/inline
|
|
982
930
|
else if (parent._columnParent)
|
|
983
931
|
setLink( col, '_columnParent', parent._columnParent );
|
|
932
|
+
// colParent not set for `{ col } as struct`, except if that is in expand/inline
|
|
984
933
|
}
|
|
985
934
|
if (col.val === '*') {
|
|
986
935
|
if (!wildcard) {
|
|
@@ -1009,10 +958,13 @@ function define( model ) {
|
|
|
1009
958
|
}
|
|
1010
959
|
}
|
|
1011
960
|
// Either expression (value), expand, new virtual or new association
|
|
1012
|
-
else
|
|
961
|
+
else {
|
|
1013
962
|
col.kind = 'element';
|
|
1014
963
|
if (!col._block)
|
|
1015
964
|
setLink( col, '_block', parent._block );
|
|
965
|
+
setMemberParent( col, null, parent );
|
|
966
|
+
// remark: _parent might be change if col is in expand of to-many assoc
|
|
967
|
+
ensureColumnName( col, colIndex, parent, !columns );
|
|
1016
968
|
if (col.inline) { // `@anno elem.{ * }` does not work
|
|
1017
969
|
if (col.doc) {
|
|
1018
970
|
message( 'syntax-unexpected-anno', [ col.doc.location, col ],
|
|
@@ -1029,7 +981,8 @@ function define( model ) {
|
|
|
1029
981
|
// TODO: allow sub queries? at least in top-level expand without parallel ref
|
|
1030
982
|
if (columns && !inExtension) // not (yet) in `extend … with columns {…}`
|
|
1031
983
|
initExprForQuery( col.value, parent );
|
|
1032
|
-
|
|
984
|
+
if (col.expand || col.inline)
|
|
985
|
+
initSelectItems( col, null, user ); // TODO: use col, remove 3rd param?
|
|
1033
986
|
}
|
|
1034
987
|
|
|
1035
988
|
initCdlTypeCast( col, parent );
|
|
@@ -1044,76 +997,81 @@ function define( model ) {
|
|
|
1044
997
|
}
|
|
1045
998
|
}
|
|
1046
999
|
|
|
1000
|
+
/**
|
|
1001
|
+
* Ensure that the column has a name.
|
|
1002
|
+
*
|
|
1003
|
+
* @param col
|
|
1004
|
+
* @param {number} colIndex
|
|
1005
|
+
* @param query
|
|
1006
|
+
* @param {boolean} insideExpand
|
|
1007
|
+
* Whether the column is inside 'expand'.
|
|
1008
|
+
* Anonymous 'expands' don't have a column parent, hence why we need to know this explicitly.
|
|
1009
|
+
*/
|
|
1010
|
+
function ensureColumnName( col, colIndex, query, insideExpand ) {
|
|
1011
|
+
if (col.name)
|
|
1012
|
+
return col.name.id;
|
|
1013
|
+
if (col.inline || col.val === '*' || col.val === '**') // '**' = duplicate '*'
|
|
1014
|
+
return '';
|
|
1015
|
+
// TODO: should we give technical name (colIndex) for error messages?
|
|
1016
|
+
const path = col.value &&
|
|
1017
|
+
(col.value.path || !col.value.args && col.value.func?.path);
|
|
1018
|
+
const last = path?.length && path[path.length - 1];
|
|
1019
|
+
if (last) {
|
|
1020
|
+
col.name = { id: last.id || '', location: last.location, $inferred: 'as' };
|
|
1021
|
+
return col.name.id;
|
|
1022
|
+
}
|
|
1023
|
+
else if ((col.expand || col.value) &&
|
|
1024
|
+
!path && // no parse error (path without last item)
|
|
1025
|
+
(insideExpand || query._parent.kind !== 'select')) { // not sub-selects
|
|
1026
|
+
error( 'query-req-name',
|
|
1027
|
+
// TODO: message function: `query` should work directly
|
|
1028
|
+
[ (col.value || col).location, (query.name ? query : query._parent ) ],
|
|
1029
|
+
{},
|
|
1030
|
+
'Alias name is required for this select item' );
|
|
1031
|
+
}
|
|
1032
|
+
col.name = {
|
|
1033
|
+
// NOTE: If the alias is changed, corresponding name-clash tests must be updated as well!
|
|
1034
|
+
id: `$_column_${ colIndex }`,
|
|
1035
|
+
location: (col.value || col).location,
|
|
1036
|
+
$inferred: '$internal',
|
|
1037
|
+
};
|
|
1038
|
+
return col.name.id;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1047
1041
|
function initCdlTypeCast( col, parent ) {
|
|
1048
1042
|
if (col.val)
|
|
1049
1043
|
return; // e.g. '*' column
|
|
1050
1044
|
|
|
1051
1045
|
setMemberParent( col, col.name, parent );
|
|
1052
|
-
initMembers( col
|
|
1046
|
+
initMembers( col );
|
|
1053
1047
|
|
|
1054
1048
|
// We don't allow CDL-style casts to anonymous structures. We reject it already here
|
|
1055
1049
|
// and not in checks.js to ensure that it's rejected in parseCdl.
|
|
1050
|
+
// TODO: via <guard> in CdlGrammar.g4
|
|
1056
1051
|
if (col.elements) {
|
|
1057
|
-
error('type-invalid-cast', [ col.elements[$location] ?? col.location, col ],
|
|
1058
|
-
|
|
1052
|
+
error( 'type-invalid-cast', [ (col.elements[$location] ?? col.location), col ],
|
|
1053
|
+
{ '#': 'to-inline-structure' } );
|
|
1059
1054
|
}
|
|
1060
1055
|
else if (col.expand && (col.type || col.elements || col.items)) {
|
|
1061
1056
|
const loc = (col.type?.location || col.elements?.[$location] ||
|
|
1062
|
-
|
|
1063
|
-
error('type-invalid-cast', [ loc, col ], { '#': 'expand' });
|
|
1057
|
+
col.items?.location || col.location);
|
|
1058
|
+
error( 'type-invalid-cast', [ loc, col ], { '#': 'expand' } );
|
|
1064
1059
|
}
|
|
1060
|
+
// Remark: there are quite a few consequential error, TODO: avoid them
|
|
1065
1061
|
}
|
|
1066
1062
|
|
|
1067
|
-
/**
|
|
1068
|
-
* If we have a valid top-level exists, exists in filters of sub-expressions can be translated,
|
|
1069
|
-
* since we will have a top-level subquery after exists-processing in the forRelationalDB.
|
|
1070
|
-
*
|
|
1071
|
-
* Recursively drill down into:
|
|
1072
|
-
* - the .path
|
|
1073
|
-
* - the .args
|
|
1074
|
-
* - the .where.args
|
|
1075
|
-
*
|
|
1076
|
-
* Any $expected === 'exists' encountered along the way are turned into 'approved-exists'
|
|
1077
|
-
*
|
|
1078
|
-
* working: exists toE[exists toE] -> select from E where exists toE
|
|
1079
|
-
* not working: toE[exists toE] -> we don't support subqueries in filters
|
|
1080
|
-
*
|
|
1081
|
-
* @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
|
|
1082
|
-
*/
|
|
1083
|
-
function approveExistsInChildren( exprOrPathElement ) {
|
|
1084
|
-
if (!exprOrPathElement) // may be null in case of parse error
|
|
1085
|
-
return;
|
|
1086
|
-
if (exprOrPathElement.$expected === 'exists')
|
|
1087
|
-
exprOrPathElement.$expected = 'approved-exists';
|
|
1088
|
-
// Drill down
|
|
1089
|
-
if (Array.isArray(exprOrPathElement.args))
|
|
1090
|
-
exprOrPathElement.args.forEach( elem => approveExistsInChildren( elem ) );
|
|
1091
|
-
else if (exprOrPathElement.where?.args)
|
|
1092
|
-
exprOrPathElement.where.args.forEach( elem => approveExistsInChildren( elem ) );
|
|
1093
|
-
else if (exprOrPathElement.path)
|
|
1094
|
-
exprOrPathElement.path.forEach( elem => approveExistsInChildren( elem ) );
|
|
1095
|
-
}
|
|
1096
|
-
// TODO: we might issue 'expr-unexpected-exists' and 'expr-no-subquery' already in
|
|
1097
|
-
// define.js (using a to-be-written expression traversal function in utils.js)
|
|
1098
|
-
|
|
1099
1063
|
// Members (elements, enum, actions, params): ---------------------------------
|
|
1100
1064
|
|
|
1101
1065
|
/**
|
|
1102
1066
|
* Set property `_parent` for all elements in `parent` to `parent` and do so
|
|
1103
1067
|
* recursively for all sub elements.
|
|
1104
1068
|
*
|
|
1105
|
-
* If not for extensions: construct === parent
|
|
1106
|
-
*
|
|
1107
1069
|
* Param `initExtensions` is for parse.cdl - TODO delete
|
|
1108
|
-
*
|
|
1109
|
-
* TODO: separate extension!
|
|
1110
1070
|
*/
|
|
1111
|
-
function initMembers(
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
let obj = initItemsLinks( construct, block );
|
|
1116
|
-
initExprAnnoBlock( construct, block );
|
|
1071
|
+
function initMembers( parent, initExtensions = false ) {
|
|
1072
|
+
const block = parent._block;
|
|
1073
|
+
let obj = initItemsLinks( parent, block );
|
|
1074
|
+
initExprAnnoBlock( parent, block );
|
|
1117
1075
|
if (obj.target && targetIsTargetAspect( obj )) {
|
|
1118
1076
|
obj.targetAspect = obj.target;
|
|
1119
1077
|
delete obj.target;
|
|
@@ -1121,74 +1079,30 @@ function define( model ) {
|
|
|
1121
1079
|
const { targetAspect } = obj;
|
|
1122
1080
|
if (targetAspect) {
|
|
1123
1081
|
if (obj.foreignKeys) {
|
|
1124
|
-
error( 'type-unexpected-foreign-keys', [ obj.foreignKeys[$location],
|
|
1082
|
+
error( 'type-unexpected-foreign-keys', [ obj.foreignKeys[$location], parent ] );
|
|
1125
1083
|
delete obj.foreignKeys; // continuation semantics: not specified
|
|
1126
1084
|
}
|
|
1127
1085
|
if (obj.on && !obj.target) {
|
|
1128
|
-
error( 'type-unexpected-on-condition', [ obj.on.location,
|
|
1086
|
+
error( 'type-unexpected-on-condition', [ obj.on.location, parent ] );
|
|
1129
1087
|
delete obj.on; // continuation semantics: not specified
|
|
1130
1088
|
}
|
|
1131
1089
|
if (targetAspect.elements)
|
|
1132
1090
|
initAnonymousAspect();
|
|
1133
1091
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
forEachInOrder( obj, 'elements', init );
|
|
1140
|
-
if (checkDefinitions( construct, parent, 'enum', obj.enum || false ))
|
|
1141
|
-
forEachGeneric( obj, 'enum', init );
|
|
1142
|
-
}
|
|
1092
|
+
forEachInOrder( obj, 'elements', init );
|
|
1093
|
+
forEachGeneric( obj, 'enum', init );
|
|
1094
|
+
forEachInOrder( obj, 'foreignKeys', init );
|
|
1095
|
+
forEachGeneric( parent, 'actions', init );
|
|
1096
|
+
forEachInOrder( parent, 'params', init );
|
|
1143
1097
|
|
|
1144
|
-
|
|
1145
|
-
forEachInOrder( obj, 'foreignKeys', init );
|
|
1146
|
-
if (checkDefinitions( construct, parent, 'actions' ))
|
|
1147
|
-
forEachGeneric( construct, 'actions', init );
|
|
1148
|
-
if (checkDefinitions( construct, parent, 'params' ))
|
|
1149
|
-
forEachInOrder( construct, 'params', init );
|
|
1150
|
-
const { returns } = construct;
|
|
1098
|
+
const { returns } = parent;
|
|
1151
1099
|
if (returns) {
|
|
1152
|
-
const { kind } =
|
|
1100
|
+
const { kind } = parent;
|
|
1153
1101
|
returns.kind = (kind === 'extend' || kind === 'annotate') ? kind : 'param';
|
|
1154
1102
|
init( returns, '' ); // '' is special name for returns parameter
|
|
1155
1103
|
}
|
|
1156
1104
|
return;
|
|
1157
1105
|
|
|
1158
|
-
function initElementsAsEnum() {
|
|
1159
|
-
// in extensions, extended enums are represented as elements
|
|
1160
|
-
let hasElement = false;
|
|
1161
|
-
for (const n in obj.elements) {
|
|
1162
|
-
const e = obj.elements[n];
|
|
1163
|
-
if (e.kind === 'extend')
|
|
1164
|
-
continue;
|
|
1165
|
-
const noVal = e.value?.val === undefined && e.value?.sym === undefined;
|
|
1166
|
-
// TODO: forbid #symbol as enum value
|
|
1167
|
-
if (e.$syntax === 'element' || // `extend … with elements` or `extend with { element … }`
|
|
1168
|
-
noVal && e.$syntax !== 'enum' || // no value in CDL input
|
|
1169
|
-
e.virtual || e.key || e.masked || e.type || e.elements || e.items || e.stored) {
|
|
1170
|
-
// We do not want to complain separately about all element properties:
|
|
1171
|
-
error( 'ext-unexpected-element', [ e.location, construct ],
|
|
1172
|
-
{ name: e.name.id, code: 'extend … with enum' },
|
|
1173
|
-
// eslint-disable-next-line @stylistic/max-len
|
|
1174
|
-
'Unexpected elements like $(NAME) in an extension for an enum. Additionally, use $(CODE) when extending enums' );
|
|
1175
|
-
// Don't emit 'ext-expecting-enum' if this error is emitted.
|
|
1176
|
-
return;
|
|
1177
|
-
}
|
|
1178
|
-
e.kind = 'enum';
|
|
1179
|
-
if (noVal || e.$syntax !== 'enum')
|
|
1180
|
-
hasElement = true; // warning with CDL input or `name: {}` in CSN input
|
|
1181
|
-
}
|
|
1182
|
-
if (hasElement) {
|
|
1183
|
-
// This message is similar to the one above. In v6, we could probably
|
|
1184
|
-
// turn this warning into an error, remove `$syntax: 'element',
|
|
1185
|
-
// and use the above `ext-unexpected-element` only for CSN input.
|
|
1186
|
-
warning( 'ext-expecting-enum', [ obj.elements[$location], construct ],
|
|
1187
|
-
{ code: 'extend … with enum' }, 'Use $(CODE) when extending enums' );
|
|
1188
|
-
}
|
|
1189
|
-
forEachGeneric( { enum: obj.elements }, 'enum', init );
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
1106
|
function initAnonymousAspect() {
|
|
1193
1107
|
// TODO: main?
|
|
1194
1108
|
const inEntity = parent._main?.kind === 'entity';
|
|
@@ -1198,7 +1112,6 @@ function define( model ) {
|
|
|
1198
1112
|
setLink( targetAspect, '_main', null ); // for name resolution
|
|
1199
1113
|
|
|
1200
1114
|
parent = targetAspect;
|
|
1201
|
-
construct = parent; // avoid extension behavior
|
|
1202
1115
|
targetAspect.kind = 'aspect'; // TODO: probably '$aspect' to detect
|
|
1203
1116
|
setLink( targetAspect, '_block', block );
|
|
1204
1117
|
initDollarSelf( targetAspect );
|
|
@@ -1243,24 +1156,15 @@ function define( model ) {
|
|
|
1243
1156
|
storeExtension( elem, name, prop, parent, block );
|
|
1244
1157
|
return;
|
|
1245
1158
|
}
|
|
1246
|
-
if (isQueryExtension && elem.kind === 'element') {
|
|
1247
|
-
error( 'extend-query', [ elem.location, construct ], // TODO: searchName ?
|
|
1248
|
-
{ code: 'extend projection' },
|
|
1249
|
-
'Use $(CODE) to add select items to the query entity' );
|
|
1250
|
-
return;
|
|
1251
|
-
}
|
|
1252
1159
|
|
|
1253
1160
|
const bl = elem._block || block;
|
|
1254
1161
|
setLink( elem, '_block', bl );
|
|
1255
|
-
|
|
1256
|
-
const add = construct !== parent && (!existing || elem.$inferred !== 'include');
|
|
1162
|
+
|
|
1257
1163
|
// don't dump with `entity T {}; extend T with { extend e {}; e {}; e {} };`:
|
|
1258
|
-
|
|
1259
|
-
elem.$duplicates = null;
|
|
1260
|
-
setMemberParent( elem, name, parent, add && prop );
|
|
1164
|
+
setMemberParent( elem, name, parent );
|
|
1261
1165
|
checkRedefinition( elem );
|
|
1262
|
-
initMembers( elem,
|
|
1263
|
-
if (
|
|
1166
|
+
initMembers( elem, initExtensions );
|
|
1167
|
+
if (elem.kind === 'action' || elem.kind === 'function')
|
|
1264
1168
|
initBoundSelfParam( elem.params, elem._main );
|
|
1265
1169
|
|
|
1266
1170
|
// for a correct home path, setMemberParent needed to be called
|
|
@@ -1269,7 +1173,7 @@ function define( model ) {
|
|
|
1269
1173
|
elem.$syntax === 'enum' && parent.kind === 'extend') // ambiguous in parse-cdl
|
|
1270
1174
|
return;
|
|
1271
1175
|
// -> it's a calculated element
|
|
1272
|
-
if (!elem.type && elem.value
|
|
1176
|
+
if (!elem.type && elem.value.type) { // top-level CAST( expr AS type )
|
|
1273
1177
|
if (!elem.target)
|
|
1274
1178
|
elem.type = { ...elem.value.type, $inferred: 'cast' };
|
|
1275
1179
|
}
|
|
@@ -1279,6 +1183,7 @@ function define( model ) {
|
|
|
1279
1183
|
|
|
1280
1184
|
// Special case (hack) for calculated elements that use composition+filter:
|
|
1281
1185
|
// See "Notes on `$enclosed`" in `ExposingAssocWithFilter.md` for details.
|
|
1186
|
+
// TODO: hm, only for inferred type - then just do not infer it in that case
|
|
1282
1187
|
if (elem.target && elem.value.path?.[elem.value.path.length - 1]?.where) {
|
|
1283
1188
|
delete elem.type;
|
|
1284
1189
|
delete elem.on;
|
|
@@ -1287,126 +1192,6 @@ function define( model ) {
|
|
|
1287
1192
|
}
|
|
1288
1193
|
}
|
|
1289
1194
|
|
|
1290
|
-
function initBoundSelfParam( params, main ) {
|
|
1291
|
-
if (!params)
|
|
1292
|
-
return;
|
|
1293
|
-
if (boundSelfParamType === true) { // first try
|
|
1294
|
-
const def = model.definitions.$self;
|
|
1295
|
-
if (def) {
|
|
1296
|
-
boundSelfParamType = false;
|
|
1297
|
-
return;
|
|
1298
|
-
}
|
|
1299
|
-
boundSelfParamType = '$self';
|
|
1300
|
-
}
|
|
1301
|
-
const first = params[Object.keys( params )[0] || ''];
|
|
1302
|
-
const type = first?.type || first?.items?.type; // this sequence = no derived type
|
|
1303
|
-
const path = type?.path;
|
|
1304
|
-
if (path?.length === 1 && path[0]?.id === '$self') { // TODO: no where: ?
|
|
1305
|
-
const $self = main.$tableAliases?.$self ||
|
|
1306
|
-
main.kind === 'extend' && { name: { id: '$self' } };
|
|
1307
|
-
// remark: an 'extend' has no "table alias" `$self` (relevant for parse-cdl)
|
|
1308
|
-
setLink( type, '_artifact', $self );
|
|
1309
|
-
setLink( path[0], '_artifact', $self );
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
/**
|
|
1314
|
-
* Initialize artifact links inside `obj.items` (for nested ones as well).
|
|
1315
|
-
* Does nothing, it `obj.items` does not exist.
|
|
1316
|
-
*
|
|
1317
|
-
* @param {XSN.Artifact} obj
|
|
1318
|
-
* @param {object} block
|
|
1319
|
-
* @return {XSN.Artifact}
|
|
1320
|
-
*/
|
|
1321
|
-
function initItemsLinks( obj, block ) {
|
|
1322
|
-
let { items } = obj;
|
|
1323
|
-
while (items) {
|
|
1324
|
-
setLink( items, '_outer', obj );
|
|
1325
|
-
setLink( items, '_parent', obj._parent );
|
|
1326
|
-
setLink( items, '_block', block );
|
|
1327
|
-
obj = items;
|
|
1328
|
-
items = obj.items;
|
|
1329
|
-
}
|
|
1330
|
-
return obj;
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
// To be reworked -------------------------------------------------------------
|
|
1334
|
-
|
|
1335
|
-
// TODO: is only necessary for extensions - make special for extend/annotate
|
|
1336
|
-
function checkDefinitions( construct, parent, prop, dict = construct[prop] ) {
|
|
1337
|
-
// TODO: do differently, see also annotateMembers() in resolver
|
|
1338
|
-
// To have been checked by parsers:
|
|
1339
|
-
// - artifacts (CDL-only anyway) only inside [extend] context|service
|
|
1340
|
-
if (!dict)
|
|
1341
|
-
return false;
|
|
1342
|
-
const feature = kindProperties[parent.kind ?? 'element'][prop];
|
|
1343
|
-
if (feature &&
|
|
1344
|
-
(feature === true || construct.kind !== 'extend' || feature( prop, parent )))
|
|
1345
|
-
return true;
|
|
1346
|
-
const location = dict[$location];
|
|
1347
|
-
|
|
1348
|
-
// TODO: a bit inconsistent = not a simple switch on `prop`…
|
|
1349
|
-
if (prop === 'actions') {
|
|
1350
|
-
if (Object.keys( dict ).length) {
|
|
1351
|
-
error( 'def-unexpected-actions', [ location, construct ], {}, // TODO: ext-
|
|
1352
|
-
'Actions and functions only exist top-level and for entities' ); // or aspects
|
|
1353
|
-
}
|
|
1354
|
-
else {
|
|
1355
|
-
warning( 'ext-ignoring-actions', [ location, construct ], {},
|
|
1356
|
-
'Actions and functions only exist top-level and for entities' );
|
|
1357
|
-
return false;
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
else if (parent.kind === 'action' || parent.kind === 'function') {
|
|
1361
|
-
error( 'ext-unexpected-action', [ construct.location, construct ], { '#': parent.kind }, {
|
|
1362
|
-
std: 'Actions and functions can\'t be extended, only annotated', // TODO: → ext-unsupported
|
|
1363
|
-
action: 'Actions can\'t be extended, only annotated',
|
|
1364
|
-
function: 'Functions can\'t be extended, only annotated',
|
|
1365
|
-
} );
|
|
1366
|
-
}
|
|
1367
|
-
else if (prop === 'params') {
|
|
1368
|
-
if (!feature) {
|
|
1369
|
-
// Note: This error can't be triggered at the moment. But as we likely want to
|
|
1370
|
-
// allow extensions with params in the future, we keep the code.
|
|
1371
|
-
error( 'def-unexpected-params', [ location, construct ], {},
|
|
1372
|
-
'Parameters only exist for entities, actions or functions' );
|
|
1373
|
-
}
|
|
1374
|
-
else {
|
|
1375
|
-
// remark: we could allow this
|
|
1376
|
-
error( 'extend-with-params', [ location, construct ], {},
|
|
1377
|
-
'Extending artifacts with parameters is not supported' );
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
else if (feature) { // allowed in principle, but not with extend
|
|
1381
|
-
if (!Object.keys( dict ).length) {
|
|
1382
|
-
warning( 'ext-ignoring-elements', [ location, construct ], {},
|
|
1383
|
-
'Only structures with directly specified elements can be extended by elements' );
|
|
1384
|
-
return false;
|
|
1385
|
-
}
|
|
1386
|
-
else if (parent.$inferred === 'include') { // special case for better error message
|
|
1387
|
-
const variant = (construct.enum || construct.elements) ? 'elements' : 'std';
|
|
1388
|
-
error( 'ref-expected-direct-structure', [ location, construct ],
|
|
1389
|
-
{ '#': variant, art: parent } );
|
|
1390
|
-
}
|
|
1391
|
-
else {
|
|
1392
|
-
error( 'extend-type', [ location, construct ], {},
|
|
1393
|
-
'Only structures or enum types can be extended with elements/enums' );
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
else if (prop === 'elements') {
|
|
1397
|
-
error( 'def-unexpected-elements', [ location, construct ], {},
|
|
1398
|
-
'Elements only exist in entities, types or typed constructs' );
|
|
1399
|
-
}
|
|
1400
|
-
else if (prop === 'columns') {
|
|
1401
|
-
error( 'extend-columns', [ location, construct ], { art: construct } );
|
|
1402
|
-
}
|
|
1403
|
-
else { // if (prop === 'enum') {
|
|
1404
|
-
error( 'def-unexpected-enum', [ location, construct ], {},
|
|
1405
|
-
'Enum symbols can only be defined for types or typed constructs' );
|
|
1406
|
-
}
|
|
1407
|
-
return construct === parent;
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
1195
|
/**
|
|
1411
1196
|
* Return whether the `target` is actually a `targetAspect`
|
|
1412
1197
|
*/
|