@sap/cds-compiler 4.0.2 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +200 -5
- package/bin/cdsc.js +18 -15
- package/doc/CHANGELOG_BETA.md +16 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +33 -13
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +25 -25
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +123 -42
- package/lib/base/messages.js +18 -10
- package/lib/base/model.js +43 -10
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/elements.js +11 -10
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +22 -14
- package/lib/checks/queryNoDbArtifacts.js +132 -73
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +4 -3
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +71 -40
- package/lib/compiler/base.js +7 -2
- package/lib/compiler/builtins.js +40 -41
- package/lib/compiler/checks.js +415 -367
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +9 -9
- package/lib/compiler/define.js +124 -90
- package/lib/compiler/extend.js +115 -88
- package/lib/compiler/finalize-parse-cdl.js +26 -25
- package/lib/compiler/generate.js +57 -49
- package/lib/compiler/index.js +56 -56
- package/lib/compiler/kick-start.js +10 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +180 -144
- package/lib/compiler/propagator.js +10 -9
- package/lib/compiler/resolve.js +321 -246
- package/lib/compiler/shared.js +812 -433
- package/lib/compiler/tweak-assocs.js +114 -50
- package/lib/compiler/utils.js +241 -46
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +679 -770
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +689 -648
- package/lib/edm/edmUtils.js +279 -300
- package/lib/gen/Dictionary.json +34 -10
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2857 -2856
- package/lib/json/from-csn.js +77 -51
- package/lib/json/to-csn.js +15 -15
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +61 -64
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +65 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +51 -18
- package/lib/model/revealInternalProperties.js +30 -22
- package/lib/modelCompare/compare.js +149 -41
- package/lib/modelCompare/utils/filter.js +55 -25
- package/lib/optionProcessor.js +21 -9
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +63 -23
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +82 -35
- package/lib/render/utils/common.js +11 -9
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +62 -21
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +9 -9
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +138 -68
- package/lib/transform/db/flattening.js +98 -30
- package/lib/transform/db/rewriteCalculatedElements.js +20 -14
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +8 -7
- package/lib/transform/db/views.js +73 -33
- package/lib/transform/draft/db.js +11 -9
- package/lib/transform/draft/odata.js +1 -1
- package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
- package/lib/transform/forRelationalDB.js +148 -136
- package/lib/transform/localized.js +92 -54
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
- package/lib/transform/translateAssocsToJoins.js +14 -28
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
- package/share/messages/message-explanations.json +1 -1
package/lib/compiler/resolve.js
CHANGED
|
@@ -62,14 +62,14 @@ const {
|
|
|
62
62
|
testExpr,
|
|
63
63
|
targetMaxNotOne,
|
|
64
64
|
traverseQueryPost,
|
|
65
|
-
|
|
65
|
+
linkToOrigin,
|
|
66
66
|
} = require('./utils');
|
|
67
67
|
|
|
68
68
|
const detectCycles = require('./cycle-detector');
|
|
69
69
|
|
|
70
|
-
const $location = Symbol.for('cds.$location');
|
|
70
|
+
const $location = Symbol.for( 'cds.$location' );
|
|
71
71
|
|
|
72
|
-
const $inferred = Symbol.for('cds.$inferred');
|
|
72
|
+
const $inferred = Symbol.for( 'cds.$inferred' );
|
|
73
73
|
|
|
74
74
|
// TODO: make this part of specExpected in shared.js
|
|
75
75
|
// (standard: not possible on last if !ref.$expected → ref.scope: '$exists')
|
|
@@ -87,18 +87,20 @@ function resolve( model ) {
|
|
|
87
87
|
} = model.$messageFunctions;
|
|
88
88
|
const {
|
|
89
89
|
resolvePath,
|
|
90
|
+
traverseExpr,
|
|
90
91
|
createRemainingAnnotateStatements,
|
|
91
92
|
effectiveType,
|
|
92
93
|
getOrigin,
|
|
93
|
-
|
|
94
|
+
getInheritedProp,
|
|
94
95
|
resolveTypeArgumentsUnchecked,
|
|
95
96
|
} = model.$functions;
|
|
96
97
|
Object.assign( model.$functions, {
|
|
97
98
|
resolveExpr,
|
|
99
|
+
addForeignKeyNavigations,
|
|
98
100
|
} );
|
|
99
101
|
|
|
100
102
|
const ignoreSpecifiedElements
|
|
101
|
-
= isDeprecatedEnabled(model.options, 'ignoreSpecifiedQueryElements');
|
|
103
|
+
= isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' );
|
|
102
104
|
|
|
103
105
|
return doResolve();
|
|
104
106
|
|
|
@@ -125,14 +127,21 @@ function resolve( model ) {
|
|
|
125
127
|
// create “super” ANNOTATE statements for annotations on unknown artifacts:
|
|
126
128
|
createRemainingAnnotateStatements();
|
|
127
129
|
// report cyclic dependencies:
|
|
128
|
-
detectCycles( model.definitions, ( user, art, location ) => {
|
|
130
|
+
detectCycles( model.definitions, ( user, art, location, semanticLoc ) => {
|
|
129
131
|
if (location) {
|
|
130
|
-
|
|
132
|
+
model.$assert = null;
|
|
133
|
+
const msg = semanticLoc && 'target';
|
|
134
|
+
error( 'ref-cyclic', [ location, semanticLoc || user ], { art, '#': msg }, {
|
|
131
135
|
std: 'Illegal circular reference to $(ART)',
|
|
132
136
|
element: 'Illegal circular reference to element $(MEMBER) of $(ART)',
|
|
133
|
-
|
|
137
|
+
target: 'Illegal circular reference to target $(ART)',
|
|
138
|
+
} );
|
|
134
139
|
}
|
|
135
|
-
});
|
|
140
|
+
} );
|
|
141
|
+
if (model.$assert) {
|
|
142
|
+
error( '$internal-expecting-cyclic', null, {},
|
|
143
|
+
'INTERNAL: the compiler should have issued an Error[ref-cyclic]' );
|
|
144
|
+
}
|
|
136
145
|
return model;
|
|
137
146
|
}
|
|
138
147
|
|
|
@@ -149,22 +158,52 @@ function resolve( model ) {
|
|
|
149
158
|
return;
|
|
150
159
|
// TODO: what about elements where _origin is set without value?
|
|
151
160
|
// TODO: or should we push elems with `expand` sibling to extra list for
|
|
152
|
-
//
|
|
161
|
+
// better messages? (Whatever that means exactly.)
|
|
153
162
|
const nav = pathNavigation( elem.value );
|
|
154
163
|
const { path } = elem.value;
|
|
155
|
-
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
// redirectImplicitly( elem, origin );
|
|
159
|
-
pushLink( nav.navigation, '_projections', elem );
|
|
160
|
-
}
|
|
161
|
-
else if (elem._pathHead?.kind === '$inline' && path.length === 1) {
|
|
164
|
+
|
|
165
|
+
if (elem._pathHead?.kind === '$inline' && path.length === 1) {
|
|
166
|
+
const item = path[0];
|
|
162
167
|
const hpath = elem._pathHead.value?.path;
|
|
163
168
|
const head = hpath?.length === 1 && hpath[0]._navigation;
|
|
164
169
|
// Alias .{ elem } - but not with Alias.{ $magic }: consider for ON-rewrite
|
|
165
170
|
if (head?.kind === '$tableAlias' && item.id.charAt(0) !== '$')
|
|
166
171
|
pushLink( head.elements[item.id], '_projections', elem );
|
|
167
172
|
}
|
|
173
|
+
else if (nav.navigation) { // not set for $self.…
|
|
174
|
+
// Path could start with table alias; get start index
|
|
175
|
+
let index = path.indexOf(nav.item);
|
|
176
|
+
if (index === -1)
|
|
177
|
+
return;
|
|
178
|
+
|
|
179
|
+
let navItem = nav.navigation;
|
|
180
|
+
if (path[index].where || path[index].args)
|
|
181
|
+
return;
|
|
182
|
+
++index;
|
|
183
|
+
while (navItem && index < path.length) {
|
|
184
|
+
const step = path[index];
|
|
185
|
+
if (!step?.id || step.where || step.args)
|
|
186
|
+
break;
|
|
187
|
+
if (!navItem.elements?.[step.id]) {
|
|
188
|
+
const elements = navItem._origin?.elements ||
|
|
189
|
+
navItem._origin?.target?._artifact?.elements;
|
|
190
|
+
if (!elements)
|
|
191
|
+
break;
|
|
192
|
+
// Only link available path steps (navigation tree).
|
|
193
|
+
const origin = elements[step.id];
|
|
194
|
+
const member = linkToOrigin( origin, step.id, navItem, 'elements',
|
|
195
|
+
navItem.path?.location, true );
|
|
196
|
+
member.$inferred = 'expanded';
|
|
197
|
+
member.kind = '$navElement';
|
|
198
|
+
}
|
|
199
|
+
navItem = navItem.elements[step.id];
|
|
200
|
+
setLink( step, '_navigation', navItem );
|
|
201
|
+
++index;
|
|
202
|
+
}
|
|
203
|
+
// Last path step, if found, is a simple projection
|
|
204
|
+
if (index === path.length && navItem)
|
|
205
|
+
pushLink( navItem, '_projections', elem );
|
|
206
|
+
}
|
|
168
207
|
} );
|
|
169
208
|
}
|
|
170
209
|
}
|
|
@@ -319,13 +358,32 @@ function resolve( model ) {
|
|
|
319
358
|
const parent = art._parent;
|
|
320
359
|
const allowedInMain = [ 'entity', 'aspect', 'event' ].includes( adHocOrMainKind( art ) );
|
|
321
360
|
const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
361
|
+
|
|
362
|
+
// Check KEY (TODO: make this an extra function)
|
|
363
|
+
const { key } = art;
|
|
364
|
+
if (key?.val && !key.$inferred) {
|
|
365
|
+
// With unmanaged/composition as key, we complain at the `key` keyword, not
|
|
366
|
+
// the `on` condition / the aspect, because the easiest fix would be to
|
|
367
|
+
// simply remove the keyword. Text and message-id are accordingly.
|
|
368
|
+
// This fits nicely with exposing unmanaged/composition with explicit `key`.
|
|
369
|
+
// We do not complain about unmanaged/composition inside struct keys.
|
|
370
|
+
// (Actually, aspect compositions are not supported as sub elements anyway.)
|
|
371
|
+
if (getInheritedProp( art, 'targetAspect' )) {
|
|
372
|
+
error( 'def-invalid-key', [ key.location, art ], { '#': 'composition' } );
|
|
373
|
+
// TODO: test with managed composition exposed with explicit KEY
|
|
374
|
+
}
|
|
375
|
+
else if (art.target && getInheritedProp( art, 'on' )) {
|
|
376
|
+
error( 'def-invalid-key', [ key.location, art ], { '#': 'unmanaged' } );
|
|
377
|
+
}
|
|
378
|
+
else if (!allowedInMain || !isTopLevelElement) {
|
|
379
|
+
warning( 'def-unsupported-key', [ art.key.location, art ],
|
|
380
|
+
{ '#': allowedInMain ? 'sub' : 'std', keyword: 'key' }, {
|
|
381
|
+
std: '$(KEYWORD) is only supported for elements in an entity or an aspect',
|
|
382
|
+
sub: '$(KEYWORD) is only supported for top-level elements',
|
|
383
|
+
} );
|
|
384
|
+
}
|
|
328
385
|
}
|
|
386
|
+
|
|
329
387
|
if (art.targetAspect && !(allowedInMain && isTopLevelElement)) {
|
|
330
388
|
message( 'type-managed-composition', [ art.targetAspect.location, art ],
|
|
331
389
|
{ '#': allowedInMain ? 'sub' : 'std' } );
|
|
@@ -334,7 +392,7 @@ function resolve( model ) {
|
|
|
334
392
|
for (const include of art.includes) {
|
|
335
393
|
const struct = include._artifact;
|
|
336
394
|
if (struct && struct.kind !== 'type' && struct.elements &&
|
|
337
|
-
Object.values( struct.elements ).some( e => e.targetAspect)) {
|
|
395
|
+
Object.values( struct.elements ).some( e => e.targetAspect )) {
|
|
338
396
|
message( 'type-managed-composition', [ include.location, art ],
|
|
339
397
|
{ '#': struct.kind, art: struct } );
|
|
340
398
|
}
|
|
@@ -358,7 +416,7 @@ function resolve( model ) {
|
|
|
358
416
|
}
|
|
359
417
|
if (obj.items) { // TODO: make this a while in v2 (also items proxy)
|
|
360
418
|
obj = obj.items || obj; // the object which has type properties
|
|
361
|
-
effectiveType(obj);
|
|
419
|
+
effectiveType( obj );
|
|
362
420
|
}
|
|
363
421
|
if (obj.type) { // TODO: && !obj.type.$inferred ?
|
|
364
422
|
if (obj !== (art.returns || art)) // not already checked
|
|
@@ -385,13 +443,13 @@ function resolve( model ) {
|
|
|
385
443
|
if (elemtype.category === 'relation' && obj.type.path.length > 0 &&
|
|
386
444
|
!obj.target && !obj.targetAspect) {
|
|
387
445
|
const isCsn = (obj._block && obj._block.$frontend === 'json');
|
|
388
|
-
error('type-missing-target', [ obj.type.location, obj ],
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
446
|
+
error( 'type-missing-target', [ obj.type.location, obj ],
|
|
447
|
+
{ '#': isCsn ? 'csn' : 'std', type: elemtype.name.absolute }, {
|
|
448
|
+
// We don't say "use 'association to <target>" because the type could be used
|
|
449
|
+
// in action parameters, etc. as well.
|
|
450
|
+
std: 'The type $(TYPE) can\'t be used directly because it\'s compiler internal',
|
|
451
|
+
csn: 'Type $(TYPE) is missing a target',
|
|
452
|
+
} );
|
|
395
453
|
}
|
|
396
454
|
}
|
|
397
455
|
}
|
|
@@ -426,21 +484,26 @@ function resolve( model ) {
|
|
|
426
484
|
forEachGeneric( obj.targetAspect, 'elements', elem => dependsOnSilent( art, elem ) );
|
|
427
485
|
}
|
|
428
486
|
if (obj.foreignKeys) { // silent dependencies
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
487
|
+
// Avoid strange ref-cyclic if managed composition is key (check comes later)
|
|
488
|
+
// TODO: the following is already done by addImplicitForeignKeys()!
|
|
489
|
+
if (obj.$inferred !== 'aspect-composition') {
|
|
490
|
+
forEachGeneric( obj, 'foreignKeys', (elem) => {
|
|
491
|
+
dependsOnSilent( art, elem );
|
|
492
|
+
} );
|
|
493
|
+
}
|
|
432
494
|
addForeignKeyNavigations( art );
|
|
433
495
|
}
|
|
434
496
|
|
|
435
497
|
resolveExpr( art.default, 'default', art );
|
|
436
|
-
|
|
498
|
+
// TODO: distinguish not by $syntax (it is semantics), but whether in query
|
|
499
|
+
resolveExpr( art.value, (art.$syntax === 'calc' ? 'calc' : 'column'), art );
|
|
437
500
|
if (art.type?.$inferred === 'cast')
|
|
438
501
|
inferTypePropertiesFromCast( art );
|
|
439
502
|
if (art.value) {
|
|
440
503
|
if (art.$syntax === 'calc')
|
|
441
|
-
checkCalculatedElement(art);
|
|
504
|
+
checkCalculatedElement( art );
|
|
442
505
|
else if (art.type && !art.$inferred )
|
|
443
|
-
checkStructureCast(art);
|
|
506
|
+
checkStructureCast( art );
|
|
444
507
|
}
|
|
445
508
|
|
|
446
509
|
forEachMember( art, resolveRefs, art.targetAspect );
|
|
@@ -450,14 +513,16 @@ function resolve( model ) {
|
|
|
450
513
|
|
|
451
514
|
if (!ignoreSpecifiedElements && art.elements$ && art.elements) {
|
|
452
515
|
for (const id in art.elements$) {
|
|
453
|
-
resolveRefs(art.elements$[id]);
|
|
454
|
-
checkSpecifiedElement(art.elements[id], art.elements$[id]);
|
|
516
|
+
resolveRefs( art.elements$[id] );
|
|
517
|
+
checkSpecifiedElement( art.elements[id], art.elements$[id] );
|
|
455
518
|
}
|
|
456
519
|
}
|
|
457
520
|
|
|
458
521
|
/**
|
|
459
522
|
* Check whether the signature of the specified element matches that of the inferred one.
|
|
460
523
|
*
|
|
524
|
+
* TODO: resolveRefs() is already too long → do not add sub functions
|
|
525
|
+
*
|
|
461
526
|
* TODO:
|
|
462
527
|
* - This function has a lot of quite similar code blocks; it should be refactored to
|
|
463
528
|
* combine them.
|
|
@@ -474,96 +539,97 @@ function resolve( model ) {
|
|
|
474
539
|
|
|
475
540
|
// Check explicit types: If either side has one, so must the other.
|
|
476
541
|
const sType = specifiedElement.type?._artifact;
|
|
477
|
-
const iType = getInferredPropFromOrigin('type')?._artifact || inferredElement;
|
|
542
|
+
const iType = getInferredPropFromOrigin( 'type' )?._artifact || inferredElement;
|
|
478
543
|
|
|
479
544
|
// xor: could be missing a type;
|
|
480
545
|
// FIXME: The coding above returns incorrect iType for expand on associations
|
|
481
546
|
if (!specifiedElement.type && inferredElement.type) {
|
|
482
|
-
error('query-mismatched-element', [ specifiedElement.location, user ], {
|
|
547
|
+
error( 'query-mismatched-element', [ specifiedElement.location, user ], {
|
|
483
548
|
'#': !specifiedElement.type ? 'missing' : 'extra', name: user.name.id, prop: 'type',
|
|
484
|
-
});
|
|
549
|
+
} );
|
|
485
550
|
return;
|
|
486
551
|
}
|
|
487
552
|
// If specified type is `null`, type could not be resolved.
|
|
488
553
|
else if (sType && sType !== iType) {
|
|
489
554
|
const typeName = iType?.name && sType?.name ? 'typeName' : 'type';
|
|
490
|
-
|
|
555
|
+
const othertype = typeName !== 'type' && iType || '';
|
|
556
|
+
error( 'query-mismatched-element', [
|
|
491
557
|
specifiedElement.type.location || specifiedElement.location, user,
|
|
492
558
|
], {
|
|
493
559
|
'#': typeName,
|
|
494
560
|
name: user.name.id,
|
|
495
561
|
type: sType,
|
|
496
|
-
othertype
|
|
497
|
-
});
|
|
562
|
+
othertype,
|
|
563
|
+
} );
|
|
498
564
|
return;
|
|
499
565
|
}
|
|
500
566
|
|
|
501
567
|
// This relies on (element) expansion! Check that both sides have the following properties.
|
|
502
568
|
// On the inferred side, they are likely expanded.
|
|
503
|
-
if (!hasXorPropMismatch('elements') && !hasXorPropMismatch('items') &&
|
|
504
|
-
!hasXorPropMismatch('target') && !hasXorPropMismatch('enum')) {
|
|
569
|
+
if (!hasXorPropMismatch( 'elements' ) && !hasXorPropMismatch( 'items' ) &&
|
|
570
|
+
!hasXorPropMismatch( 'target' ) && !hasXorPropMismatch( 'enum' )) {
|
|
505
571
|
// Element are already traversed via elements$ merging.
|
|
506
572
|
|
|
507
573
|
// only check items, if the specified one is not expanded/inferred
|
|
508
574
|
if (specifiedElement.items && !specifiedElement.items.$inferred)
|
|
509
|
-
checkSpecifiedElement(inferredElement.items, specifiedElement.items, specifiedElement);
|
|
575
|
+
checkSpecifiedElement( inferredElement.items, specifiedElement.items, specifiedElement );
|
|
510
576
|
|
|
511
|
-
if (specifiedElement.target &&
|
|
577
|
+
if (specifiedElement.target?._artifact && inferredElement.target?._artifact &&
|
|
512
578
|
specifiedElement.target._artifact !== inferredElement.target._artifact) {
|
|
513
|
-
error('query-mismatched-element', [
|
|
579
|
+
error( 'query-mismatched-element', [
|
|
514
580
|
specifiedElement.target.location || specifiedElement.location, user,
|
|
515
581
|
], {
|
|
516
582
|
'#': 'target',
|
|
517
583
|
name: user.name.id,
|
|
518
584
|
target: specifiedElement.target,
|
|
519
585
|
art: inferredElement.target,
|
|
520
|
-
});
|
|
586
|
+
} );
|
|
521
587
|
}
|
|
522
588
|
|
|
523
589
|
if (specifiedElement.foreignKeys) {
|
|
524
|
-
const sKeys = Object.keys(specifiedElement.foreignKeys);
|
|
590
|
+
const sKeys = Object.keys( specifiedElement.foreignKeys );
|
|
525
591
|
/** @type {any} */
|
|
526
592
|
let iKeys = inferredElement;
|
|
527
593
|
if (inferredElement._effectiveType !== 0) {
|
|
528
594
|
while (iKeys._origin && !iKeys.foreignKeys)
|
|
529
595
|
iKeys = iKeys._origin;
|
|
530
596
|
}
|
|
531
|
-
iKeys = Object.keys(iKeys.foreignKeys || {});
|
|
532
|
-
if (sKeys.length !== iKeys.length || sKeys.some(
|
|
533
|
-
error('query-mismatched-element', [
|
|
597
|
+
iKeys = Object.keys( iKeys.foreignKeys || {} );
|
|
598
|
+
if (sKeys.length !== iKeys.length || sKeys.some( fkey => !iKeys.includes( fkey ) )) {
|
|
599
|
+
error( 'query-mismatched-element', [
|
|
534
600
|
specifiedElement.foreignKeys.location || specifiedElement.location, user,
|
|
535
601
|
], {
|
|
536
602
|
'#': 'foreignKeys',
|
|
537
603
|
name: user.name.id,
|
|
538
604
|
target: specifiedElement.target,
|
|
539
605
|
art: inferredElement.target,
|
|
540
|
-
});
|
|
606
|
+
} );
|
|
541
607
|
}
|
|
542
608
|
}
|
|
543
609
|
|
|
544
610
|
if (specifiedElement.virtual) {
|
|
545
|
-
const iVirtual = getInferredPropFromOrigin('virtual')?.val || false;
|
|
611
|
+
const iVirtual = getInferredPropFromOrigin( 'virtual' )?.val || false;
|
|
546
612
|
if (!specifiedElement.virtual.val !== !iVirtual) {
|
|
547
|
-
error('query-mismatched-element', [
|
|
613
|
+
error( 'query-mismatched-element', [
|
|
548
614
|
specifiedElement.virtual.location || specifiedElement.location, user,
|
|
549
615
|
], {
|
|
550
616
|
'#': 'prop', prop: 'virtual', name: user.name.id,
|
|
551
|
-
});
|
|
617
|
+
} );
|
|
552
618
|
}
|
|
553
619
|
}
|
|
554
620
|
|
|
555
621
|
// If cardinality is not specified, the compiler uses the inferred one.
|
|
556
622
|
if (specifiedElement.cardinality) {
|
|
557
623
|
const sCardinality = specifiedElement.cardinality;
|
|
558
|
-
const iCardinality = getInferredPropFromOrigin('cardinality');
|
|
624
|
+
const iCardinality = getInferredPropFromOrigin( 'cardinality' );
|
|
559
625
|
if (!iCardinality) {
|
|
560
|
-
error('query-mismatched-element', [
|
|
626
|
+
error( 'query-mismatched-element', [
|
|
561
627
|
sCardinality.location || specifiedElement.location, user,
|
|
562
628
|
], {
|
|
563
629
|
'#': 'extra',
|
|
564
630
|
prop: 'cardinality',
|
|
565
631
|
name: user.name.id,
|
|
566
|
-
});
|
|
632
|
+
} );
|
|
567
633
|
}
|
|
568
634
|
else {
|
|
569
635
|
// Note: Cardinality does not have sourceMin (CSN "srcmin").
|
|
@@ -575,7 +641,7 @@ function resolve( model ) {
|
|
|
575
641
|
for (const prop in props) {
|
|
576
642
|
if (sCardinality[prop]?.val === iCardinality[prop]?.val)
|
|
577
643
|
continue;
|
|
578
|
-
error('query-mismatched-element', [
|
|
644
|
+
error( 'query-mismatched-element', [
|
|
579
645
|
sCardinality[prop]?.location || sCardinality.location || specifiedElement.location,
|
|
580
646
|
user,
|
|
581
647
|
], {
|
|
@@ -583,53 +649,59 @@ function resolve( model ) {
|
|
|
583
649
|
'#': !sCardinality[prop] ? 'missing' : (iCardinality[prop] ? 'prop' : 'extra'),
|
|
584
650
|
prop: `cardinality.${ props[prop] }`,
|
|
585
651
|
name: user.name.id,
|
|
586
|
-
});
|
|
652
|
+
} );
|
|
587
653
|
}
|
|
588
654
|
}
|
|
589
655
|
}
|
|
590
656
|
|
|
591
657
|
if (specifiedElement.value) {
|
|
592
|
-
error('query-unexpected-property', [
|
|
658
|
+
error( 'query-unexpected-property', [
|
|
593
659
|
specifiedElement.value.location || specifiedElement.location, user,
|
|
594
660
|
], {
|
|
595
661
|
'#': 'calculatedElement', prop: 'value', name: user.name.id,
|
|
596
|
-
});
|
|
662
|
+
} );
|
|
597
663
|
}
|
|
598
664
|
|
|
599
665
|
if (specifiedElement.key) { // TODO: `|| inferredElement.key?.val`, once to.sql is fixed
|
|
600
666
|
// TODO: Do not use _origin chain for key; has been propagated in propagateKeyProps().
|
|
601
|
-
const iKey = getInferredPropFromOrigin('key')?.val;
|
|
667
|
+
const iKey = getInferredPropFromOrigin( 'key' )?.val;
|
|
602
668
|
// If "key" is specified or truthy in the inferred element, the values must match.
|
|
603
669
|
if (!iKey !== !specifiedElement.key?.val) {
|
|
604
|
-
error('query-mismatched-element', [
|
|
670
|
+
error( 'query-mismatched-element', [
|
|
605
671
|
specifiedElement.key?.location || specifiedElement.location, user,
|
|
606
672
|
], {
|
|
607
673
|
'#': specifiedElement.key ? 'prop' : 'missing', prop: 'key', name: user.name.id,
|
|
608
|
-
});
|
|
674
|
+
} );
|
|
609
675
|
}
|
|
610
676
|
}
|
|
611
677
|
|
|
612
|
-
if (specifiedElement.enum) {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
678
|
+
if (specifiedElement.enum && !specifiedElement.$expand) {
|
|
679
|
+
// TODO: ".value" is necessary due to recompilation: The compiler does not copy
|
|
680
|
+
// "enum" out of ".value", i.e. casts, only "type", changing the _effectiveType.
|
|
681
|
+
const iEnumValues = inferredElement.enum || inferredElement.value?.enum;
|
|
682
|
+
const sEnumValues = specifiedElement.enum;
|
|
683
|
+
for (const name in specifiedElement.enum) {
|
|
684
|
+
// TODO: See TODO above; issue is cast()
|
|
685
|
+
const sEnumEntry = sEnumValues[name];
|
|
686
|
+
const iEnumEntry = iEnumValues[name]?._effectiveType || iEnumValues[name];
|
|
687
|
+
if (!iEnumEntry) {
|
|
688
|
+
error( 'query-mismatched-element', [ specifiedElement.location, user ], {
|
|
689
|
+
'#': 'enumExtra', name: user.name.id, id: name,
|
|
690
|
+
} );
|
|
691
|
+
break;
|
|
622
692
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
693
|
+
else {
|
|
694
|
+
// We allow implicit `val: "<name>"`.
|
|
695
|
+
const iVal = iEnumEntry.value?.val || iEnumEntry.value?.['#'] || name;
|
|
696
|
+
const sVal = sEnumEntry.value?.val || sEnumEntry.value?.['#'] || name;
|
|
697
|
+
if (iVal !== sVal) {
|
|
698
|
+
error( 'query-mismatched-element', [ specifiedElement.location, user ], {
|
|
699
|
+
'#': 'enumVal', name: user.name.id, id: name,
|
|
700
|
+
} );
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
633
705
|
}
|
|
634
706
|
}
|
|
635
707
|
|
|
@@ -642,7 +714,7 @@ function resolve( model ) {
|
|
|
642
714
|
!inferredElement.value?.[prop] !== !specifiedElement[prop]) {
|
|
643
715
|
error( 'query-mismatched-element', [ specifiedElement.location, specifiedElement ], {
|
|
644
716
|
'#': specifiedElement[prop] ? 'extra' : 'missing', name: user.name.id, prop,
|
|
645
|
-
});
|
|
717
|
+
} );
|
|
646
718
|
return true;
|
|
647
719
|
}
|
|
648
720
|
return false;
|
|
@@ -652,8 +724,8 @@ function resolve( model ) {
|
|
|
652
724
|
// Inferred property via _origin chain (0 === circular).
|
|
653
725
|
let element = inferredElement;
|
|
654
726
|
if (element._effectiveType !== 0) {
|
|
655
|
-
while (getOrigin(element) && !element[prop])
|
|
656
|
-
element = getOrigin(element);
|
|
727
|
+
while (getOrigin( element ) && !element[prop])
|
|
728
|
+
element = getOrigin( element );
|
|
657
729
|
}
|
|
658
730
|
return element[prop];
|
|
659
731
|
}
|
|
@@ -691,21 +763,21 @@ function resolve( model ) {
|
|
|
691
763
|
while (parent.kind === 'element')
|
|
692
764
|
parent = parent._parent;
|
|
693
765
|
|
|
694
|
-
if (!allowedInKind.includes(art._main.kind)) {
|
|
766
|
+
if (!allowedInKind.includes( art._main.kind )) {
|
|
695
767
|
if (art.$inferred === 'include') {
|
|
696
768
|
// even for include-chains, we find the correct ref due to element-expansion.
|
|
697
|
-
const include = art._main.includes.find(i => i._artifact === art._origin._main);
|
|
698
|
-
error('ref-invalid-calc-elem', [ include.location || art.value.location, art ],
|
|
699
|
-
|
|
769
|
+
const include = art._main.includes.find( i => i._artifact === art._origin._main );
|
|
770
|
+
error( 'ref-invalid-calc-elem', [ include.location || art.value.location, art ],
|
|
771
|
+
{ '#': art._main.kind } );
|
|
700
772
|
}
|
|
701
773
|
else {
|
|
702
774
|
error( 'def-invalid-calc-elem', loc, { '#': art._main.kind } );
|
|
703
775
|
}
|
|
704
776
|
}
|
|
705
|
-
else if (!allowedInKind.includes(parent.kind)) {
|
|
777
|
+
else if (!allowedInKind.includes( parent.kind )) {
|
|
706
778
|
error( 'def-invalid-calc-elem', loc, { '#': parent.kind } );
|
|
707
779
|
}
|
|
708
|
-
else if (effectiveType(art)?.elements) {
|
|
780
|
+
else if (effectiveType( art )?.elements) {
|
|
709
781
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
710
782
|
if (!art.$inferred) {
|
|
711
783
|
if (art.type)
|
|
@@ -714,7 +786,7 @@ function resolve( model ) {
|
|
|
714
786
|
error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
|
|
715
787
|
}
|
|
716
788
|
}
|
|
717
|
-
else if (effectiveType(art)?.items) {
|
|
789
|
+
else if (effectiveType( art )?.items) {
|
|
718
790
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
719
791
|
if (!art.$inferred) {
|
|
720
792
|
const isCast = art.type?.$inferred === 'cast';
|
|
@@ -730,7 +802,7 @@ function resolve( model ) {
|
|
|
730
802
|
if (art[prop]?.val) {
|
|
731
803
|
// probably better than a parse error (which is good for DEFAULT vs calc),
|
|
732
804
|
// also appears with parse-cdl:
|
|
733
|
-
error('def-invalid-calc-elem', loc, { '#': prop });
|
|
805
|
+
error( 'def-invalid-calc-elem', loc, { '#': prop } );
|
|
734
806
|
return; // one error is enough
|
|
735
807
|
}
|
|
736
808
|
}
|
|
@@ -742,22 +814,19 @@ function resolve( model ) {
|
|
|
742
814
|
? art.value.args[0]?._artifact
|
|
743
815
|
: art.value._artifact;
|
|
744
816
|
if (elem && art.type) { // has explicit type
|
|
745
|
-
if (art.type._artifact?.elements)
|
|
746
|
-
error('type-cast
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
else if (elem.elements) { // TODO: calc elements
|
|
750
|
-
error('type-cast-structured', [ art.type.location, art ], {},
|
|
751
|
-
'Structured elements can\'t be cast to a different type');
|
|
752
|
-
}
|
|
817
|
+
if (art.type._artifact?.elements)
|
|
818
|
+
error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'to-structure' } );
|
|
819
|
+
else if (elem.elements) // TODO: calc elements
|
|
820
|
+
error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'from-structure' } );
|
|
753
821
|
}
|
|
754
822
|
}
|
|
755
823
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
824
|
+
/**
|
|
825
|
+
* Return type containing the assoc spec (keys, on); note that no
|
|
826
|
+
* propagation/rewrite has been done yet, cyclic dependency must have been
|
|
827
|
+
* checked before!
|
|
828
|
+
*/
|
|
759
829
|
function getAssocSpec( type ) {
|
|
760
|
-
// only to be called without cycles
|
|
761
830
|
let unmanaged = null;
|
|
762
831
|
while (type) {
|
|
763
832
|
if (type.on) // if unmanaged, continue trying to find targetAspect
|
|
@@ -793,7 +862,7 @@ function resolve( model ) {
|
|
|
793
862
|
} );
|
|
794
863
|
// if (!query.$inlines) console.log('RQ:',query)
|
|
795
864
|
for (const col of query.$inlines)
|
|
796
|
-
resolveExpr( col.value, '
|
|
865
|
+
resolveExpr( col.value, 'column', col );
|
|
797
866
|
// for (const col of query.$inlines)
|
|
798
867
|
// if (!col.value.path) throw new CompilerAssertion(col.name.element)
|
|
799
868
|
if (query !== query._main._leadingQuery) // will be done later
|
|
@@ -801,22 +870,27 @@ function resolve( model ) {
|
|
|
801
870
|
if (query.from)
|
|
802
871
|
resolveJoinOn( query.from );
|
|
803
872
|
if (query.where)
|
|
804
|
-
resolveExpr( query.where, '
|
|
873
|
+
resolveExpr( query.where, 'where', query );
|
|
805
874
|
if (query.groupBy)
|
|
806
|
-
resolveBy( query.groupBy, '
|
|
807
|
-
resolveExpr( query.having, '
|
|
875
|
+
resolveBy( query.groupBy, 'groupBy', 'groupBy' );
|
|
876
|
+
resolveExpr( query.having, 'having', query );
|
|
808
877
|
if (query.$orderBy) // ORDER BY from UNION:
|
|
809
878
|
// TODO clarify: can I access the tab alias of outer queries? If not:
|
|
810
879
|
// 4th arg query._main instead query._parent.
|
|
811
|
-
resolveBy( query.$orderBy, '
|
|
880
|
+
resolveBy( query.$orderBy, 'orderBy-set-ref', 'orderBy-set-expr' );
|
|
812
881
|
if (query.orderBy) { // ORDER BY
|
|
813
882
|
// search in `query.elements` after having checked table aliases of the current query
|
|
814
|
-
resolveBy( query.orderBy, '
|
|
883
|
+
resolveBy( query.orderBy, 'orderBy-ref', 'orderBy-expr' );
|
|
815
884
|
// TODO: disallow resulting element ref if in expression!
|
|
816
885
|
// Necessary to check it in the compiler as it might work with other semantics on DB!
|
|
817
886
|
// (we could downgrade it to a warning if name is equal to unique source element name)
|
|
818
887
|
// TODO: Some helping text mentioning an alias name would be useful
|
|
819
888
|
}
|
|
889
|
+
for (const limit of query.$limit || []) // LIMIT from UNION:
|
|
890
|
+
resolveLimit( limit );
|
|
891
|
+
if (query.limit)
|
|
892
|
+
resolveLimit( query.limit );
|
|
893
|
+
|
|
820
894
|
return;
|
|
821
895
|
|
|
822
896
|
function resolveJoinOn( join ) {
|
|
@@ -824,27 +898,35 @@ function resolve( model ) {
|
|
|
824
898
|
for (const j of join.args)
|
|
825
899
|
resolveJoinOn( j );
|
|
826
900
|
if (join.on)
|
|
827
|
-
resolveExpr( join.on, '
|
|
828
|
-
// TODO: check restrictions according to join "query"
|
|
901
|
+
resolveExpr( join.on, 'join-on', join );
|
|
829
902
|
}
|
|
830
903
|
}
|
|
831
904
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
905
|
+
/**
|
|
906
|
+
* Note the strange name resolution (dynamic part) for ORDER BY: the same
|
|
907
|
+
* as for select items if it is an expression, but first look at select
|
|
908
|
+
* item alias (i.e. like `$projection.NAME` if it is a path. If it is an
|
|
909
|
+
* ORDER BY of an UNION, do not allow any dynamic path in an expression,
|
|
910
|
+
* and only allow the elements of the leading query if it is a path.
|
|
911
|
+
*
|
|
912
|
+
* This seems to be similar, but different in SQLite 3.22.0: ORDER BY seems
|
|
913
|
+
* to bind stronger than UNION (see <SQLite>/src/parse.y), and the name
|
|
914
|
+
* resolution seems to use select item aliases from all SELECTs of the
|
|
915
|
+
* UNION (see <SQLite>/test/tkt2822.test).
|
|
916
|
+
*/
|
|
842
917
|
function resolveBy( array, refMode, exprMode ) {
|
|
843
918
|
for (const value of array ) {
|
|
844
919
|
if (value)
|
|
845
920
|
resolveExpr( value, (value.path ? refMode : exprMode), query );
|
|
846
921
|
}
|
|
847
922
|
}
|
|
923
|
+
|
|
924
|
+
function resolveLimit( limit ) {
|
|
925
|
+
if (limit.rows)
|
|
926
|
+
resolveExpr( limit.rows, 'limit-rows', query );
|
|
927
|
+
if (limit.offset)
|
|
928
|
+
resolveExpr( limit.offset, 'limit-offset', query );
|
|
929
|
+
}
|
|
848
930
|
}
|
|
849
931
|
|
|
850
932
|
function resolveTarget( art, obj ) {
|
|
@@ -875,7 +957,7 @@ function resolve( model ) {
|
|
|
875
957
|
{ '#': isComposition ? 'comp' : 'std' }, {
|
|
876
958
|
std: 'An unmanaged association can\'t be defined as type',
|
|
877
959
|
comp: 'An unmanaged composition can\'t be defined as type',
|
|
878
|
-
});
|
|
960
|
+
} );
|
|
879
961
|
// TODO: also warning if inside structure
|
|
880
962
|
}
|
|
881
963
|
else {
|
|
@@ -933,7 +1015,7 @@ function resolve( model ) {
|
|
|
933
1015
|
const serviceKeys = keyElementNames( issue.target.elements );
|
|
934
1016
|
const modelKeys = keyElementNames( modelTarget.elements );
|
|
935
1017
|
if (modelKeys.length !== serviceKeys.length) {
|
|
936
|
-
issue.id = modelKeys.find( id => !serviceKeys.includes( id ));
|
|
1018
|
+
issue.id = modelKeys.find( id => !serviceKeys.includes( id ) );
|
|
937
1019
|
issue['#'] = 'missing';
|
|
938
1020
|
}
|
|
939
1021
|
else if (!modelKeys.every( (id, index) => id === serviceKeys[index] )) {
|
|
@@ -986,7 +1068,7 @@ function resolve( model ) {
|
|
|
986
1068
|
}
|
|
987
1069
|
|
|
988
1070
|
function addImplicitForeignKeys( art, obj, target ) {
|
|
989
|
-
obj.foreignKeys = Object.create(null);
|
|
1071
|
+
obj.foreignKeys = Object.create( null );
|
|
990
1072
|
forEachInOrder( target, 'elements', ( elem, name ) => {
|
|
991
1073
|
if (elem.key && elem.key.val) {
|
|
992
1074
|
const { location } = obj.target;
|
|
@@ -1002,22 +1084,59 @@ function resolve( model ) {
|
|
|
1002
1084
|
// the following should be done automatically, since we run resolveRefs after that
|
|
1003
1085
|
setArtifactLink( key.targetElement, elem );
|
|
1004
1086
|
setArtifactLink( key.targetElement.path[0], elem );
|
|
1005
|
-
setLink( key, '_effectiveType', effectiveType(elem) );
|
|
1006
|
-
dependsOn(key, elem, location);
|
|
1007
|
-
|
|
1087
|
+
setLink( key, '_effectiveType', effectiveType( elem ) );
|
|
1088
|
+
dependsOn( key, elem, location );
|
|
1089
|
+
// TODO TMP: instead, make managed composition of aspects and unmanaged
|
|
1090
|
+
// assocs not depend on their `on` condition (empty `_deps` after resolve)
|
|
1091
|
+
if (art.$inferred !== 'aspect-composition')
|
|
1092
|
+
dependsOnSilent( art, key );
|
|
1008
1093
|
}
|
|
1009
|
-
});
|
|
1094
|
+
} );
|
|
1010
1095
|
obj.foreignKeys[$inferred] = 'keys';
|
|
1011
1096
|
}
|
|
1012
1097
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1098
|
+
/**
|
|
1099
|
+
* Add reference tree from foreign key reference back to foreign key of association.
|
|
1100
|
+
*
|
|
1101
|
+
* For `type T: Association to Target { foo as bar, elem.sub }`, this function adds:
|
|
1102
|
+
*
|
|
1103
|
+
* '$keysNavigation': {
|
|
1104
|
+
* foo: { _artifact: 'type:“T”/key:“bar”' },
|
|
1105
|
+
* elem: {
|
|
1106
|
+
* '$keysNavigation': { sub: { _artifact: 'type:“T”/key:“sub”' } }
|
|
1107
|
+
* }
|
|
1108
|
+
*
|
|
1109
|
+
* This function complains if two foreign keys point to the same target element
|
|
1110
|
+
* (`Association to Target { foo as bar, foo }`) or overlapping target elements
|
|
1111
|
+
* (`Association to Target { elem.sub, elem }`). In `resolvePath`, the compiler
|
|
1112
|
+
* already forbids to follow associations in foreign key refs.
|
|
1113
|
+
*
|
|
1114
|
+
* This ref tree could also be used in a core-compiler check which is now part
|
|
1115
|
+
* of to.sql: refs in the `on` condition of unmanaged associations cannot follow
|
|
1116
|
+
* associations other to foreign key refs.
|
|
1117
|
+
*
|
|
1118
|
+
* This ref tree is only created for originally defined managed associations
|
|
1119
|
+
* (including those created by the compiler, like the `up_` association), not
|
|
1120
|
+
* for derived association like for `type DerivedT: T`, or exposed ones.
|
|
1121
|
+
*/
|
|
1122
|
+
function addForeignKeyNavigations( art, silent = false ) {
|
|
1123
|
+
art.$keysNavigation = Object.create( null );
|
|
1124
|
+
const keys = [];
|
|
1125
|
+
// Basically sort foreign keys according to length of target element ref.
|
|
1126
|
+
// This way, we complain about ref to sub element (`elem.sub`) even if it
|
|
1127
|
+
// comes earlier than the ref to structure element (`elem`).
|
|
1015
1128
|
forEachGeneric( art, 'foreignKeys', ( key ) => {
|
|
1016
|
-
|
|
1017
|
-
|
|
1129
|
+
const path = key.targetElement?.path;
|
|
1130
|
+
if (path) {
|
|
1131
|
+
const arr = keys[path.length] || (keys[path.length] = []);
|
|
1132
|
+
arr.push( key );
|
|
1133
|
+
}
|
|
1134
|
+
} );
|
|
1135
|
+
for (const key of keys.flat()) {
|
|
1018
1136
|
let dict = art.$keysNavigation;
|
|
1019
|
-
const
|
|
1020
|
-
|
|
1137
|
+
const { path } = key.targetElement;
|
|
1138
|
+
const last = path[path.length - 1];
|
|
1139
|
+
for (const item of path) {
|
|
1021
1140
|
let nav = dict[item.id];
|
|
1022
1141
|
if (!nav) {
|
|
1023
1142
|
nav = {};
|
|
@@ -1025,16 +1144,24 @@ function resolve( model ) {
|
|
|
1025
1144
|
if (item === last)
|
|
1026
1145
|
setArtifactLink( nav, key );
|
|
1027
1146
|
else
|
|
1028
|
-
nav.$keysNavigation = Object.create(null);
|
|
1147
|
+
nav.$keysNavigation = Object.create( null );
|
|
1029
1148
|
}
|
|
1030
1149
|
else if (item === last || nav._artifact) {
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1150
|
+
if (silent)
|
|
1151
|
+
break;
|
|
1152
|
+
const name = nav._artifact?.name.id;
|
|
1153
|
+
const text = (item !== last) ? 'sub' : 'std';
|
|
1154
|
+
error( 'duplicate-key-ref', [ item.location, key ], { '#': text, name }, {
|
|
1155
|
+
std: 'Foreign key $(NAME) already refers to the same target element',
|
|
1156
|
+
// eslint-disable-next-line max-len
|
|
1157
|
+
sub: 'Foreign key $(NAME) already refers to the target element whose sub element is again referred to here',
|
|
1158
|
+
// TODO: please add ideas for a better text, e.g. to (closed) PR #11325
|
|
1159
|
+
} );
|
|
1160
|
+
break;
|
|
1034
1161
|
}
|
|
1035
1162
|
dict = nav.$keysNavigation;
|
|
1036
1163
|
}
|
|
1037
|
-
}
|
|
1164
|
+
}
|
|
1038
1165
|
}
|
|
1039
1166
|
|
|
1040
1167
|
// TODO: add this somehow to tweak-assocs.js ?
|
|
@@ -1067,6 +1194,8 @@ function resolve( model ) {
|
|
|
1067
1194
|
}
|
|
1068
1195
|
}
|
|
1069
1196
|
const origTarget = origType.target._artifact;
|
|
1197
|
+
// console.log(require('../model/revealInternalProperties').ref(elem),
|
|
1198
|
+
// !!origTarget,!!origType._effectiveType,!!origType.target)
|
|
1070
1199
|
if (!origTarget || !target)
|
|
1071
1200
|
return;
|
|
1072
1201
|
|
|
@@ -1100,7 +1229,7 @@ function resolve( model ) {
|
|
|
1100
1229
|
target: 'The redirected target $(ART) is a complex view',
|
|
1101
1230
|
// eslint-disable-next-line max-len
|
|
1102
1231
|
targetOp: 'The redirected target $(ART) is a complex view with $(KEYWORD)',
|
|
1103
|
-
});
|
|
1232
|
+
} );
|
|
1104
1233
|
break;
|
|
1105
1234
|
}
|
|
1106
1235
|
target = from._artifact;
|
|
@@ -1116,7 +1245,7 @@ function resolve( model ) {
|
|
|
1116
1245
|
let redirected = null;
|
|
1117
1246
|
chain.reverse();
|
|
1118
1247
|
let news = [ { chain, sources: [ target ] } ];
|
|
1119
|
-
const dict = Object.create(null);
|
|
1248
|
+
const dict = Object.create( null );
|
|
1120
1249
|
while (news.length) {
|
|
1121
1250
|
const outer = news;
|
|
1122
1251
|
news = [];
|
|
@@ -1194,65 +1323,9 @@ function resolve( model ) {
|
|
|
1194
1323
|
|
|
1195
1324
|
// Resolve the type and its arguments if applicable.
|
|
1196
1325
|
function resolveTypeExpr( art, user ) {
|
|
1197
|
-
const typeArt =
|
|
1198
|
-
if (typeArt)
|
|
1326
|
+
const typeArt = resolvePath( art.type, 'type', user );
|
|
1327
|
+
if (typeArt)
|
|
1199
1328
|
resolveTypeArgumentsUnchecked( art, typeArt, user );
|
|
1200
|
-
checkTypeArguments( art, typeArt );
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
/**
|
|
1205
|
-
* Check the type arguments on `artWithType`.
|
|
1206
|
-
* If the effective type is an array or structured type, an error is emitted.
|
|
1207
|
-
*/
|
|
1208
|
-
function checkTypeArguments( artWithType, typeArt ) {
|
|
1209
|
-
// Note: `_effectiveType` point to `artWithType` itself, if it is an enum type,
|
|
1210
|
-
// descend to the origin in this case.
|
|
1211
|
-
// TODO: this function is not complete(!): parallel `elements` and `length`, … - rework function
|
|
1212
|
-
// TODO: check relationship with resolveTypeArgumentsUnchecked()
|
|
1213
|
-
let effectiveTypeArt = effectiveType( typeArt );
|
|
1214
|
-
while (effectiveTypeArt?.enum)
|
|
1215
|
-
effectiveTypeArt = effectiveType( getOrigin( effectiveTypeArt ) );
|
|
1216
|
-
if (!effectiveTypeArt)
|
|
1217
|
-
return; // e.g. illegal definition references, cycles, ...
|
|
1218
|
-
|
|
1219
|
-
const params = effectiveTypeArt.parameters &&
|
|
1220
|
-
effectiveTypeArt.parameters.map(p => p.name || p) || [];
|
|
1221
|
-
|
|
1222
|
-
for (const param of typeParameters.list) {
|
|
1223
|
-
if (artWithType[param] !== undefined) {
|
|
1224
|
-
if (!params.includes(param)) {
|
|
1225
|
-
// Whether the type ref itself is a builtin or a custom type with a builtin as base.
|
|
1226
|
-
const type = getOrigin(artWithType);
|
|
1227
|
-
|
|
1228
|
-
let variant;
|
|
1229
|
-
if (type.builtin)
|
|
1230
|
-
// `.type` is already a builtin: use a nicer message.
|
|
1231
|
-
variant = 'builtin';
|
|
1232
|
-
else if (effectiveTypeArt.builtin)
|
|
1233
|
-
// base type is a builtin, i.e. a scalar
|
|
1234
|
-
variant = 'type';
|
|
1235
|
-
else
|
|
1236
|
-
// effectiveType is not a builtin -> array or structured
|
|
1237
|
-
variant = 'non-scalar';
|
|
1238
|
-
|
|
1239
|
-
// console.log(typeArt.name,artWithType.name,effectiveTypeArt.name)
|
|
1240
|
-
error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
|
|
1241
|
-
'#': variant, prop: param, art: artWithType.type, type: effectiveTypeArt,
|
|
1242
|
-
});
|
|
1243
|
-
break; // Avoid spam: Only emit the first error.
|
|
1244
|
-
}
|
|
1245
|
-
else if (!typeParameters.expectedLiteralsFor[param].includes(artWithType[param].literal)) {
|
|
1246
|
-
error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
|
|
1247
|
-
'#': 'incorrect-type',
|
|
1248
|
-
prop: param,
|
|
1249
|
-
code: artWithType[param].literal,
|
|
1250
|
-
names: typeParameters.expectedLiteralsFor[param],
|
|
1251
|
-
});
|
|
1252
|
-
break; // Avoid spam: Only emit the first error.
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
1329
|
}
|
|
1257
1330
|
|
|
1258
1331
|
function resolveExpr( expr, exprCtx, user ) {
|
|
@@ -1264,6 +1337,7 @@ function resolve( model ) {
|
|
|
1264
1337
|
resolveTypeExpr( expr, user._user || user );
|
|
1265
1338
|
|
|
1266
1339
|
if (expr.path) {
|
|
1340
|
+
// TODO: re-think this $expected: 'exists' thing
|
|
1267
1341
|
if (expr.$expected === 'exists') {
|
|
1268
1342
|
error( 'expr-unexpected-exists', [ expr.location, user ], {},
|
|
1269
1343
|
'An EXISTS predicate is not expected here' );
|
|
@@ -1296,7 +1370,7 @@ function resolve( model ) {
|
|
|
1296
1370
|
function resolveParamsAndWhere( step, expected, user, isLast ) {
|
|
1297
1371
|
const alias = (step._navigation?.kind === '$tableAlias') ? step._navigation : null;
|
|
1298
1372
|
const type = alias || effectiveType( step._artifact );
|
|
1299
|
-
const art =
|
|
1373
|
+
const art = type?.target ? type.target._artifact : type;
|
|
1300
1374
|
if (!art)
|
|
1301
1375
|
return;
|
|
1302
1376
|
const entity = art.kind === 'entity' &&
|
|
@@ -1317,7 +1391,7 @@ function resolve( model ) {
|
|
|
1317
1391
|
if (expected === 'from')
|
|
1318
1392
|
variant = 'from';
|
|
1319
1393
|
// XSN TODO: filter$location including […]
|
|
1320
|
-
message( 'expr-
|
|
1394
|
+
message( 'expr-unexpected-filter', [ location, user ], { '#': variant }, {
|
|
1321
1395
|
std: 'A filter can only be provided when navigating along associations',
|
|
1322
1396
|
// to help users for `… from E:toF { toF[…].x }`
|
|
1323
1397
|
// eslint-disable-next-line max-len
|
|
@@ -1329,26 +1403,26 @@ function resolve( model ) {
|
|
|
1329
1403
|
|
|
1330
1404
|
function resolveParams( dict, art, entity, expected, user, stepLocation ) {
|
|
1331
1405
|
if (!entity || !entity.params) {
|
|
1332
|
-
let first = dict[Object.keys(dict)[0]];
|
|
1333
|
-
if (Array.isArray(first))
|
|
1406
|
+
let first = dict[Object.keys( dict )[0]];
|
|
1407
|
+
if (Array.isArray( first ))
|
|
1334
1408
|
first = first[0];
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
dictLocation( dict, first && first.name && first.name.location || stepLocation),
|
|
1409
|
+
error( 'expr-unexpected-argument',
|
|
1410
|
+
[ dict[$location] || dictLocation( dict, first?.name?.location || stepLocation ),
|
|
1338
1411
|
user ],
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1412
|
+
{ art, '#': (entity ? 'entity' : expected ) },
|
|
1413
|
+
{
|
|
1414
|
+
std: 'Parameters can only be provided when navigating along associations',
|
|
1415
|
+
from: 'Parameters can only be provided for the source entity or associations',
|
|
1416
|
+
// or extra message id for entity?
|
|
1417
|
+
entity: 'Unexpected arguments for entity $(ART) without parameters',
|
|
1418
|
+
} );
|
|
1346
1419
|
return;
|
|
1347
1420
|
}
|
|
1348
|
-
const exp = (expected === 'from') ? '
|
|
1349
|
-
if (Array.isArray(dict)) {
|
|
1350
|
-
|
|
1351
|
-
|
|
1421
|
+
const exp = (expected === 'from') ? 'from-args' : expected;
|
|
1422
|
+
if (Array.isArray( dict )) {
|
|
1423
|
+
const loc = [ dict[0] && dict[0].location || stepLocation, user ];
|
|
1424
|
+
error( 'expr-expected-named-argument', loc, {},
|
|
1425
|
+
'Expected named parameters for the entity' );
|
|
1352
1426
|
for (const a of dict)
|
|
1353
1427
|
resolveExpr( a, exp, user );
|
|
1354
1428
|
return;
|
|
@@ -1357,33 +1431,34 @@ function resolve( model ) {
|
|
|
1357
1431
|
for (const name in dict) {
|
|
1358
1432
|
const param = art.params[name];
|
|
1359
1433
|
const arg = dict[name];
|
|
1360
|
-
for (const a of Array.isArray(arg) ? arg : [ arg ]) {
|
|
1434
|
+
for (const a of Array.isArray( arg ) ? arg : [ arg ]) {
|
|
1361
1435
|
setArtifactLink( a.name, param );
|
|
1362
1436
|
if (!param) {
|
|
1363
|
-
|
|
1364
|
-
|
|
1437
|
+
error( 'expr-undefined-param', [ a.name.location, user ], { art, id: name },
|
|
1438
|
+
'Entity $(ART) has no parameter $(ID)' );
|
|
1365
1439
|
}
|
|
1366
|
-
|
|
1367
|
-
resolveExpr( a, (expected === 'from') ? 'param-only' : exp, user );
|
|
1440
|
+
resolveExpr( a, exp, user );
|
|
1368
1441
|
}
|
|
1369
1442
|
}
|
|
1370
1443
|
}
|
|
1371
1444
|
}
|
|
1372
1445
|
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1446
|
+
/**
|
|
1447
|
+
* Return condensed info about reference in select item
|
|
1448
|
+
* - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
|
|
1449
|
+
* - sourceElem (in query) -> { navigation: navElem, item: path[0], tableAlias }
|
|
1450
|
+
* - mixinElem -> { navigation: mixinElement, item: path[0] }
|
|
1451
|
+
* - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
|
|
1452
|
+
* - $self -> { item: undefined, tableAlias: $self }
|
|
1453
|
+
* - $parameters.P, :P -> {}
|
|
1454
|
+
* - $now, current_date -> {}
|
|
1455
|
+
* - undef, redef -> {}
|
|
1456
|
+
* With 'navigation': store that navigation._artifact is projected
|
|
1457
|
+
* With 'navigation': rewrite its ON condition
|
|
1458
|
+
* With navigation: Do KEY propagation
|
|
1459
|
+
*
|
|
1460
|
+
* TODO: re-think this function, copied in populate.js and tweak-assocs.js
|
|
1461
|
+
*/
|
|
1387
1462
|
function pathNavigation( ref ) {
|
|
1388
1463
|
// currently, indirectly projectable elements are not included - we might
|
|
1389
1464
|
// keep it this way! If we want them to be included - be aware: cycles
|