@sap/cds-compiler 4.4.4 → 4.6.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 +88 -0
- package/bin/cdsc.js +18 -11
- package/bin/cdsv2m.js +7 -5
- package/doc/CHANGELOG_BETA.md +22 -0
- package/lib/api/main.js +306 -144
- package/lib/api/options.js +18 -6
- package/lib/api/validate.js +1 -1
- package/lib/base/message-registry.js +45 -10
- package/lib/base/messages.js +33 -16
- package/lib/base/model.js +4 -0
- package/lib/base/optionProcessorHelper.js +45 -176
- package/lib/checks/annotationsOData.js +49 -0
- package/lib/checks/elements.js +32 -34
- package/lib/checks/enricher.js +39 -3
- package/lib/checks/validator.js +8 -7
- package/lib/compiler/assert-consistency.js +40 -17
- package/lib/compiler/builtins.js +30 -53
- package/lib/compiler/checks.js +46 -14
- package/lib/compiler/cycle-detector.js +1 -4
- package/lib/compiler/define.js +35 -10
- package/lib/compiler/extend.js +21 -7
- package/lib/compiler/generate.js +3 -0
- package/lib/compiler/populate.js +5 -1
- package/lib/compiler/propagator.js +46 -9
- package/lib/compiler/resolve.js +94 -35
- package/lib/compiler/shared.js +60 -33
- package/lib/compiler/tweak-assocs.js +188 -92
- package/lib/compiler/utils.js +11 -1
- package/lib/edm/annotations/edmJson.js +41 -66
- package/lib/edm/annotations/genericTranslation.js +27 -9
- package/lib/edm/annotations/preprocessAnnotations.js +2 -3
- package/lib/edm/csn2edm.js +28 -11
- package/lib/edm/edmInboundChecks.js +58 -15
- package/lib/edm/edmPreprocessor.js +12 -16
- package/lib/edm/edmUtils.js +5 -2
- package/lib/gen/Dictionary.json +10 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +15 -2
- package/lib/gen/language.tokens +1 -0
- package/lib/gen/languageParser.js +6557 -5618
- package/lib/json/from-csn.js +4 -5
- package/lib/json/to-csn.js +29 -4
- package/lib/language/antlrParser.js +19 -1
- package/lib/language/errorStrategy.js +28 -7
- package/lib/language/genericAntlrParser.js +118 -24
- package/lib/language/textUtils.js +16 -0
- package/lib/main.d.ts +28 -3
- package/lib/main.js +3 -0
- package/lib/model/csnRefs.js +4 -1
- package/lib/model/csnUtils.js +20 -14
- package/lib/model/revealInternalProperties.js +5 -2
- package/lib/optionProcessor.js +23 -22
- package/lib/render/manageConstraints.js +13 -29
- package/lib/render/toCdl.js +47 -26
- package/lib/render/toHdbcds.js +63 -42
- package/lib/render/toRename.js +6 -10
- package/lib/render/toSql.js +71 -117
- package/lib/render/utils/common.js +41 -6
- package/lib/transform/.eslintrc.json +9 -1
- package/lib/transform/addTenantFields.js +228 -0
- package/lib/transform/db/applyTransformations.js +57 -4
- package/lib/transform/db/assertUnique.js +4 -4
- package/lib/transform/db/backlinks.js +13 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +24 -3
- package/lib/transform/db/flattening.js +70 -71
- package/lib/transform/db/killAnnotations.js +37 -0
- package/lib/transform/db/rewriteCalculatedElements.js +46 -6
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/draft/db.js +2 -16
- package/lib/transform/draft/odata.js +3 -3
- package/lib/transform/effective/associations.js +3 -5
- package/lib/transform/effective/main.js +6 -9
- package/lib/transform/forOdata.js +26 -55
- package/lib/transform/forRelationalDB.js +38 -18
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/transform/transformUtils.js +47 -34
- package/lib/transform/translateAssocsToJoins.js +45 -11
- package/lib/transform/universalCsn/coreComputed.js +1 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +7 -6
package/lib/compiler/populate.js
CHANGED
|
@@ -38,6 +38,7 @@ const {
|
|
|
38
38
|
annotationLocation,
|
|
39
39
|
linkToOrigin,
|
|
40
40
|
setMemberParent,
|
|
41
|
+
dependsOn,
|
|
41
42
|
proxyCopyMembers,
|
|
42
43
|
setExpandStatus,
|
|
43
44
|
setExpandStatusAnnotate,
|
|
@@ -855,6 +856,9 @@ function populate( model ) {
|
|
|
855
856
|
const elemLocation = !query._main.$inferred && location;
|
|
856
857
|
const origin = envParent ? navElem : navElem._origin;
|
|
857
858
|
const elem = linkToOrigin( origin, name, query, null, elemLocation );
|
|
859
|
+
if (origin.$calcDepElement) // TODO: this will be changed in the next PR
|
|
860
|
+
dependsOn( elem, origin.$calcDepElement, location );
|
|
861
|
+
|
|
858
862
|
// TODO: check assocToMany { * }
|
|
859
863
|
dictAdd( elements, name, elem, ( _name, loc ) => {
|
|
860
864
|
// there can be a definition from a previous inline with the same name:
|
|
@@ -1183,7 +1187,7 @@ function populate( model ) {
|
|
|
1183
1187
|
function isDirectProjection( proj, base ) {
|
|
1184
1188
|
return proj.kind === 'entity' && // not event
|
|
1185
1189
|
// direct proj (TODO: or should we add them to another list?)
|
|
1186
|
-
// TODO: delete ENTITY._from
|
|
1190
|
+
// TODO: delete ENTITY._from - maybe not...
|
|
1187
1191
|
proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
|
|
1188
1192
|
proj._from && proj._from.length === 1 &&
|
|
1189
1193
|
base === resolvePath( proj._from[0], 'from', proj.query );
|
|
@@ -21,7 +21,7 @@ const {
|
|
|
21
21
|
viewFromPrimary,
|
|
22
22
|
} = require('./utils');
|
|
23
23
|
const $inferred = Symbol.for( 'cds.$inferred' );
|
|
24
|
-
// const {
|
|
24
|
+
// const { ref } = require( '../model/revealInternalProperties' )
|
|
25
25
|
|
|
26
26
|
// Note that propagation here is also used for deep-copying (function `onlyViaParent`)
|
|
27
27
|
function propagate( model ) {
|
|
@@ -59,7 +59,7 @@ function propagate( model ) {
|
|
|
59
59
|
srid: always,
|
|
60
60
|
localized: always,
|
|
61
61
|
target: notWithExpand,
|
|
62
|
-
targetAspect
|
|
62
|
+
targetAspect,
|
|
63
63
|
cardinality: notWithExpand,
|
|
64
64
|
on: notWithExpand,
|
|
65
65
|
foreignKeys: expensive, // includes "notWithExpand", dictionary copy
|
|
@@ -68,6 +68,7 @@ function propagate( model ) {
|
|
|
68
68
|
enum: expensive,
|
|
69
69
|
params: expensive, // actually only with parent action
|
|
70
70
|
returns,
|
|
71
|
+
$filtered: annotation,
|
|
71
72
|
};
|
|
72
73
|
const { options } = model;
|
|
73
74
|
// eslint-disable-next-line max-len
|
|
@@ -90,7 +91,7 @@ function propagate( model ) {
|
|
|
90
91
|
runMembers( art );
|
|
91
92
|
return;
|
|
92
93
|
}
|
|
93
|
-
// console.log('RUN:',
|
|
94
|
+
// if (!art.builtin)console.log('RUN:', ref(art))
|
|
94
95
|
|
|
95
96
|
const chain = [];
|
|
96
97
|
let targets = [ art ];
|
|
@@ -109,6 +110,7 @@ function propagate( model ) {
|
|
|
109
110
|
if (target.value?._artifact.$inferred !== 'include') {
|
|
110
111
|
// If the referred to element is not inferred, it is a new one and not the original.
|
|
111
112
|
// The new one was not originally referred to => error;
|
|
113
|
+
// TODO: no messages in propagator.js
|
|
112
114
|
warning( 'ref-unexpected-override', [ target.name.location, target ],
|
|
113
115
|
{ id: target.name.id, target: target.value?._artifact },
|
|
114
116
|
'Calculated element $(ID) does not originally refer to $(TARGET)' );
|
|
@@ -134,11 +136,11 @@ function propagate( model ) {
|
|
|
134
136
|
chain.reverse();
|
|
135
137
|
chain.forEach( step );
|
|
136
138
|
runMembers( art );
|
|
137
|
-
// console.log('DONE:',
|
|
139
|
+
// if(!art.builtin)console.log('DONE:',ref(art),art.elements?Object.keys(art.elements):0);
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
function runMembers( art ) {
|
|
141
|
-
// console.log('MEMBERS:',
|
|
143
|
+
// if(!art.builtin)console.log('MEMBERS:',ref(art))
|
|
142
144
|
forEachMember( art, run ); // after propagation in parent!
|
|
143
145
|
// propagate to sub query elements even if not requested:
|
|
144
146
|
if (art.$queries)
|
|
@@ -150,16 +152,29 @@ function propagate( model ) {
|
|
|
150
152
|
}
|
|
151
153
|
if (obj.items)
|
|
152
154
|
run( obj.items );
|
|
155
|
+
obj = obj.targetAspect;
|
|
156
|
+
// if(obj)console.log('TA:',ref(art),!!getOrigin( obj ))
|
|
157
|
+
if (obj && isAnonymousAspect( obj ))
|
|
158
|
+
run( obj );
|
|
153
159
|
setLink( art, '_status', 'propagated' );
|
|
154
160
|
}
|
|
155
161
|
|
|
162
|
+
function isAnonymousAspect( aspect ) {
|
|
163
|
+
while (aspect) {
|
|
164
|
+
if (aspect.elements)
|
|
165
|
+
return true;
|
|
166
|
+
aspect = getOrigin( aspect );
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
156
171
|
function step({ target, source }) {
|
|
157
|
-
// console.log('PROPS:',source&&source.name,'->',target.name)
|
|
158
172
|
const viaType = target.type && // TODO: falsy $inferred value instead of 'cast'?
|
|
159
173
|
(!target.type.$inferred || target.type.$inferred === 'cast');
|
|
160
174
|
const keys = Object.keys( source );
|
|
175
|
+
// console.log('PROPS:',ref(source),'->',ref(target),keys.join('+'))
|
|
161
176
|
for (const prop of keys) {
|
|
162
|
-
// TODO: warning with competing props from multi-includes
|
|
177
|
+
// TODO: warning with competing props from multi-includes, but not in propagator.js
|
|
163
178
|
if (target[prop] !== undefined || source[prop] === undefined)
|
|
164
179
|
continue;
|
|
165
180
|
const transformer = props[prop] || props[prop.charAt(0)];
|
|
@@ -227,7 +242,7 @@ function propagate( model ) {
|
|
|
227
242
|
// * `enum`: an enum cannot be used with `expand`
|
|
228
243
|
// * `keys`: should also not be propagated with `expand`
|
|
229
244
|
function expensive( prop, target, source ) {
|
|
230
|
-
// console.log(prop,source
|
|
245
|
+
// console.log('EXP:',prop,ref(source),'->',ref(target));
|
|
231
246
|
if (source.kind === 'builtin')
|
|
232
247
|
return;
|
|
233
248
|
if (target.expand) // do not propagate `keys` with expand
|
|
@@ -245,8 +260,12 @@ function propagate( model ) {
|
|
|
245
260
|
target._outer && target._outer.location;
|
|
246
261
|
const dict = source[prop];
|
|
247
262
|
target[prop] = Object.create( null ); // also propagate empty elements
|
|
263
|
+
const propagateKey = target.kind === 'aspect'; // anonymous aspect
|
|
248
264
|
for (const name in dict) {
|
|
249
|
-
const
|
|
265
|
+
const origin = dict[name];
|
|
266
|
+
const member = linkToOrigin( origin, name, target, prop, location );
|
|
267
|
+
if (propagateKey && origin.key)
|
|
268
|
+
member.key = Object.assign( { $inferred: 'expanded' }, origin.key );
|
|
250
269
|
member.$inferred = 'proxy';
|
|
251
270
|
if (prop === 'foreignKeys')
|
|
252
271
|
setLink( member, '_effectiveType', member );
|
|
@@ -262,6 +281,24 @@ function propagate( model ) {
|
|
|
262
281
|
always( prop, target, source );
|
|
263
282
|
}
|
|
264
283
|
|
|
284
|
+
function targetAspect( prop, target, source ) {
|
|
285
|
+
if (target.targetAspect)
|
|
286
|
+
return;
|
|
287
|
+
const ta = source.targetAspect;
|
|
288
|
+
if (!ta.elements && !ta._origin) { // _origin set for elements in source
|
|
289
|
+
notWithExpand( prop, target, source );
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
const tat = { location: ta.location, $inferred: 'prop', kind: 'aspect' };
|
|
293
|
+
setLink( tat, '_origin', ta );
|
|
294
|
+
setLink( tat, '_outer', target );
|
|
295
|
+
setLink( tat, '_parent', target._parent );
|
|
296
|
+
setLink( tat, '_main', null );
|
|
297
|
+
target.targetAspect = tat;
|
|
298
|
+
// console.log('TAC:',ref(tat),'via',ref(ta))
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
265
302
|
function notWithExpand( prop, target, source ) {
|
|
266
303
|
if (!target.expand || prop === 'type' && source.elements)
|
|
267
304
|
always( prop, target, source );
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -44,6 +44,7 @@ const {
|
|
|
44
44
|
forEachGeneric,
|
|
45
45
|
forEachInOrder,
|
|
46
46
|
isDeprecatedEnabled,
|
|
47
|
+
isBetaEnabled,
|
|
47
48
|
} = require('../base/model');
|
|
48
49
|
const { dictAdd } = require('../base/dictionaries');
|
|
49
50
|
const { dictLocation, weakLocation } = require('../base/location');
|
|
@@ -132,7 +133,9 @@ function resolve( model ) {
|
|
|
132
133
|
if (location) {
|
|
133
134
|
model.$assert = null;
|
|
134
135
|
const msg = semanticLoc && 'target';
|
|
135
|
-
error( 'ref-cyclic', [ location, semanticLoc || user ], {
|
|
136
|
+
error( 'ref-cyclic', [ location, semanticLoc || user ], {
|
|
137
|
+
art, '#': msg,
|
|
138
|
+
}, {
|
|
136
139
|
std: 'Illegal circular reference to $(ART)',
|
|
137
140
|
element: 'Illegal circular reference to element $(MEMBER) of $(ART)',
|
|
138
141
|
target: 'Illegal circular reference to target $(ART)',
|
|
@@ -516,8 +519,8 @@ function resolve( model ) {
|
|
|
516
519
|
if (art.value) {
|
|
517
520
|
if (art.$syntax === 'calc')
|
|
518
521
|
checkCalculatedElement( art );
|
|
519
|
-
else if (art.type && !art.$inferred
|
|
520
|
-
|
|
522
|
+
else if (art.type && !art.$inferred)
|
|
523
|
+
checkColumnTypeCast( art );
|
|
521
524
|
}
|
|
522
525
|
|
|
523
526
|
resolveExprInAnnotations( art );
|
|
@@ -554,10 +557,12 @@ function resolve( model ) {
|
|
|
554
557
|
|
|
555
558
|
// Check explicit types: If either side has one, so must the other.
|
|
556
559
|
const sType = specifiedElement.type?._artifact;
|
|
557
|
-
const
|
|
560
|
+
const iTypeArt = getInferredPropFromOrigin( 'type' )?._artifact;
|
|
561
|
+
const iType = iTypeArt || inferredElement;
|
|
558
562
|
|
|
559
563
|
// xor: could be missing a type;
|
|
560
564
|
// FIXME: The coding above returns incorrect iType for expand on associations
|
|
565
|
+
|
|
561
566
|
if (!specifiedElement.type && inferredElement.type) {
|
|
562
567
|
error( 'query-mismatched-element', [ specifiedElement.location, user ], {
|
|
563
568
|
'#': !specifiedElement.type ? 'missing' : 'extra', name: user.name.id, prop: 'type',
|
|
@@ -565,8 +570,12 @@ function resolve( model ) {
|
|
|
565
570
|
return;
|
|
566
571
|
}
|
|
567
572
|
// If specified type is `null`, type could not be resolved.
|
|
568
|
-
else if (sType && sType !== iType
|
|
569
|
-
|
|
573
|
+
else if (sType && sType !== iType &&
|
|
574
|
+
// Special case for $recompilation: allow one level of type indirection. See #12113.
|
|
575
|
+
(!model.options.$recompile || sType !== iType.type?._artifact)) {
|
|
576
|
+
const typeName = !iTypeArt && 'typeExtra' || // no inferred type prop
|
|
577
|
+
iType?.name && sType?.name && 'typeName' || // both types are named
|
|
578
|
+
'type'; // unknown type names
|
|
570
579
|
const othertype = typeName !== 'type' && iType || '';
|
|
571
580
|
error( 'query-mismatched-element', [
|
|
572
581
|
specifiedElement.type.location || specifiedElement.location, user,
|
|
@@ -638,8 +647,11 @@ function resolve( model ) {
|
|
|
638
647
|
|
|
639
648
|
// If cardinality is not specified, the compiler uses the inferred one.
|
|
640
649
|
if (specifiedElement.cardinality) {
|
|
650
|
+
// Users can change the origin's cardinality via filter: We can't rely on the origin.
|
|
651
|
+
const ref = inferredElement.value?.path;
|
|
652
|
+
const assocFilterCardinality = ref?.[ref.length - 1]?.cardinality;
|
|
641
653
|
const sCardinality = specifiedElement.cardinality;
|
|
642
|
-
const iCardinality =
|
|
654
|
+
const iCardinality = assocFilterCardinality || getInferredCardinality();
|
|
643
655
|
if (!iCardinality) {
|
|
644
656
|
error( 'query-mismatched-element', [
|
|
645
657
|
sCardinality.location || specifiedElement.location, user,
|
|
@@ -747,6 +759,20 @@ function resolve( model ) {
|
|
|
747
759
|
}
|
|
748
760
|
return element[prop];
|
|
749
761
|
}
|
|
762
|
+
|
|
763
|
+
function getInferredCardinality() {
|
|
764
|
+
let element = inferredElement;
|
|
765
|
+
if (element._effectiveType !== 0) {
|
|
766
|
+
while (getOrigin( element )) {
|
|
767
|
+
const ref = element.value?.path;
|
|
768
|
+
if (element.cardinality || ref?.[ref.length - 1]?.cardinality)
|
|
769
|
+
break;
|
|
770
|
+
element = getOrigin( element );
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
const ref = element.value?.path;
|
|
774
|
+
return element.cardinality || ref?.[ref.length - 1]?.cardinality;
|
|
775
|
+
}
|
|
750
776
|
}
|
|
751
777
|
|
|
752
778
|
|
|
@@ -795,24 +821,20 @@ function resolve( model ) {
|
|
|
795
821
|
else if (!allowedInKind.includes( parent.kind )) {
|
|
796
822
|
error( 'def-invalid-calc-elem', loc, { '#': parent.kind } );
|
|
797
823
|
}
|
|
798
|
-
else if (effectiveType( art )?.elements) {
|
|
824
|
+
else if (effectiveType( art )?.elements && !art.$inferred) {
|
|
799
825
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
800
|
-
if (
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
|
|
805
|
-
}
|
|
826
|
+
if (art.type)
|
|
827
|
+
error( 'type-unexpected-structure', [ art.type.location, art ], { '#': 'calc' } );
|
|
828
|
+
else
|
|
829
|
+
error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
|
|
806
830
|
}
|
|
807
|
-
else if (effectiveType( art )?.items) {
|
|
831
|
+
else if (effectiveType( art )?.items && !art.$inferred) {
|
|
808
832
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
} );
|
|
815
|
-
}
|
|
833
|
+
const isCast = art.type?.$inferred === 'cast';
|
|
834
|
+
error( 'type-unexpected-many', [ (art.type || art.value).location, art ], {
|
|
835
|
+
'#': (!art.type && 'calc-implicit') || (isCast && 'calc-cast') || 'calc',
|
|
836
|
+
elemref: art.type ? undefined : { ref: art.value.path },
|
|
837
|
+
} );
|
|
816
838
|
}
|
|
817
839
|
else {
|
|
818
840
|
const noTruthyAllowed = [ 'localized', 'key', 'virtual' ];
|
|
@@ -827,7 +849,7 @@ function resolve( model ) {
|
|
|
827
849
|
}
|
|
828
850
|
}
|
|
829
851
|
|
|
830
|
-
function
|
|
852
|
+
function checkColumnTypeCast( art ) {
|
|
831
853
|
const elem = (art.value.op?.val === 'cast')
|
|
832
854
|
? art.value.args[0]?._artifact
|
|
833
855
|
: art.value._artifact;
|
|
@@ -836,6 +858,9 @@ function resolve( model ) {
|
|
|
836
858
|
error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'to-structure' } );
|
|
837
859
|
else if (elem.elements) // TODO: calc elements
|
|
838
860
|
error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'from-structure' } );
|
|
861
|
+
else if (elem.target && !art.type._artifact?.target)
|
|
862
|
+
// allow cast to association -> that is already checked and denied elsewhere
|
|
863
|
+
error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'from-assoc' } );
|
|
839
864
|
}
|
|
840
865
|
}
|
|
841
866
|
|
|
@@ -1353,18 +1378,50 @@ function resolve( model ) {
|
|
|
1353
1378
|
}
|
|
1354
1379
|
}
|
|
1355
1380
|
|
|
1356
|
-
function resolveAnnoExpr( expr, art ) {
|
|
1357
|
-
if (expr.$tokenTexts)
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1381
|
+
function resolveAnnoExpr( expr, art, anno = expr ) {
|
|
1382
|
+
if (expr.$tokenTexts) {
|
|
1383
|
+
if (!anno.kind)
|
|
1384
|
+
initAnnotationForExpression( anno, art );
|
|
1385
|
+
resolveExpr( expr, 'annotation', anno );
|
|
1386
|
+
reportUnsupportedAnnoExpr( expr );
|
|
1387
|
+
}
|
|
1388
|
+
else if (expr.literal === 'array') {
|
|
1389
|
+
expr.val.forEach( val => resolveAnnoExpr( val, art, anno ) );
|
|
1390
|
+
}
|
|
1391
|
+
else if (expr.literal === 'struct') {
|
|
1392
|
+
Object.values( expr.struct ).forEach( val => resolveAnnoExpr( val, art, anno ) );
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
function reportUnsupportedAnnoExpr( expr ) {
|
|
1397
|
+
if (isBetaEnabled( model.options, 'annotationExpressions' ))
|
|
1398
|
+
return;
|
|
1399
|
+
const alreadyReported = model.$messageFunctions.messages
|
|
1400
|
+
.find(msg => msg.messageId === 'anno-experimental-expressions');
|
|
1401
|
+
if (!alreadyReported) {
|
|
1402
|
+
info( 'anno-experimental-expressions', [ expr.location ], {
|
|
1403
|
+
option: 'annotationExpressions',
|
|
1404
|
+
// eslint-disable-next-line max-len
|
|
1405
|
+
}, 'Expressions in annotation values are a beta feature. Use at your own risk. (This message can be suppressed with beta flag $(OPTION))' );
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// for faster processing, mark artifacts and annotations which contain anno expressions
|
|
1410
|
+
function initAnnotationForExpression( anno, art ) {
|
|
1411
|
+
anno.kind = '$annotation';
|
|
1412
|
+
setLink( anno, '_outer', art );
|
|
1413
|
+
while (!art.$contains?.$annotation) {
|
|
1414
|
+
art.$contains ??= {};
|
|
1415
|
+
art.$contains.$annotation = true; // TODO: extra values for elem and $self refs
|
|
1416
|
+
if (!art._main)
|
|
1417
|
+
break;
|
|
1418
|
+
art = art._parent; // TODO: really go up?
|
|
1419
|
+
}
|
|
1363
1420
|
}
|
|
1364
1421
|
|
|
1365
1422
|
function resolveExpr( expr, exprCtx, user ) {
|
|
1366
1423
|
// console.log(expr?.location.line,exprCtx)
|
|
1367
|
-
traverseExpr( expr, exprCtx, user,
|
|
1424
|
+
traverseExpr( expr, exprCtx, user, resolveExprItem );
|
|
1368
1425
|
}
|
|
1369
1426
|
|
|
1370
1427
|
function resolveExprItem( expr, expected, user ) {
|
|
@@ -1374,8 +1431,10 @@ function resolve( model ) {
|
|
|
1374
1431
|
if (expr.path) {
|
|
1375
1432
|
// TODO: re-think this $expected: 'exists' thing
|
|
1376
1433
|
if (expr.$expected === 'exists') {
|
|
1377
|
-
|
|
1378
|
-
|
|
1434
|
+
if (expected !== 'annotation') { // `exists e[…]` allowed in annotation expressions
|
|
1435
|
+
error( 'expr-unexpected-exists', [ expr.location, user ], {},
|
|
1436
|
+
'An EXISTS predicate is not expected here' );
|
|
1437
|
+
}
|
|
1379
1438
|
// We complain about the EXISTS before, as EXISTS subquery is also not supported
|
|
1380
1439
|
// TODO: location of EXISTS, TODO: really do this in define.js
|
|
1381
1440
|
expr.$expected = 'approved-exists'; // only complain once
|
|
@@ -1414,8 +1473,8 @@ function resolve( model ) {
|
|
|
1414
1473
|
if (step.args)
|
|
1415
1474
|
resolveParams( step.args, art, entity, expected, user, step.location );
|
|
1416
1475
|
|
|
1417
|
-
// Publishing an association with filters is ok in
|
|
1418
|
-
const publishAssoc = art.kind === 'entity' && expected === 'column';
|
|
1476
|
+
// Publishing an association with filters is ok in columns and also ok in calculated elements.
|
|
1477
|
+
const publishAssoc = art.kind === 'entity' && (expected === 'column' || expected === 'calc');
|
|
1419
1478
|
|
|
1420
1479
|
if (entity || publishAssoc) {
|
|
1421
1480
|
if (step.where) {
|
package/lib/compiler/shared.js
CHANGED
|
@@ -105,7 +105,7 @@ function fns( model ) {
|
|
|
105
105
|
dynamic: modelBuiltinsOrDefinitions,
|
|
106
106
|
navigation: environment,
|
|
107
107
|
notFound: undefinedDefinition,
|
|
108
|
-
accept:
|
|
108
|
+
accept: acceptQuerySource,
|
|
109
109
|
noDep: '', // dependency special for from
|
|
110
110
|
},
|
|
111
111
|
type: {
|
|
@@ -286,7 +286,6 @@ function fns( model ) {
|
|
|
286
286
|
'ref-undefined-element': 'anno-undefined-element',
|
|
287
287
|
'ref-undefined-param': 'anno-undefined-param',
|
|
288
288
|
},
|
|
289
|
-
// check: checkAssocOn,
|
|
290
289
|
param: paramSemantics,
|
|
291
290
|
nestedColumn: () => ({ // in expand and inline - TODO
|
|
292
291
|
lexical: justDollarSelf,
|
|
@@ -297,6 +296,17 @@ function fns( model ) {
|
|
|
297
296
|
rewriteProjectionToSelf: true,
|
|
298
297
|
}),
|
|
299
298
|
},
|
|
299
|
+
// TODO: introduce some kind of inheritance
|
|
300
|
+
annoRewrite: { // annotation assignments
|
|
301
|
+
lexical: justDollarSelf, // TODO: forbid $projection
|
|
302
|
+
dollar: true,
|
|
303
|
+
dynamic: parentElements,
|
|
304
|
+
navigation: assocOnNavigation,
|
|
305
|
+
noDep: true,
|
|
306
|
+
notFound: null, // no error, just falsy links
|
|
307
|
+
param: paramSemantics,
|
|
308
|
+
// nestedColumn: // in expand and inline - TODO
|
|
309
|
+
},
|
|
300
310
|
};
|
|
301
311
|
|
|
302
312
|
Object.assign( model.$functions, {
|
|
@@ -314,25 +324,23 @@ function fns( model ) {
|
|
|
314
324
|
// Expression traversal function ----------------------------------------------
|
|
315
325
|
function traverseExpr( expr, exprCtx, user, callback ) {
|
|
316
326
|
if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
|
|
317
|
-
return;
|
|
327
|
+
return null;
|
|
318
328
|
|
|
319
329
|
if (expr.path) {
|
|
320
|
-
callback( expr, exprCtx, user );
|
|
321
330
|
// TODO: move arguments and filter traversal to here
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
else if (expr.type || expr.query) {
|
|
325
|
-
callback( expr, exprCtx, user );
|
|
331
|
+
return callback( expr, exprCtx, user );
|
|
326
332
|
}
|
|
333
|
+
let first = (expr.type || expr.query) && callback( expr, exprCtx, user );
|
|
327
334
|
|
|
328
|
-
if (expr.args) {
|
|
335
|
+
if (expr.args && !first) {
|
|
329
336
|
const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
|
|
330
337
|
// TODO: re-think $expected
|
|
331
|
-
|
|
332
|
-
args.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
|
|
338
|
+
first = args.find( e => traverseExpr( e, exprCtx, user, callback ) );
|
|
333
339
|
}
|
|
334
|
-
|
|
335
|
-
|
|
340
|
+
|
|
341
|
+
first ??= expr.suffix && // fn( … ) OVER …
|
|
342
|
+
expr.suffix.find( e => traverseExpr( e, exprCtx, user, callback ) );
|
|
343
|
+
return first;
|
|
336
344
|
}
|
|
337
345
|
|
|
338
346
|
// Return absolute name for unchecked path `ref`. We first try searching for
|
|
@@ -395,7 +403,6 @@ function fns( model ) {
|
|
|
395
403
|
return setArtifactLink( ref, root );
|
|
396
404
|
|
|
397
405
|
// how many path items are for artifacts (rest: elements)
|
|
398
|
-
// console.log(expected, ref.path.map(a=>a.id),artItemsCount)
|
|
399
406
|
let art = getPathItem( ref, semantics, user );
|
|
400
407
|
if (!art)
|
|
401
408
|
return setArtifactLink( ref, art );
|
|
@@ -413,14 +420,19 @@ function fns( model ) {
|
|
|
413
420
|
const target = art._effectiveType?.target?._artifact;
|
|
414
421
|
if (target)
|
|
415
422
|
dependsOn( user._main, target, location, user );
|
|
423
|
+
if (target?.$calcDepElement)
|
|
424
|
+
dependsOn( user._main, target.$calcDepElement, location, user );
|
|
416
425
|
}
|
|
417
426
|
else if (art._main && art.kind !== 'select' || path[0]._navigation?.kind !== '$self') {
|
|
418
427
|
// no real dependency to bare $self (or actually: the underlying query)
|
|
419
428
|
dependsOn( user, art, location );
|
|
429
|
+
if (art.$calcDepElement)
|
|
430
|
+
dependsOn( user, art.$calcDepElement, location );
|
|
420
431
|
// Without on-demand resolve, we can simply signal 'undefined "x"'
|
|
421
|
-
|
|
422
|
-
|
|
432
|
+
// instead of 'illegal cycle' in the following case:
|
|
433
|
+
// element elem: type of elem.x;
|
|
423
434
|
}
|
|
435
|
+
|
|
424
436
|
// TODO: really write dependency with expand/inline? write test
|
|
425
437
|
// (removing it is not incompatible => not urgent)
|
|
426
438
|
}
|
|
@@ -498,7 +510,9 @@ function fns( model ) {
|
|
|
498
510
|
return undefined; // parse error
|
|
499
511
|
if (head._artifact !== undefined)
|
|
500
512
|
return head._artifact;
|
|
501
|
-
|
|
513
|
+
let ruser = user._user || user; // TODO: nicer name if we keep this
|
|
514
|
+
if (ruser.kind === '$annotation')
|
|
515
|
+
ruser = ruser._outer;
|
|
502
516
|
|
|
503
517
|
// Handle expand/inline, `type of`, :param, global (internally for CDL):
|
|
504
518
|
if (user._pathHead && !semantics.isMainRef) // in expand/inline
|
|
@@ -545,8 +559,9 @@ function fns( model ) {
|
|
|
545
559
|
else
|
|
546
560
|
valid.push( model.$magicVariables.elements, removeDollarNames( dynamicDict ) );
|
|
547
561
|
// TODO: streamline function arguments (probably: user, path, semantics )
|
|
548
|
-
const undef = semantics.notFound(
|
|
549
|
-
|
|
562
|
+
const undef = semantics.notFound?.( user._user || user, head, valid, dynamicDict,
|
|
563
|
+
!isMainRef && user._user && user._artifact,
|
|
564
|
+
path, semantics );
|
|
550
565
|
return setArtifactLink( head, undef || null );
|
|
551
566
|
}
|
|
552
567
|
|
|
@@ -913,9 +928,8 @@ function fns( model ) {
|
|
|
913
928
|
if (target && assocSpec && user) {
|
|
914
929
|
if (assocSpec !== 'calc')
|
|
915
930
|
dependsOn( user._main || user, target, location || user.location, user );
|
|
916
|
-
else
|
|
917
|
-
dependsOn( user, target, location || user.location );
|
|
918
|
-
// TODO: have some _delayedDeps for calc elements
|
|
931
|
+
else
|
|
932
|
+
dependsOn( user.$calcDepElement, target, location || user.location, user );
|
|
919
933
|
}
|
|
920
934
|
const effectiveTarget = Functions.effectiveType( target );
|
|
921
935
|
// if (effectiveTarget === 0 && location)
|
|
@@ -960,11 +974,11 @@ function fns( model ) {
|
|
|
960
974
|
}
|
|
961
975
|
|
|
962
976
|
function undefinedTargetElement( user, head, valid, _dict, pathItemArtifact ) {
|
|
963
|
-
//
|
|
964
|
-
const
|
|
977
|
+
// `art.target` may not set in case target entities `myEntity[unknown > 2]`
|
|
978
|
+
const art = pathItemArtifact?._effectiveType || user._parent;
|
|
965
979
|
// TODO: better with $refs in filter conditions
|
|
966
980
|
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
|
|
967
|
-
{ '#': 'target', art: target, id: head.id } );
|
|
981
|
+
{ '#': 'target', art: art.target || art, id: head.id } );
|
|
968
982
|
}
|
|
969
983
|
|
|
970
984
|
function undefinedVariable( user, head, valid ) {
|
|
@@ -1033,14 +1047,16 @@ function fns( model ) {
|
|
|
1033
1047
|
return null;
|
|
1034
1048
|
}
|
|
1035
1049
|
|
|
1036
|
-
function undefinedNestedElement( user, head, valid, _dict, _art, path ) {
|
|
1050
|
+
function undefinedNestedElement( user, head, valid, _dict, _art, path, semantics ) {
|
|
1037
1051
|
const art = user._pathHead._origin;
|
|
1038
1052
|
if (!art)
|
|
1039
1053
|
return null; // no consequential error
|
|
1040
|
-
return undefinedItemElement( user, head, valid, null, art, path );
|
|
1054
|
+
return undefinedItemElement( user, head, valid, null, art, path, semantics );
|
|
1041
1055
|
}
|
|
1042
1056
|
|
|
1043
1057
|
function undefinedItemElement( user, item, valid, _dict, art, path, semantics ) {
|
|
1058
|
+
if (semantics.notFound === null)
|
|
1059
|
+
return;
|
|
1044
1060
|
const query = userQuery( art );
|
|
1045
1061
|
if (query?.name?.id > 1) {
|
|
1046
1062
|
const root = userQuery( user ) !== query && path[0]._navigation;
|
|
@@ -1084,7 +1100,6 @@ function fns( model ) {
|
|
|
1084
1100
|
{ '#': variant, art: a, id: item.id }, semantics );
|
|
1085
1101
|
}
|
|
1086
1102
|
}
|
|
1087
|
-
return null;
|
|
1088
1103
|
}
|
|
1089
1104
|
|
|
1090
1105
|
// Functions called via semantics.accept: -------------------------------------
|
|
@@ -1264,15 +1279,17 @@ function fns( model ) {
|
|
|
1264
1279
|
return false;
|
|
1265
1280
|
}
|
|
1266
1281
|
|
|
1267
|
-
function
|
|
1282
|
+
function acceptQuerySource( art, user, ref ) { // for FROM
|
|
1268
1283
|
const { path, scope } = ref;
|
|
1269
1284
|
// see getPathItem(): how many path items are for the main artifact ref?
|
|
1270
1285
|
const artItemsCount = (typeof scope === 'number' && scope) || (scope ? 1 : path.length);
|
|
1271
|
-
// at least the last main definition should be an entity
|
|
1286
|
+
// at least the last main definition should be an entity or an
|
|
1287
|
+
// event (if the user is also an event)
|
|
1272
1288
|
// an additional check for target would need effectiveType()
|
|
1273
1289
|
const source = path[artItemsCount - 1]._artifact;
|
|
1274
|
-
|
|
1275
|
-
|
|
1290
|
+
const variant = (user._main.kind === 'event') ? 'event' : 'std';
|
|
1291
|
+
if (source.kind !== 'entity' && !acceptEventProjectionSource( source, user )) {
|
|
1292
|
+
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null, { '#': variant } );
|
|
1276
1293
|
return false;
|
|
1277
1294
|
}
|
|
1278
1295
|
if (source === art)
|
|
@@ -1280,10 +1297,20 @@ function fns( model ) {
|
|
|
1280
1297
|
const assoc = Functions.effectiveType( art );
|
|
1281
1298
|
if (assoc.target)
|
|
1282
1299
|
return art; // TODO: use target here
|
|
1283
|
-
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null );
|
|
1300
|
+
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null, { '#': variant } );
|
|
1284
1301
|
return false;
|
|
1285
1302
|
}
|
|
1286
1303
|
|
|
1304
|
+
function acceptEventProjectionSource( source, user ) {
|
|
1305
|
+
if (user._main.kind !== 'event' || (source.kind !== 'event' && source.kind !== 'type'))
|
|
1306
|
+
return false;
|
|
1307
|
+
const effectiveType = Functions.effectiveType( source );
|
|
1308
|
+
if (!effectiveType)
|
|
1309
|
+
return false;
|
|
1310
|
+
const { kind } = effectiveType;
|
|
1311
|
+
return (kind === 'entity' || kind === 'event' || (kind === 'type' && effectiveType.elements));
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1287
1314
|
function acceptTypeOrElement( art, user, ref ) { // for type
|
|
1288
1315
|
// was ['action', 'function'].includes( user._parent?.kind ))
|
|
1289
1316
|
while (user._outer)
|