@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/compiler/index.js
CHANGED
|
@@ -183,7 +183,7 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
183
183
|
return all.then( () => {
|
|
184
184
|
options.abortSignal?.throwIfAborted();
|
|
185
185
|
moduleLayers.setLayers( input.sources );
|
|
186
|
-
return compileDoX( model );
|
|
186
|
+
return compileDoX( model ); // also async
|
|
187
187
|
} );
|
|
188
188
|
|
|
189
189
|
// Read file `filename` and parse its content, return messages
|
|
@@ -303,7 +303,7 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
moduleLayers.setLayers( a.sources );
|
|
306
|
-
return
|
|
306
|
+
return compileDoXSync( model );
|
|
307
307
|
|
|
308
308
|
// Read file `filename` and parse its content, return messages
|
|
309
309
|
function readAndParseSync( filename, cb ) {
|
|
@@ -423,7 +423,7 @@ function compileSourcesX( sourcesDict, options = {} ) {
|
|
|
423
423
|
}
|
|
424
424
|
moduleLayers.setLayers( sources );
|
|
425
425
|
|
|
426
|
-
return
|
|
426
|
+
return compileDoXSync( model );
|
|
427
427
|
}
|
|
428
428
|
|
|
429
429
|
/**
|
|
@@ -457,8 +457,8 @@ function recompileX( csn, options ) {
|
|
|
457
457
|
|
|
458
458
|
sources[file] = parseCsn.augment( csn, file, options, model.$messageFunctions );
|
|
459
459
|
moduleLayers.setLayers( sources );
|
|
460
|
-
const compiled =
|
|
461
|
-
if (options.messages) // does not help with exception in
|
|
460
|
+
const compiled = compileDoXSync( model ); // calls throwWithError()
|
|
461
|
+
if (options.messages) // does not help with exception in compileDoXSync()
|
|
462
462
|
deduplicateMessages( options.messages ); // TODO: do better
|
|
463
463
|
return compiled;
|
|
464
464
|
}
|
|
@@ -467,10 +467,12 @@ function recompileX( csn, options ) {
|
|
|
467
467
|
* On the given model (AST like CSN) run the definer, resolver as well as semantic checks.
|
|
468
468
|
* Creates an augmented CSN (XSN) and returns it.
|
|
469
469
|
*
|
|
470
|
+
* This is the non-interruptible version of `compileDoX()` and can be used in non-`async` functions.
|
|
471
|
+
*
|
|
470
472
|
* @param {object} model AST like CSN generated e.g. by `parsers.parseCdl()`
|
|
471
473
|
* @returns {XSN.Model} Augmented CSN (XSN)
|
|
472
474
|
*/
|
|
473
|
-
function
|
|
475
|
+
function compileDoXSync( model ) {
|
|
474
476
|
const { options } = model;
|
|
475
477
|
const { throwWithError } = model.$messageFunctions;
|
|
476
478
|
if (!options.testMode)
|
|
@@ -512,6 +514,61 @@ function compileDoX( model ) {
|
|
|
512
514
|
return propagator.propagate( model );
|
|
513
515
|
}
|
|
514
516
|
|
|
517
|
+
/**
|
|
518
|
+
* On the given model (AST like CSN) run the definer, resolver as well as semantic checks.
|
|
519
|
+
* Creates an augmented CSN (XSN) and returns it.
|
|
520
|
+
*
|
|
521
|
+
* @param {object} model AST like CSN generated e.g. by `parsers.parseCdl()`
|
|
522
|
+
* @returns {XSN.Model} Augmented CSN (XSN)
|
|
523
|
+
*/
|
|
524
|
+
async function compileDoX( model ) {
|
|
525
|
+
const { options } = model;
|
|
526
|
+
const { throwWithError } = model.$messageFunctions;
|
|
527
|
+
if (!options.testMode)
|
|
528
|
+
model.meta = {}; // provide initial central meta object
|
|
529
|
+
|
|
530
|
+
checkRemovedDeprecatedFlags( options, model.$messageFunctions );
|
|
531
|
+
|
|
532
|
+
if (options.parseOnly) {
|
|
533
|
+
throwWithError();
|
|
534
|
+
return model;
|
|
535
|
+
}
|
|
536
|
+
model.$functions = {};
|
|
537
|
+
fns( model ); // attach (mostly) paths functions
|
|
538
|
+
define( model );
|
|
539
|
+
await checkAsyncAbortFlag( options.abortSignal );
|
|
540
|
+
|
|
541
|
+
// do not run the resolver in parse-cdl mode or we get duplicate annotations, etc.
|
|
542
|
+
// TODO: do not use this function for parseCdl anyway…
|
|
543
|
+
if (options.parseCdl) {
|
|
544
|
+
finalizeParseCdl( model );
|
|
545
|
+
throwWithError();
|
|
546
|
+
return model;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
for (const phase of [ extend, generate, kickStart, populate ]) {
|
|
550
|
+
phase( model );
|
|
551
|
+
// eslint-disable-next-line no-await-in-loop
|
|
552
|
+
await checkAsyncAbortFlag( options.abortSignal );
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
model.definitions = model.$functions.shuffleDict( model.definitions );
|
|
556
|
+
// Shuffling extensions is more difficult due to intra-file extensions of same artifact
|
|
557
|
+
// TODO: think about making this work
|
|
558
|
+
|
|
559
|
+
for (const phase of [ resolve, tweakAssocs, assertConsistency, check ]) {
|
|
560
|
+
phase( model );
|
|
561
|
+
// eslint-disable-next-line no-await-in-loop
|
|
562
|
+
await checkAsyncAbortFlag( options.abortSignal );
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
throwWithError();
|
|
566
|
+
if (options.lintMode)
|
|
567
|
+
return model;
|
|
568
|
+
|
|
569
|
+
return propagator.propagate( model );
|
|
570
|
+
}
|
|
571
|
+
|
|
515
572
|
/**
|
|
516
573
|
* Process an array of `filenames`. Returns an object with properties:
|
|
517
574
|
* - `sources`: dictionary which has a filename as key (value is irrelevant)
|
|
@@ -595,6 +652,31 @@ function createSourcesDict( filenames, filenameMap, dir ) {
|
|
|
595
652
|
return { sources, files, repeated };
|
|
596
653
|
}
|
|
597
654
|
|
|
655
|
+
/**
|
|
656
|
+
* An `await`able function to fake a real asynchronous event.
|
|
657
|
+
*
|
|
658
|
+
* @returns {Promise<unknown>}
|
|
659
|
+
*/
|
|
660
|
+
async function waitForNextEventLoopIteration() {
|
|
661
|
+
return new Promise( ( r ) => {
|
|
662
|
+
setTimeout( r, 0 );
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* An actual async function that uses `setTimeout()` to allow the Node event loop to
|
|
668
|
+
* start its next iteration, but only if the `abortSignal` is defined.
|
|
669
|
+
*
|
|
670
|
+
* @param {AbortSignal?} abortSignal
|
|
671
|
+
* @returns {Promise<void>}
|
|
672
|
+
*/
|
|
673
|
+
async function checkAsyncAbortFlag( abortSignal ) {
|
|
674
|
+
if (!abortSignal)
|
|
675
|
+
return;
|
|
676
|
+
await waitForNextEventLoopIteration();
|
|
677
|
+
abortSignal.throwIfAborted();
|
|
678
|
+
}
|
|
679
|
+
|
|
598
680
|
module.exports = {
|
|
599
681
|
parseX,
|
|
600
682
|
compileX,
|
package/lib/compiler/lsp-api.js
CHANGED
|
@@ -397,6 +397,8 @@ function* nameAsReference( ref, hint = null ) {
|
|
|
397
397
|
function* definitionNameTokens( name, art ) {
|
|
398
398
|
if (!art.kind)
|
|
399
399
|
return null; // e.g. parameter references
|
|
400
|
+
if (!name)
|
|
401
|
+
return null; // e.g. column that couldn't be populated
|
|
400
402
|
if (art.kind === '$annotation')
|
|
401
403
|
return null; // annotation name, e.g. in `@anno: (elem)`
|
|
402
404
|
|
package/lib/compiler/populate.js
CHANGED
|
@@ -813,7 +813,7 @@ function populate( model ) {
|
|
|
813
813
|
// invent a name for code completion in expression, see also #10596
|
|
814
814
|
col.name = {
|
|
815
815
|
id: '',
|
|
816
|
-
location: col.value
|
|
816
|
+
location: col.value?.location || col.location,
|
|
817
817
|
$inferred: 'none',
|
|
818
818
|
};
|
|
819
819
|
return '';
|
|
@@ -979,14 +979,14 @@ function populate( model ) {
|
|
|
979
979
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
980
980
|
info( 'wildcard-excluding-many', [ sibling.name.location, query ],
|
|
981
981
|
{ id, keyword: 'excluding' },
|
|
982
|
-
// eslint-disable-next-line @stylistic/
|
|
982
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
983
983
|
'This select item replaces $(ID) from two or more sources. Add $(ID) to $(KEYWORD) to silence this message' );
|
|
984
984
|
}
|
|
985
985
|
else {
|
|
986
986
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
987
987
|
info( 'wildcard-excluding-one', [ sibling.name.location, query ],
|
|
988
988
|
{ id, alias: navElem._parent.name.id, keyword: 'excluding' },
|
|
989
|
-
// eslint-disable-next-line @stylistic/
|
|
989
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
990
990
|
'This select item replaces $(ID) from table alias $(ALIAS). Add $(ID) to $(KEYWORD) to silence this message' );
|
|
991
991
|
}
|
|
992
992
|
}
|
|
@@ -1112,9 +1112,9 @@ function populate( model ) {
|
|
|
1112
1112
|
// art: definitionScope( target ), - TODO extra debug info in message
|
|
1113
1113
|
sorted_arts: exposed,
|
|
1114
1114
|
}, {
|
|
1115
|
-
// eslint-disable-next-line @stylistic/
|
|
1115
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1116
1116
|
std: 'Replace target $(TARGET) by one of $(SORTED_ARTS); can\'t auto-redirect this association if multiple projections exist in this service',
|
|
1117
|
-
// eslint-disable-next-line @stylistic/
|
|
1117
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1118
1118
|
two: 'Replace target $(TARGET) by $(SORTED_ARTS) or $(SECOND); can\'t auto-redirect this association if multiple projections exist in this service',
|
|
1119
1119
|
} );
|
|
1120
1120
|
// continuation semantics: no auto-redirection
|
|
@@ -1136,11 +1136,11 @@ function populate( model ) {
|
|
|
1136
1136
|
anno: 'cds.redirection.target',
|
|
1137
1137
|
sorted_arts: exposed,
|
|
1138
1138
|
}, {
|
|
1139
|
-
// eslint-disable-next-line @stylistic/
|
|
1139
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1140
1140
|
std: 'Add $(ANNO) to one of $(SORTED_ARTS) to select the entity as redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
|
|
1141
|
-
// eslint-disable-next-line @stylistic/
|
|
1141
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1142
1142
|
two: 'Add $(ANNO) to either $(SORTED_ARTS) or $(SECOND) to select the entity as redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
|
|
1143
|
-
// eslint-disable-next-line @stylistic/
|
|
1143
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1144
1144
|
justOne: 'Remove $(ANNO) from all but one of $(SORTED_ARTS) to have a unique redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
|
|
1145
1145
|
} );
|
|
1146
1146
|
}
|
|
@@ -354,7 +354,7 @@ function propagate( model ) {
|
|
|
354
354
|
const art = item && item._artifact;
|
|
355
355
|
if (art?.virtual?.val) {
|
|
356
356
|
message( 'def-missing-virtual', [ item.location, elem ], { art, keyword: 'virtual' },
|
|
357
|
-
// eslint-disable-next-line @stylistic/
|
|
357
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
358
358
|
'Prepend $(KEYWORD) to current select item - containing element $(ART) is virtual' );
|
|
359
359
|
return;
|
|
360
360
|
}
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -326,7 +326,7 @@ function resolve( model ) {
|
|
|
326
326
|
propagateKeys = false;
|
|
327
327
|
info( 'query-from-many', [ toMany.location, query ], { art: toMany }, {
|
|
328
328
|
std: 'Key properties are not propagated because a to-many association $(ART) is selected',
|
|
329
|
-
// eslint-disable-next-line @stylistic/
|
|
329
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
330
330
|
element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
|
|
331
331
|
} );
|
|
332
332
|
}
|
|
@@ -367,9 +367,9 @@ function resolve( model ) {
|
|
|
367
367
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
368
368
|
info( 'query-navigate-many', [ art.location, user || query ], { art }, {
|
|
369
369
|
std: 'Navigating along to-many association $(ART) - key properties are not propagated',
|
|
370
|
-
// eslint-disable-next-line @stylistic/
|
|
370
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
371
371
|
element: 'Navigating along to-many association $(MEMBER) of $(ART) - key properties are not propagated',
|
|
372
|
-
// eslint-disable-next-line @stylistic/
|
|
372
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
373
373
|
alias: 'Navigating along to-many mixin association $(MEMBER) - key properties are not propagated',
|
|
374
374
|
} );
|
|
375
375
|
}
|
|
@@ -430,10 +430,7 @@ function resolve( model ) {
|
|
|
430
430
|
}
|
|
431
431
|
else if (!allowedInMain || !isTopLevelElement) {
|
|
432
432
|
warning( 'def-unsupported-key', [ art.key.location, art ],
|
|
433
|
-
{ '#': allowedInMain ? 'sub' : '
|
|
434
|
-
std: '$(KEYWORD) is only supported for elements in an entity or an aspect',
|
|
435
|
-
sub: '$(KEYWORD) is only supported for top-level elements',
|
|
436
|
-
} );
|
|
433
|
+
{ '#': allowedInMain ? 'sub' : 'kind', keyword: 'key' } );
|
|
437
434
|
}
|
|
438
435
|
}
|
|
439
436
|
|
|
@@ -490,9 +487,9 @@ function resolve( model ) {
|
|
|
490
487
|
resolveTypeExpr( obj, art );
|
|
491
488
|
// typeOf unmanaged assoc? TODO: is this the right place to check this?
|
|
492
489
|
// (probably better in rewriteAssociations)
|
|
493
|
-
const
|
|
494
|
-
if (
|
|
495
|
-
const assocType = getAssocSpec(
|
|
490
|
+
const elemType = obj.type._artifact;
|
|
491
|
+
if (elemType && effectiveType( elemType )) {
|
|
492
|
+
const assocType = getAssocSpec( elemType ) || {};
|
|
496
493
|
if ((assocType.on || assocType.$assocFilter) && !obj.on)
|
|
497
494
|
obj.on = { $inferred: 'rewrite' }; // TODO: no extra rewrite here
|
|
498
495
|
if (assocType.targetAspect) {
|
|
@@ -506,11 +503,10 @@ function resolve( model ) {
|
|
|
506
503
|
}
|
|
507
504
|
|
|
508
505
|
// Check if relational type is missing its target or if it's used directly.
|
|
509
|
-
if (
|
|
510
|
-
!obj.target && !obj.targetAspect) {
|
|
506
|
+
if (elemType.category === 'relation' && !obj.target && !obj.targetAspect) {
|
|
511
507
|
const isCsn = (obj._block && obj._block.$frontend === 'json');
|
|
512
508
|
error( 'type-missing-target', [ obj.type.location, obj ],
|
|
513
|
-
{ '#': isCsn ? 'csn' : 'std', type:
|
|
509
|
+
{ '#': isCsn ? 'csn' : 'std', type: elemType }, {
|
|
514
510
|
// We don't say "use 'association to <target>" because the type could be used
|
|
515
511
|
// in action parameters, etc. as well.
|
|
516
512
|
std: 'The type $(TYPE) can\'t be used directly because it\'s compiler internal',
|
|
@@ -906,10 +902,13 @@ function resolve( model ) {
|
|
|
906
902
|
}
|
|
907
903
|
else if (effectiveType( art )?.elements && !art.$inferred) {
|
|
908
904
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
909
|
-
if (art.type)
|
|
905
|
+
if (art.type) {
|
|
910
906
|
error( 'type-unexpected-structure', [ art.type.location, art ], { '#': 'calc' } );
|
|
911
|
-
|
|
912
|
-
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
error( 'ref-unexpected-structured', [ art.value.location, art ],
|
|
910
|
+
{ '#': 'struct-expr', elemref: art.value } );
|
|
911
|
+
}
|
|
913
912
|
}
|
|
914
913
|
else if (effectiveType( art )?.items && !art.$inferred) {
|
|
915
914
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
@@ -920,7 +919,7 @@ function resolve( model ) {
|
|
|
920
919
|
} );
|
|
921
920
|
}
|
|
922
921
|
else {
|
|
923
|
-
const noTruthyAllowed = [ '
|
|
922
|
+
const noTruthyAllowed = [ 'key', 'virtual' ];
|
|
924
923
|
for (const prop of noTruthyAllowed) {
|
|
925
924
|
if (art[prop]?.val) {
|
|
926
925
|
// probably better than a parse error (which is good for DEFAULT vs calc),
|
|
@@ -1274,7 +1273,7 @@ function resolve( model ) {
|
|
|
1274
1273
|
const text = (item !== last) ? 'sub' : 'std';
|
|
1275
1274
|
error( 'duplicate-key-ref', [ item.location, key ], { '#': text, name }, {
|
|
1276
1275
|
std: 'Foreign key $(NAME) already refers to the same target element',
|
|
1277
|
-
// eslint-disable-next-line @stylistic/
|
|
1276
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1278
1277
|
sub: 'Foreign key $(NAME) already refers to the target element whose sub element is again referred to here',
|
|
1279
1278
|
// TODO: please add ideas for a better text, e.g. to (closed) PR #11325
|
|
1280
1279
|
} );
|
|
@@ -1594,10 +1593,12 @@ function resolve( model ) {
|
|
|
1594
1593
|
const art = type && (type.kind === 'entity' ? type : type.target?._artifact);
|
|
1595
1594
|
if (!art)
|
|
1596
1595
|
return ref; // error already reported via resolvePathItem()
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1596
|
+
if (last.args || last.where || last.cardinality) {
|
|
1597
|
+
const unexpectedFilter = (expected !== 'annotation' && expected !== 'column' &&
|
|
1598
|
+
expected !== 'calc' && 'std') ||
|
|
1599
|
+
isQuasiVirtualAssociation( type ) && 'model-only';
|
|
1600
1600
|
reportUnexpectedArgsAndFilter( last, expected, user, art, unexpectedFilter );
|
|
1601
|
+
}
|
|
1601
1602
|
// TODO: we should have different message-ids for the "last" stuff: adding
|
|
1602
1603
|
// `.item` likely corrects the ref, probably with location at end of ref
|
|
1603
1604
|
return ref;
|
package/lib/compiler/shared.js
CHANGED
|
@@ -991,7 +991,7 @@ function fns( model ) {
|
|
|
991
991
|
}
|
|
992
992
|
else if ($extended && art.elements) {
|
|
993
993
|
warning( 'ref-deprecated-in-extend', [ head.location, user ], { id: head.id },
|
|
994
|
-
// eslint-disable-next-line @stylistic/
|
|
994
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
995
995
|
'In an added column, do not use the table alias $(ID) to refer to source elements' );
|
|
996
996
|
}
|
|
997
997
|
}
|
|
@@ -1489,7 +1489,7 @@ function fns( model ) {
|
|
|
1489
1489
|
// of invisible table aliases; at least one stakeholder uses this,
|
|
1490
1490
|
// so it can't be an error (yet).
|
|
1491
1491
|
message( 'ref-deprecated-self-element', [ ref.path[0].location, user._user ], {},
|
|
1492
|
-
// eslint-disable-next-line @stylistic/
|
|
1492
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1493
1493
|
'Referring to the query\'s own elements here might lead to invalid SQL references; use source elements only' );
|
|
1494
1494
|
return false;
|
|
1495
1495
|
default:
|
|
@@ -1898,7 +1898,7 @@ function fns( model ) {
|
|
|
1898
1898
|
else if (target._artifact && target._artifact !== user._main && user._main.kind === 'entity') {
|
|
1899
1899
|
const last = path[path.length - 1];
|
|
1900
1900
|
warning( 'ref-invalid-backlink', [ last.location, user ], { art: target, id: '$self' },
|
|
1901
|
-
// eslint-disable-next-line @stylistic/
|
|
1901
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1902
1902
|
'The target $(ART) of the association is not the current entity represented by $(ID)' );
|
|
1903
1903
|
}
|
|
1904
1904
|
}
|
|
@@ -1988,11 +1988,11 @@ function fns( model ) {
|
|
|
1988
1988
|
std: 'Can follow association $(ART) only to its foreign key references, not to $(NAME)',
|
|
1989
1989
|
keys: 'Can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
|
|
1990
1990
|
complete: 'The reference must cover a full foreign key reference of association $(ART)',
|
|
1991
|
-
// eslint-disable-next-line @stylistic/
|
|
1991
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1992
1992
|
'self-std': 'In column ref starting with $(ALIAS), we can follow association $(ART) only to its foreign key references, not to $(NAME)',
|
|
1993
|
-
// eslint-disable-next-line @stylistic/
|
|
1993
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1994
1994
|
'self-keys': 'In column ref starting with $(ALIAS), we can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
|
|
1995
|
-
// eslint-disable-next-line @stylistic/
|
|
1995
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1996
1996
|
'self-complete': 'The column reference starting with $(ALIAS) must cover a full foreign key reference of association $(ART)',
|
|
1997
1997
|
} );
|
|
1998
1998
|
// TODO later: mention allowed ones
|
|
@@ -129,7 +129,7 @@ function tweakAssocs( model ) {
|
|
|
129
129
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
130
130
|
info( 'assoc-outside-service', loc, { '#': text, target, service: main._service }, {
|
|
131
131
|
std: 'Association target $(TARGET) is outside any service',
|
|
132
|
-
// eslint-disable-next-line @stylistic/
|
|
132
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
133
133
|
exposed: 'If association is published in service $(SERVICE), its target $(TARGET) is outside any service',
|
|
134
134
|
} );
|
|
135
135
|
}
|
|
@@ -146,7 +146,7 @@ function tweakAssocs( model ) {
|
|
|
146
146
|
if (assoc && assoc.foreignKeys) {
|
|
147
147
|
error( 'rewrite-key-for-unmanaged', [ elem.on.location, elem ],
|
|
148
148
|
{ keyword: 'on', art: assocWithExplicitSpec( assoc ) },
|
|
149
|
-
// eslint-disable-next-line @stylistic/
|
|
149
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
150
150
|
'Do not specify an $(KEYWORD) condition when redirecting the managed association $(ART)' );
|
|
151
151
|
}
|
|
152
152
|
checkIgnoredFilter( elem );
|
|
@@ -349,42 +349,46 @@ function tweakAssocs( model ) {
|
|
|
349
349
|
setLink( fk, '_effectiveType', fk );
|
|
350
350
|
fk.targetElement = copyExpr( orig.targetElement, location );
|
|
351
351
|
if (elem._redirected)
|
|
352
|
-
rewriteKey( elem, fk
|
|
352
|
+
rewriteKey( elem, fk );
|
|
353
353
|
} );
|
|
354
354
|
if (elem.foreignKeys) // Possibly no fk was set
|
|
355
355
|
elem.foreignKeys[$inferred] = 'rewrite';
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
-
function rewriteKey( elem,
|
|
358
|
+
function rewriteKey( elem, fk ) {
|
|
359
|
+
const { targetElement } = fk;
|
|
359
360
|
let projectedKey = null;
|
|
360
361
|
// rewrite along redirection chain
|
|
361
362
|
for (const alias of elem._redirected) {
|
|
362
|
-
if (alias.kind
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
setArtifactLink( targetElement.path[0], null );
|
|
374
|
-
setArtifactLink( targetElement, null );
|
|
363
|
+
if (alias.kind === '$tableAlias') {
|
|
364
|
+
projectedKey = firstProjectionForPath( targetElement.path, 0, alias, null );
|
|
365
|
+
if (projectedKey.elem) {
|
|
366
|
+
const item = targetElement.path[projectedKey.index];
|
|
367
|
+
item.id = projectedKey.elem.name.id;
|
|
368
|
+
if (projectedKey.index > 0)
|
|
369
|
+
targetElement.path.splice(0, projectedKey.index);
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
setArtifactLink( targetElement.path[0], null );
|
|
373
|
+
setArtifactLink( targetElement, null );
|
|
375
374
|
|
|
376
|
-
|
|
375
|
+
const culprit = !elem.target.$inferred && elem.target ||
|
|
377
376
|
elem.value?.path?.[elem.value.path.length - 1] ||
|
|
378
377
|
elem;
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
378
|
+
// TODO: probably better to collect the non-projected foreign keys
|
|
379
|
+
// and have one message for all
|
|
380
|
+
error('rewrite-undefined-key', [ weakLocation( culprit.location ), elem ], {
|
|
381
|
+
'#': 'std',
|
|
382
|
+
id: targetElement.path.map(p => p.id).join('.'),
|
|
383
|
+
target: alias._main,
|
|
384
|
+
name: elem.name.id,
|
|
385
|
+
});
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
// e.g. redirection target is entity that includes original target
|
|
391
|
+
projectedKey = { elem: findTargetElement( alias, targetElement ) };
|
|
388
392
|
}
|
|
389
393
|
}
|
|
390
394
|
|
|
@@ -397,6 +401,24 @@ function tweakAssocs( model ) {
|
|
|
397
401
|
return null;
|
|
398
402
|
}
|
|
399
403
|
|
|
404
|
+
/**
|
|
405
|
+
* Find the target element in the given redirection target.
|
|
406
|
+
* Used to find the target element in entities that include the original
|
|
407
|
+
* target entity.
|
|
408
|
+
*
|
|
409
|
+
* @param redirected
|
|
410
|
+
* @param targetElement
|
|
411
|
+
* @returns {*|null}
|
|
412
|
+
*/
|
|
413
|
+
function findTargetElement( redirected, targetElement ) {
|
|
414
|
+
for (const step of targetElement.path) {
|
|
415
|
+
redirected = redirected.elements?.[step.id];
|
|
416
|
+
if (!redirected)
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
return redirected;
|
|
420
|
+
}
|
|
421
|
+
|
|
400
422
|
// TODO: there is no need to rewrite the on condition of non-leading queries,
|
|
401
423
|
// i.e. we could just have on = {…}
|
|
402
424
|
// TODO: re-check $self rewrite (with managed composition of aspects),
|
|
@@ -714,7 +736,7 @@ function tweakAssocs( model ) {
|
|
|
714
736
|
if (assoc.$errorReported !== 'assoc-unexpected-scope') {
|
|
715
737
|
error( 'assoc-unexpected-scope', [ assoc.value.location, assoc ],
|
|
716
738
|
{ id: assoc.value._artifact.name.id },
|
|
717
|
-
// eslint-disable-next-line @stylistic/
|
|
739
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
718
740
|
'Association $(ID) can\'t be projected because its ON-condition refers to a parameter' );
|
|
719
741
|
assoc.$errorReported = 'assoc-unexpected-scope';
|
|
720
742
|
}
|
|
@@ -747,11 +769,11 @@ function tweakAssocs( model ) {
|
|
|
747
769
|
if (!(elem === item._artifact || // redirection for explicit def
|
|
748
770
|
elem._origin === item._artifact)) {
|
|
749
771
|
const art = assoc._origin;
|
|
750
|
-
// eslint-disable-next-line @stylistic/
|
|
772
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
751
773
|
warning( 'rewrite-shadowed', [ elem.name.location, elem ], { art: art && effectiveType( art ) }, {
|
|
752
|
-
// eslint-disable-next-line @stylistic/
|
|
774
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
753
775
|
std: 'This element is not originally referred to in the ON-condition of association $(ART)',
|
|
754
|
-
// eslint-disable-next-line @stylistic/
|
|
776
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
755
777
|
element: 'This element is not originally referred to in the ON-condition of association $(MEMBER) of $(ART)',
|
|
756
778
|
} );
|
|
757
779
|
}
|
package/lib/compiler/utils.js
CHANGED
|
@@ -150,7 +150,7 @@ function setMemberParent( elem, name, parent, prop ) {
|
|
|
150
150
|
p[prop] = Object.create( null );
|
|
151
151
|
dictAdd( p[prop], name, elem );
|
|
152
152
|
}
|
|
153
|
-
if (parent._outer
|
|
153
|
+
if (parent._outer?.items) // TODO: remove for items, too
|
|
154
154
|
parent = parent._outer;
|
|
155
155
|
setLink( elem, '_parent', parent );
|
|
156
156
|
setLink( elem, '_main', parent._main || parent );
|
|
@@ -484,11 +484,13 @@ function traverseQueryPost( query, simpleOnly, callback ) {
|
|
|
484
484
|
// else: with parse error (`select from <EOF>`, `select distinct from;`)
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
487
|
+
/**
|
|
488
|
+
* Call callback on all queries in dependency order, i.e. starting with query Q
|
|
489
|
+
* 1. sub queries in FROM sources of Q
|
|
490
|
+
* 2. Q itself, ALSO if non-referred query
|
|
491
|
+
* 3. sub queries in ON in FROM of Q
|
|
492
|
+
* 4. sub queries in columns, WHERE, HAVING
|
|
493
|
+
*/
|
|
492
494
|
function traverseQueryExtra( main, callback ) {
|
|
493
495
|
if (!main.$queries)
|
|
494
496
|
return;
|
|
@@ -501,16 +503,7 @@ function traverseQueryExtra( main, callback ) {
|
|
|
501
503
|
if (query._status === 'extra' || query._parent.kind === '$tableAlias')
|
|
502
504
|
continue; // if parent is alias, query is FROM source -> run by traverseQueryPost
|
|
503
505
|
// we are now in the top-level (parent is entity) or a non-referred query (parent is query)
|
|
504
|
-
|
|
505
|
-
// console.log( 'A:', query.name,query._status)
|
|
506
|
-
traverseQueryPost( query, null, (q) => {
|
|
507
|
-
if (q._status !== 'extra') {
|
|
508
|
-
// console.log( 'T:', q.name)
|
|
509
|
-
setLink( q, '_status', 'extra' );
|
|
510
|
-
callback( q );
|
|
511
|
-
}
|
|
512
|
-
// else console.log( 'E:', q.name)
|
|
513
|
-
} );
|
|
506
|
+
traverseQueryPost( query, null, callback );
|
|
514
507
|
}
|
|
515
508
|
}
|
|
516
509
|
|
|
@@ -266,7 +266,7 @@ function xprRewriteFns( model ) {
|
|
|
266
266
|
config.tokenExpr = expr;
|
|
267
267
|
return traverseExpr.STOP === traverseExpr(
|
|
268
268
|
expr, 'annoRewrite', config.target,
|
|
269
|
-
// eslint-disable-next-line @stylistic/
|
|
269
|
+
// eslint-disable-next-line @stylistic/max-len, @stylistic/function-paren-newline
|
|
270
270
|
(e, refCtx) => (rewriteAnnoExpr( e, config, refCtx ) ? traverseExpr.STOP : traverseExpr.SKIP) );
|
|
271
271
|
}
|
|
272
272
|
return false;
|
|
@@ -323,7 +323,7 @@ function xprRewriteFns( model ) {
|
|
|
323
323
|
const filterConfig = { ...config, target: assocTarget, isInFilter: true };
|
|
324
324
|
if (traverseExpr.STOP === traverseExpr(
|
|
325
325
|
step.where, 'filter', step,
|
|
326
|
-
// eslint-disable-next-line @stylistic/
|
|
326
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
327
327
|
(e, ctx) => expr.path && (rewriteGenericAnnoPath( e, filterConfig, ctx ) ? traverseExpr.STOP : traverseExpr.SKIP)
|
|
328
328
|
))
|
|
329
329
|
return true;
|