@sap/cds-compiler 2.10.4 → 2.12.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 +136 -0
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +58 -35
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +16 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +10 -36
- package/lib/api/options.js +17 -8
- package/lib/api/validate.js +30 -3
- package/lib/backends.js +12 -13
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +64 -11
- package/lib/base/messages.js +38 -18
- package/lib/base/model.js +6 -4
- package/lib/base/optionProcessorHelper.js +148 -86
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +14 -5
- package/lib/compiler/base.js +64 -0
- package/lib/compiler/builtins.js +62 -16
- package/lib/compiler/checks.js +34 -10
- package/lib/compiler/definer.js +91 -112
- package/lib/compiler/index.js +30 -30
- package/lib/compiler/propagator.js +8 -4
- package/lib/compiler/resolver.js +279 -63
- package/lib/compiler/shared.js +65 -230
- package/lib/compiler/utils.js +191 -0
- package/lib/edm/annotations/genericTranslation.js +35 -18
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +4 -3
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +61 -59
- package/lib/edm/edmUtils.js +14 -15
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +19 -1
- package/lib/gen/language.tokens +80 -73
- package/lib/gen/languageLexer.interp +27 -1
- package/lib/gen/languageLexer.js +925 -826
- package/lib/gen/languageLexer.tokens +72 -65
- package/lib/gen/languageParser.js +4817 -4102
- package/lib/json/from-csn.js +57 -26
- package/lib/json/to-csn.js +244 -51
- package/lib/language/antlrParser.js +12 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +106 -30
- package/lib/language/language.g4 +200 -70
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +220 -21
- package/lib/main.js +6 -3
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +218 -86
- package/lib/model/csnUtils.js +99 -178
- package/lib/model/enrichCsn.js +84 -43
- package/lib/model/revealInternalProperties.js +25 -8
- package/lib/model/sortViews.js +8 -1
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -18
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +2 -2
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +202 -82
- package/lib/render/toHdbcds.js +194 -135
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +91 -51
- package/lib/render/utils/common.js +24 -5
- package/lib/render/utils/sql.js +6 -4
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +275 -119
- package/lib/transform/db/draft.js +6 -4
- package/lib/transform/db/expansion.js +10 -9
- package/lib/transform/db/flattening.js +23 -8
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +106 -25
- package/lib/transform/db/views.js +485 -0
- package/lib/transform/forHanaNew.js +90 -1036
- package/lib/transform/forOdataNew.js +11 -3
- package/lib/transform/localized.js +5 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +34 -20
- package/lib/transform/translateAssocsToJoins.js +15 -23
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +13 -6
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +55 -27
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
package/lib/compiler/resolver.js
CHANGED
|
@@ -47,25 +47,30 @@ const {
|
|
|
47
47
|
const { dictLocation } = require('../base/location');
|
|
48
48
|
const { searchName, weakLocation } = require('../base/messages');
|
|
49
49
|
const { combinedLocation } = require('../base/location');
|
|
50
|
-
|
|
50
|
+
|
|
51
|
+
const { kindProperties } = require('./base');
|
|
51
52
|
const {
|
|
52
|
-
|
|
53
|
+
pushLink,
|
|
54
|
+
setLink,
|
|
55
|
+
annotationVal,
|
|
53
56
|
augmentPath,
|
|
57
|
+
pathName,
|
|
54
58
|
splitIntoPath,
|
|
55
|
-
|
|
59
|
+
linkToOrigin,
|
|
60
|
+
setMemberParent,
|
|
61
|
+
withAssociation,
|
|
62
|
+
storeExtension,
|
|
63
|
+
dependsOn,
|
|
64
|
+
dependsOnSilent,
|
|
65
|
+
} = require('./utils');
|
|
56
66
|
|
|
57
67
|
const detectCycles = require('./cycle-detector');
|
|
58
68
|
const layers = require('./moduleLayers');
|
|
59
69
|
|
|
60
|
-
const {
|
|
61
|
-
kindProperties, fns, setLink, linkToOrigin, setMemberParent, withAssociation, storeExtension,
|
|
62
|
-
dependsOn, dependsOnSilent,
|
|
63
|
-
} = require('./shared');
|
|
64
|
-
|
|
65
70
|
const annotationPriorities = {
|
|
66
71
|
define: 1, extend: 2, annotate: 2, edmx: 3,
|
|
67
72
|
};
|
|
68
|
-
|
|
73
|
+
const $inferred = Symbol.for('cds.$inferred');
|
|
69
74
|
|
|
70
75
|
// Export function of this file. Resolve type references in augmented CSN
|
|
71
76
|
// `model`. If the model has a property argument `messages`, do not throw
|
|
@@ -73,21 +78,21 @@ const annotationPriorities = {
|
|
|
73
78
|
// that property (should be a vector).
|
|
74
79
|
function resolve( model ) {
|
|
75
80
|
const { options } = model;
|
|
76
|
-
// Get shared
|
|
81
|
+
// Get shared functionality and the message function:
|
|
82
|
+
const {
|
|
83
|
+
info, warning, error, message,
|
|
84
|
+
} = model.$messageFunctions;
|
|
77
85
|
const {
|
|
78
86
|
resolvePath,
|
|
79
87
|
resolveTypeArguments,
|
|
80
88
|
defineAnnotations,
|
|
81
89
|
attachAndEmitValidNames,
|
|
82
|
-
} = fns( model, environment );
|
|
83
|
-
const {
|
|
84
|
-
info, warning, error, message,
|
|
85
|
-
} = model.$messageFunctions;
|
|
86
|
-
const {
|
|
87
90
|
initArtifact,
|
|
88
91
|
lateExtensions,
|
|
89
92
|
projectionAncestor,
|
|
90
|
-
} =
|
|
93
|
+
} = model.$functions;
|
|
94
|
+
model.$volatileFunctions.environment = environment;
|
|
95
|
+
|
|
91
96
|
/** @type {any} may also be a boolean */
|
|
92
97
|
let newAutoExposed = [];
|
|
93
98
|
|
|
@@ -242,6 +247,7 @@ function resolve( model ) {
|
|
|
242
247
|
const chain = [];
|
|
243
248
|
while (art && !('_effectiveType' in art) &&
|
|
244
249
|
(art.type || art._origin || art.value && art.value.path) &&
|
|
250
|
+
// TODO: really stop at art.enum?
|
|
245
251
|
!art.target && !art.enum && !art.elements && !art.items) {
|
|
246
252
|
chain.push( art );
|
|
247
253
|
setProp( art, '_effectiveType', 0 ); // initial setting in case of cycles
|
|
@@ -273,13 +279,17 @@ function resolve( model ) {
|
|
|
273
279
|
// collect the "latest" cardinality (calculate lazyly if necessary)
|
|
274
280
|
let cardinality = art.cardinality ||
|
|
275
281
|
art._effectiveType && (() => getCardinality( art._effectiveType ));
|
|
282
|
+
let prev = art;
|
|
276
283
|
for (const a of chain) {
|
|
277
284
|
if (a.cardinality)
|
|
278
285
|
cardinality = a.cardinality;
|
|
279
286
|
if (a.expand && expandFromColumns( a, art, cardinality ) ||
|
|
280
287
|
art.target && redirectImplicitly( a, art ) ||
|
|
281
|
-
art.elements && expandElements( a, art, eType )
|
|
288
|
+
art.elements && expandElements( a, art, eType ) ||
|
|
289
|
+
art.items && expandItems( a, art, eType ))
|
|
282
290
|
art = a;
|
|
291
|
+
else if (art.enum && expandEnum( a, prev ))
|
|
292
|
+
prev = a; // do not set art - effective type is base
|
|
283
293
|
setProp( a, '_effectiveType', art );
|
|
284
294
|
}
|
|
285
295
|
}
|
|
@@ -351,13 +361,13 @@ function resolve( model ) {
|
|
|
351
361
|
while (struct.kind === 'element')
|
|
352
362
|
struct = struct._parent;
|
|
353
363
|
if (struct.kind === 'select') {
|
|
354
|
-
message( '
|
|
364
|
+
message( 'type-unexpected-typeof', [ ref.location, user ],
|
|
355
365
|
{ keyword: 'type of', '#': struct.kind } );
|
|
356
366
|
// we actually refer to an element in _combined; TODO: return null if
|
|
357
367
|
// not configurable; would produce illegal CSN with sub queries in FROM
|
|
358
368
|
}
|
|
359
369
|
else if (struct !== user._main) {
|
|
360
|
-
message( '
|
|
370
|
+
message( 'type-unexpected-typeof', [ ref.location, user ],
|
|
361
371
|
{ keyword: 'type of', '#': struct.kind } );
|
|
362
372
|
return setProp( ref, '_artifact', null );
|
|
363
373
|
}
|
|
@@ -405,7 +415,8 @@ function resolve( model ) {
|
|
|
405
415
|
for (const view of resolveChain.reverse()) {
|
|
406
416
|
if (view._status !== '_query' ) { // not already resolved
|
|
407
417
|
setProp( view, '_status', '_query' );
|
|
408
|
-
|
|
418
|
+
// must be run in order “sub query in FROM first”:
|
|
419
|
+
traverseQueryPost( view.query, null, populateQuery );
|
|
409
420
|
if (view.elements$) // specified elements
|
|
410
421
|
mergeSpecifiedElements( view );
|
|
411
422
|
if (!view.$entity) {
|
|
@@ -494,7 +505,8 @@ function resolve( model ) {
|
|
|
494
505
|
});
|
|
495
506
|
}
|
|
496
507
|
forEachGeneric( { elements: alias.elements }, 'elements', ( elem, name ) => {
|
|
497
|
-
|
|
508
|
+
if (elem.$duplicates !== true)
|
|
509
|
+
dictAddArray( query._combined, name, elem, null ); // not dictAdd()
|
|
498
510
|
});
|
|
499
511
|
}
|
|
500
512
|
}
|
|
@@ -535,6 +547,23 @@ function resolve( model ) {
|
|
|
535
547
|
setMemberParent( key, name, elem ); // TODO: set _block here if not present?
|
|
536
548
|
}
|
|
537
549
|
|
|
550
|
+
function expandItems( art, origin, eType ) {
|
|
551
|
+
if (!enableExpandElements || art.items)
|
|
552
|
+
return false;
|
|
553
|
+
if (isInParents( art, eType )) {
|
|
554
|
+
art.items = 0; // circular
|
|
555
|
+
return true;
|
|
556
|
+
}
|
|
557
|
+
const ref = art.type || art.value || art.name;
|
|
558
|
+
const location = ref && ref.location || art.location;
|
|
559
|
+
art.items = { $inferred: 'expand-element', location };
|
|
560
|
+
setProp( art.items, '_outer', art );
|
|
561
|
+
setProp( art.items, '_origin', origin.items );
|
|
562
|
+
if (!art.$expand)
|
|
563
|
+
art.$expand = 'origin'; // if value stays, elements won't appear in CSN
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
566
|
+
|
|
538
567
|
function expandElements( art, struct, eType ) {
|
|
539
568
|
if (!enableExpandElements)
|
|
540
569
|
return false;
|
|
@@ -566,7 +595,28 @@ function resolve( model ) {
|
|
|
566
595
|
// member property):
|
|
567
596
|
if (!art.$expand)
|
|
568
597
|
art.$expand = 'origin'; // if value stays, elements won't appear in CSN
|
|
569
|
-
// TODO: have some art.elements[SYM.$inferred] = 'expand-
|
|
598
|
+
// TODO: have some art.elements[SYM.$inferred] = 'expand-element';
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function expandEnum( art, origin ) {
|
|
603
|
+
if (!enableExpandElements || art.enum)
|
|
604
|
+
return false;
|
|
605
|
+
const ref = art.type || art.value || art.name;
|
|
606
|
+
const location = weakLocation( ref && ref.location || art.location );
|
|
607
|
+
art.enum = Object.create(null);
|
|
608
|
+
for (const name in origin.enum) {
|
|
609
|
+
const orig = origin.enum[name];
|
|
610
|
+
linkToOrigin( orig, name, art, 'enum', location, true )
|
|
611
|
+
// or should we use orig.location? - TODO: try to find test to see message
|
|
612
|
+
.$inferred = 'expand-element';
|
|
613
|
+
}
|
|
614
|
+
// Set elements expansion status (the if condition is always true, as no
|
|
615
|
+
// elements expansion will take place on artifact with existing other
|
|
616
|
+
// member property):
|
|
617
|
+
if (!art.$expand)
|
|
618
|
+
art.$expand = 'origin'; // if value stays, elements won't appear in CSN
|
|
619
|
+
art.enum[$inferred] = 'expand-element';
|
|
570
620
|
return true;
|
|
571
621
|
}
|
|
572
622
|
|
|
@@ -604,7 +654,7 @@ function resolve( model ) {
|
|
|
604
654
|
// not counting propagated ones; set up to the definition (main artifact)
|
|
605
655
|
// (only set with anno on $inferred elem)
|
|
606
656
|
// Usage according to CSN flavor:
|
|
607
|
-
// - gensrc: do not render
|
|
657
|
+
// - gensrc: do not render inferred elements (including expanded elements),
|
|
608
658
|
// collect annotate statements with value 'annotate'
|
|
609
659
|
// - client: do not render expanded sub elements if artifact/member is no type, has a type,
|
|
610
660
|
// has $expand = 'origin', and all its _origin also have $expand = 'origin'
|
|
@@ -672,35 +722,69 @@ function resolve( model ) {
|
|
|
672
722
|
else if (exposed.length === 1) {
|
|
673
723
|
target = exposed[0];
|
|
674
724
|
}
|
|
675
|
-
else {
|
|
676
|
-
|
|
677
|
-
|
|
725
|
+
else if (elem === assoc) {
|
|
726
|
+
// `assoc: Association to ModelEntity`: user-provided target is to be auto-redirected
|
|
727
|
+
warning( 'type-ambiguous-target',
|
|
728
|
+
[ elem.target.location, elem ],
|
|
678
729
|
{
|
|
679
730
|
target,
|
|
680
|
-
|
|
681
|
-
art: definitionScope( target ),
|
|
731
|
+
// art: definitionScope( target ), - TODO extra debug info in message
|
|
682
732
|
sorted_arts: exposed,
|
|
683
|
-
'#': ( elemScope !== true ? 'std' : 'scoped' ),
|
|
684
733
|
}, {
|
|
685
734
|
// eslint-disable-next-line max-len
|
|
686
|
-
std: '
|
|
735
|
+
std: 'Replace target $(TARGET) by one of $(SORTED_ARTS); can\'t auto-redirect this association if multiple projections exist in this service',
|
|
687
736
|
// eslint-disable-next-line max-len
|
|
688
|
-
|
|
737
|
+
two: 'Replace target $(TARGET) by $(SORTED_ARTS) or $(SECOND); can\'t auto-redirect this association if multiple projections exist in this service',
|
|
689
738
|
});
|
|
739
|
+
// continuation semantics: no auto-redirection
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
// referred (and probably inferred) assoc (without a user-provided target at that place)
|
|
743
|
+
// HINT: consider bin/cdsv2m.js when changing the following message text
|
|
744
|
+
// No grouped and sub messages yet (TODO v3): mention at all target places with all assocs
|
|
745
|
+
const withAnno = annotationVal( exposed[0]['@cds.redirection.target'] );
|
|
746
|
+
for (const proj of exposed) {
|
|
747
|
+
// TODO: def-ambiguous-target (just v3, as the current is infamous and used in options),
|
|
748
|
+
message( 'redirected-implicitly-ambiguous',
|
|
749
|
+
[ weakLocation( proj.location ), proj ],
|
|
750
|
+
{
|
|
751
|
+
'#': withAnno && 'justOne',
|
|
752
|
+
target,
|
|
753
|
+
art: elem,
|
|
754
|
+
// art: definitionScope( target ), - TODO extra debug info in message
|
|
755
|
+
anno: 'cds.redirection.target',
|
|
756
|
+
sorted_arts: exposed,
|
|
757
|
+
}, {
|
|
758
|
+
// eslint-disable-next-line max-len
|
|
759
|
+
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',
|
|
760
|
+
// eslint-disable-next-line max-len
|
|
761
|
+
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',
|
|
762
|
+
// eslint-disable-next-line max-len
|
|
763
|
+
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',
|
|
764
|
+
} );
|
|
765
|
+
}
|
|
690
766
|
// continuation semantics: no implicit redirections
|
|
691
767
|
}
|
|
692
768
|
}
|
|
693
769
|
if (elem.target) { // redirection for Association to / Composition of
|
|
694
770
|
if (elem.target._artifact === target) // no change (due to no implicit redirection)
|
|
695
771
|
return true;
|
|
772
|
+
const type = resolvePath( elem.type, 'type', elem ); // cds.Association or cds.Composition
|
|
696
773
|
const origin = {
|
|
697
|
-
kind: elem.kind,
|
|
774
|
+
kind: elem.kind, // necessary for rewrite, '$user-provided' would be best
|
|
698
775
|
name: elem.name,
|
|
776
|
+
type: {
|
|
777
|
+
path: [ { id: type.name.absolute, location: elem.type.location } ],
|
|
778
|
+
scope: 'global',
|
|
779
|
+
location: elem.type.location,
|
|
780
|
+
},
|
|
699
781
|
target: elem.target,
|
|
700
782
|
$inferred: 'REDIRECTED',
|
|
701
783
|
location: elem.target.location,
|
|
702
784
|
};
|
|
703
785
|
setLink( elem, origin, '_origin' );
|
|
786
|
+
setLink( elem.type, type, '_artifact' );
|
|
787
|
+
setLink( origin, elem, '_outer' );
|
|
704
788
|
setLink( origin, elem._parent, '_parent' );
|
|
705
789
|
if (elem._main) // remark: the param `elem` can also be a type
|
|
706
790
|
setLink( origin, elem._main, '_main' );
|
|
@@ -708,11 +792,11 @@ function resolve( model ) {
|
|
|
708
792
|
setLink( origin, elem._block, '_block' );
|
|
709
793
|
if (elem.foreignKeys) {
|
|
710
794
|
origin.foreignKeys = elem.foreignKeys;
|
|
711
|
-
delete elem.foreignKeys;
|
|
795
|
+
delete elem.foreignKeys; // will be rewritten
|
|
712
796
|
}
|
|
713
797
|
if (elem.on) {
|
|
714
798
|
origin.on = elem.on;
|
|
715
|
-
delete elem.on;
|
|
799
|
+
delete elem.on; // will be rewritten
|
|
716
800
|
}
|
|
717
801
|
}
|
|
718
802
|
elem.target = {
|
|
@@ -771,10 +855,7 @@ function resolve( model ) {
|
|
|
771
855
|
target._descendants[service.name.absolute] ||
|
|
772
856
|
[],
|
|
773
857
|
elemScope, target );
|
|
774
|
-
const preferred = descendants.filter( ( d )
|
|
775
|
-
const anno = d['@cds.redirection.target'];
|
|
776
|
-
return anno && (anno.val === undefined || anno.val );
|
|
777
|
-
} );
|
|
858
|
+
const preferred = descendants.filter( d => annotationVal( d['@cds.redirection.target'] ) );
|
|
778
859
|
const exposed = preferred.length ? preferred : descendants;
|
|
779
860
|
if (exposed.length < 2)
|
|
780
861
|
return exposed || [];
|
|
@@ -813,7 +894,7 @@ function resolve( model ) {
|
|
|
813
894
|
// Need to filter out auto-exposed, otherwise the behavior is
|
|
814
895
|
// processing-order dependent (not storing the autoexposed in
|
|
815
896
|
// _descendents would only be an alternative w/o recompilation)
|
|
816
|
-
return descendants.filter( d => !d['@cds.autoexposed'] );
|
|
897
|
+
return descendants.filter( d => !annotationVal( d['@cds.autoexposed'] ) );
|
|
817
898
|
}
|
|
818
899
|
// try scope as target first, even if it has @cds.redirection.target: false
|
|
819
900
|
if (isDirectProjection( elemScope, target ))
|
|
@@ -944,6 +1025,7 @@ function resolve( model ) {
|
|
|
944
1025
|
$inferred: 'autoexposed',
|
|
945
1026
|
'@cds.autoexposed': {
|
|
946
1027
|
name: { path: [ { id: 'cds.autoexposed', location } ], location },
|
|
1028
|
+
$inferred: 'autoexposed',
|
|
947
1029
|
},
|
|
948
1030
|
};
|
|
949
1031
|
// TODO: do we need to tag the generated entity with elemScope = 'auto'?
|
|
@@ -1027,7 +1109,6 @@ function resolve( model ) {
|
|
|
1027
1109
|
// or use userQuery( query ) in the following, too?
|
|
1028
1110
|
setMemberParent( col, `.${ q.$inlines.length }`, query );
|
|
1029
1111
|
initFromColumns( query, col.inline, col );
|
|
1030
|
-
continue;
|
|
1031
1112
|
}
|
|
1032
1113
|
else if (!col.$replacement) {
|
|
1033
1114
|
const id = ensureColumnName( col, query );
|
|
@@ -1441,8 +1522,9 @@ function resolve( model ) {
|
|
|
1441
1522
|
}
|
|
1442
1523
|
// Resolve projections/views
|
|
1443
1524
|
// if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );
|
|
1444
|
-
|
|
1445
|
-
|
|
1525
|
+
|
|
1526
|
+
if (art.$queries)
|
|
1527
|
+
art.$queries.forEach( resolveQuery );
|
|
1446
1528
|
|
|
1447
1529
|
if (obj.type || obj._origin || obj.value && obj.value.path || obj.elements) // typed artifacts
|
|
1448
1530
|
effectiveType(obj); // set _effectiveType if appropriate, (future?): copy elems if extended
|
|
@@ -1520,9 +1602,17 @@ function resolve( model ) {
|
|
|
1520
1602
|
}
|
|
1521
1603
|
}
|
|
1522
1604
|
|
|
1523
|
-
|
|
1605
|
+
/**
|
|
1606
|
+
* @param {XSN.Artifact} art
|
|
1607
|
+
* @param {XSN.Extension[]} [extensions]
|
|
1608
|
+
* @param {string} [prop]
|
|
1609
|
+
* @param {string} [name]
|
|
1610
|
+
* @param {object} [parent]
|
|
1611
|
+
* @param {string} [kind]
|
|
1612
|
+
*/
|
|
1613
|
+
function annotateMembers( art, extensions, prop, name, parent, kind ) {
|
|
1524
1614
|
const showMsg = !art && parent && parent.kind !== 'annotate';
|
|
1525
|
-
if (!art && extensions.length) {
|
|
1615
|
+
if (!art && extensions && extensions.length) {
|
|
1526
1616
|
if (Array.isArray( parent ))
|
|
1527
1617
|
return;
|
|
1528
1618
|
const parentExt = extensionFor(parent);
|
|
@@ -1538,7 +1628,7 @@ function resolve( model ) {
|
|
|
1538
1628
|
}
|
|
1539
1629
|
}
|
|
1540
1630
|
|
|
1541
|
-
for (const ext of extensions) {
|
|
1631
|
+
for (const ext of extensions || []) {
|
|
1542
1632
|
if ('_artifact' in ext.name) // already applied
|
|
1543
1633
|
continue;
|
|
1544
1634
|
setProp( ext.name, '_artifact', art );
|
|
@@ -1658,6 +1748,8 @@ function resolve( model ) {
|
|
|
1658
1748
|
location,
|
|
1659
1749
|
$inferred: 'expand-param',
|
|
1660
1750
|
};
|
|
1751
|
+
setProp( art.returns, '_parent', art );
|
|
1752
|
+
setProp( art.returns, '_main', art._main || art );
|
|
1661
1753
|
setProp( art.returns, '_origin', origin.returns );
|
|
1662
1754
|
}
|
|
1663
1755
|
}
|
|
@@ -1765,7 +1857,7 @@ function resolve( model ) {
|
|
|
1765
1857
|
[ mergeSource.name.location, art ], { code: '...' } );
|
|
1766
1858
|
return;
|
|
1767
1859
|
}
|
|
1768
|
-
mergeTarget.val
|
|
1860
|
+
mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
|
|
1769
1861
|
}
|
|
1770
1862
|
}
|
|
1771
1863
|
});
|
|
@@ -1785,7 +1877,7 @@ function resolve( model ) {
|
|
|
1785
1877
|
[ mergeSource.name.location, art ], { code: '...' } );
|
|
1786
1878
|
return mergeTarget;
|
|
1787
1879
|
}
|
|
1788
|
-
mergeTarget.val
|
|
1880
|
+
mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
|
|
1789
1881
|
layer = layers.layer( mergeSource._block );
|
|
1790
1882
|
delete layerAnnos[(layer) ? layer.realname : ''];
|
|
1791
1883
|
pos = findEllipsis( mergeTarget );
|
|
@@ -1796,6 +1888,89 @@ function resolve( model ) {
|
|
|
1796
1888
|
return mergeTarget;
|
|
1797
1889
|
}
|
|
1798
1890
|
|
|
1891
|
+
function mergeArrayValues( previousValue, arraySpec ) {
|
|
1892
|
+
let prevPos = 0;
|
|
1893
|
+
const result = [];
|
|
1894
|
+
for (const item of arraySpec) {
|
|
1895
|
+
const ell = item && item.literal === 'token' && item.val === '...';
|
|
1896
|
+
if (!ell) {
|
|
1897
|
+
result.push( item );
|
|
1898
|
+
}
|
|
1899
|
+
else {
|
|
1900
|
+
let upToSpec = item.upTo && checkUpToSpec( item.upTo, true );
|
|
1901
|
+
while (prevPos < previousValue.length) {
|
|
1902
|
+
const prevItem = previousValue[prevPos++];
|
|
1903
|
+
result.push( prevItem );
|
|
1904
|
+
if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
|
|
1905
|
+
upToSpec = false;
|
|
1906
|
+
break;
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
if (upToSpec) { // non-matched UP TO
|
|
1910
|
+
warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
|
|
1911
|
+
'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
return result;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
function checkUpToSpec( upToSpec, trueIfFullUpTo ) {
|
|
1919
|
+
const { literal } = upToSpec;
|
|
1920
|
+
if (trueIfFullUpTo !== true) { // inside struct of UP TO
|
|
1921
|
+
if (![ 'struct', 'array' ].includes( literal ))
|
|
1922
|
+
return true;
|
|
1923
|
+
}
|
|
1924
|
+
else if (literal === 'struct') {
|
|
1925
|
+
return Object.values( upToSpec.struct ).every( checkUpToSpec );
|
|
1926
|
+
}
|
|
1927
|
+
else if (![ 'array', 'boolean', 'null' ].includes( literal )) {
|
|
1928
|
+
return true;
|
|
1929
|
+
}
|
|
1930
|
+
error( null, [ upToSpec.location, art ],
|
|
1931
|
+
{ anno: annoName, code: '... up to', '#': literal },
|
|
1932
|
+
{
|
|
1933
|
+
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
1934
|
+
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
1935
|
+
// eslint-disable-next-line max-len
|
|
1936
|
+
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
1937
|
+
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
1938
|
+
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
1939
|
+
} );
|
|
1940
|
+
return false;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
function equalUpTo( previousItem, upToSpec ) {
|
|
1944
|
+
if (!previousItem)
|
|
1945
|
+
return false;
|
|
1946
|
+
if ('val' in upToSpec) {
|
|
1947
|
+
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
|
|
1948
|
+
return true;
|
|
1949
|
+
const typeUpTo = typeof upToSpec.val;
|
|
1950
|
+
const typePrev = typeof previousItem.val;
|
|
1951
|
+
if (typeUpTo === 'number')
|
|
1952
|
+
return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
|
|
1953
|
+
if (typePrev === 'number')
|
|
1954
|
+
return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
|
|
1955
|
+
}
|
|
1956
|
+
else if (upToSpec.path) {
|
|
1957
|
+
return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
|
|
1958
|
+
}
|
|
1959
|
+
else if (upToSpec.sym) {
|
|
1960
|
+
return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
|
|
1961
|
+
}
|
|
1962
|
+
else if (upToSpec.struct && previousItem.struct) {
|
|
1963
|
+
return Object.entries( upToSpec.struct )
|
|
1964
|
+
.every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
|
|
1965
|
+
}
|
|
1966
|
+
return false;
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
function normalizeRef( node ) { // see to-csn.js
|
|
1970
|
+
const ref = pathName( node.path );
|
|
1971
|
+
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1799
1974
|
function removeEllipsis(a, pos = findEllipsis( a )) {
|
|
1800
1975
|
let count = 0;
|
|
1801
1976
|
while (a.literal === 'array' && pos > -1) {
|
|
@@ -1868,7 +2043,7 @@ function resolve( model ) {
|
|
|
1868
2043
|
function resolveQuery( query ) {
|
|
1869
2044
|
if (!query._main) // parse error
|
|
1870
2045
|
return;
|
|
1871
|
-
|
|
2046
|
+
traverseQueryPost( query, null, populateQuery );
|
|
1872
2047
|
forEachGeneric( query, '$tableAliases', ( alias ) => {
|
|
1873
2048
|
// console.log( info( null, [alias.location,alias], 'SQA:' ).toString() );
|
|
1874
2049
|
if (alias.kind === 'mixin')
|
|
@@ -2009,6 +2184,7 @@ function resolve( model ) {
|
|
|
2009
2184
|
dependsOnSilent(art, key);
|
|
2010
2185
|
}
|
|
2011
2186
|
});
|
|
2187
|
+
obj.foreignKeys[$inferred] = 'keys';
|
|
2012
2188
|
}
|
|
2013
2189
|
|
|
2014
2190
|
function addForeignKeyNavigations( art ) {
|
|
@@ -2177,8 +2353,6 @@ function resolve( model ) {
|
|
|
2177
2353
|
// Only top-level queries and sub queries in FROM
|
|
2178
2354
|
|
|
2179
2355
|
function rewriteSimple( art ) {
|
|
2180
|
-
// If we have a proper seperation of view elements and elements of the
|
|
2181
|
-
// primary query, we can delete this function.
|
|
2182
2356
|
// return;
|
|
2183
2357
|
if (!art.includes && !art.query) {
|
|
2184
2358
|
// console.log(message( null, art.location, art, {target:art._target},
|
|
@@ -2191,7 +2365,7 @@ function resolve( model ) {
|
|
|
2191
2365
|
}
|
|
2192
2366
|
|
|
2193
2367
|
function rewriteView( view ) {
|
|
2194
|
-
|
|
2368
|
+
traverseQueryExtra( view, ( query ) => {
|
|
2195
2369
|
forEachGeneric( query, 'elements', rewriteAssociation );
|
|
2196
2370
|
} );
|
|
2197
2371
|
if (view.includes) // entities with structure includes:
|
|
@@ -2199,6 +2373,7 @@ function resolve( model ) {
|
|
|
2199
2373
|
}
|
|
2200
2374
|
|
|
2201
2375
|
// Check explicit ON / keys with REDIRECTED TO
|
|
2376
|
+
// TODO: run on all queries, but this is potentially incompatible
|
|
2202
2377
|
function rewriteViewCheck( view ) {
|
|
2203
2378
|
traverseQueryPost( view.query, false, ( query ) => {
|
|
2204
2379
|
forEachGeneric( query, 'elements', rewriteAssociationCheck );
|
|
@@ -2424,7 +2599,7 @@ function resolve( model ) {
|
|
|
2424
2599
|
resolveExpr( cond, rewriteExpr, elem, nav.tableAlias );
|
|
2425
2600
|
}
|
|
2426
2601
|
else {
|
|
2427
|
-
// TODO: support that
|
|
2602
|
+
// TODO: support that, now that the ON condition is rewritten in the right order
|
|
2428
2603
|
error( null, [ elem.value.location, elem ],
|
|
2429
2604
|
'Selecting unmanaged associations from a sub query is not supported' );
|
|
2430
2605
|
}
|
|
@@ -2593,6 +2768,8 @@ function resolve( model ) {
|
|
|
2593
2768
|
}
|
|
2594
2769
|
|
|
2595
2770
|
function resolveExpr( expr, expected, user, extDict, expandOrInline) {
|
|
2771
|
+
// TODO: when we have rewritten the resolvePath functions,
|
|
2772
|
+
// define a traverseExpr() in ./utils.js
|
|
2596
2773
|
// TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`
|
|
2597
2774
|
if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
|
|
2598
2775
|
return;
|
|
@@ -2629,7 +2806,7 @@ function resolve( model ) {
|
|
|
2629
2806
|
else if (expr.query) {
|
|
2630
2807
|
const { query } = expr;
|
|
2631
2808
|
if (query.kind || query._leadingQuery) { // UNION has _leadingQuery
|
|
2632
|
-
traverseQueryPost( query, false, resolveQuery );
|
|
2809
|
+
// traverseQueryPost( query, false, resolveQuery );
|
|
2633
2810
|
}
|
|
2634
2811
|
else {
|
|
2635
2812
|
error( 'expr-no-subquery', [ expr.location, user ], {},
|
|
@@ -2640,9 +2817,10 @@ function resolve( model ) {
|
|
|
2640
2817
|
const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
|
|
2641
2818
|
args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
|
|
2642
2819
|
}
|
|
2643
|
-
if (expr.suffix &&
|
|
2820
|
+
if (expr.suffix && isDeprecatedEnabled( options )) {
|
|
2644
2821
|
const { location } = expr.suffix[0] || expr;
|
|
2645
|
-
error( null, [ location, user ],
|
|
2822
|
+
error( null, [ location, user ], { prop: 'deprecated' },
|
|
2823
|
+
'Window functions are not supported if $(PROP) options are set' );
|
|
2646
2824
|
}
|
|
2647
2825
|
if (expr.suffix)
|
|
2648
2826
|
expr.suffix.forEach( s => s && resolveExpr( s, expected, user, extDict ) );
|
|
@@ -2827,11 +3005,11 @@ function navProjection( navigation, preferred ) {
|
|
|
2827
3005
|
: navigation._projections[0] || null;
|
|
2828
3006
|
}
|
|
2829
3007
|
|
|
2830
|
-
// Query tree post-order traversal - called for everything which
|
|
3008
|
+
// Query tree post-order traversal - called for everything which contributes to the query
|
|
3009
|
+
// i.e. is necessary to calculate the elements of the query
|
|
2831
3010
|
// except "real ones": operands of UNION etc, JOIN with ON, and sub queries in FROM
|
|
3011
|
+
// NOTE: does not run on non-referred sub queries! Consider using ‹main›.$queries instead!
|
|
2832
3012
|
function traverseQueryPost( query, simpleOnly, callback ) {
|
|
2833
|
-
while (Array.isArray(query)) // query in parentheses, TODO: remove
|
|
2834
|
-
query = query[0];
|
|
2835
3013
|
if (!query) // parser error
|
|
2836
3014
|
return;
|
|
2837
3015
|
if (!query.op) { // in FROM (not JOIN)
|
|
@@ -2851,13 +3029,51 @@ function traverseQueryPost( query, simpleOnly, callback ) {
|
|
|
2851
3029
|
// console.log('FE:')
|
|
2852
3030
|
}
|
|
2853
3031
|
else if (query.args) { // JOIN, UNION, INTERSECT
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
3032
|
+
if (!query.join && simpleOnly == null) {
|
|
3033
|
+
// enough for elements: traverse only first args for UNION/INTERSECT
|
|
3034
|
+
// TODO: we might use this also when we do not rewrite associations
|
|
3035
|
+
// in non-referred sub queries
|
|
3036
|
+
traverseQueryPost( query.args[0], simpleOnly, callback );
|
|
3037
|
+
}
|
|
3038
|
+
else {
|
|
3039
|
+
for (const q of query.args)
|
|
3040
|
+
traverseQueryPost( q, simpleOnly, callback );
|
|
3041
|
+
// The ON condition has to be traversed extra, because it must be evaluated
|
|
3042
|
+
// after the complete FROM has been traversed. It is also not necessary to
|
|
3043
|
+
// evaluate it in populateQuery().
|
|
3044
|
+
}
|
|
2859
3045
|
}
|
|
2860
3046
|
// else: with parse error (`select from <EOF>`, `select distinct from;`)
|
|
2861
3047
|
}
|
|
2862
3048
|
|
|
3049
|
+
// Call callback on all queries in dependency order, i.e. starting with query Q
|
|
3050
|
+
// 1. sub queries in FROM sources of Q
|
|
3051
|
+
// 2. Q itself, except if non-referred query, but with right UNION parts
|
|
3052
|
+
// 3. sub queries in ON in FROM of Q
|
|
3053
|
+
// 4. sub queries in columns, WHERE, HAVING
|
|
3054
|
+
function traverseQueryExtra( main, callback ) {
|
|
3055
|
+
if (!main.$queries)
|
|
3056
|
+
return;
|
|
3057
|
+
// with a top-level UNION, $queries[0] is just the left
|
|
3058
|
+
traverseQueryPost( main.query, false, (q) => { // also with right of UNION (to be compatible)
|
|
3059
|
+
setProp( q, '_status', 'extra' );
|
|
3060
|
+
callback( q );
|
|
3061
|
+
} );
|
|
3062
|
+
for (const query of main.$queries.slice(1)) {
|
|
3063
|
+
if (query._status === 'extra' || query._parent.kind === '$tableAlias')
|
|
3064
|
+
continue; // if parent is alias, query is FROM source -> run by traverseQueryPost
|
|
3065
|
+
// we are now in the top-level (parent is entity) or a non-referred query (parent is query)
|
|
3066
|
+
setProp( query, '_status', 'extra' ); // do not call callback() in non-referred query
|
|
3067
|
+
// console.log( 'A:', query.name,query._status)
|
|
3068
|
+
traverseQueryPost( query, null, (q) => {
|
|
3069
|
+
if (q._status !== 'extra') {
|
|
3070
|
+
// console.log( 'T:', q.name)
|
|
3071
|
+
setProp( q, '_status', 'extra' );
|
|
3072
|
+
callback( q );
|
|
3073
|
+
}
|
|
3074
|
+
// else console.log( 'E:', q.name)
|
|
3075
|
+
} );
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
|
|
2863
3079
|
module.exports = resolve;
|