@sap/cds-compiler 2.4.4 → 2.10.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 +241 -1
- package/bin/.eslintrc.json +17 -0
- package/bin/cds_update_identifiers.js +8 -7
- package/bin/cdsc.js +180 -132
- package/bin/cdshi.js +18 -11
- package/bin/cdsse.js +38 -32
- package/bin/cdsv2m.js +8 -7
- package/doc/CHANGELOG_BETA.md +36 -1
- package/lib/api/main.js +81 -100
- package/lib/api/options.js +17 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/location.js +2 -2
- package/lib/base/message-registry.js +66 -4
- package/lib/base/messages.js +84 -27
- package/lib/base/model.js +2 -61
- package/lib/checks/arrayOfs.js +0 -1
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/enricher.js +8 -2
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +27 -9
- package/lib/checks/selectItems.js +25 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +66 -13
- package/lib/compiler/assert-consistency.js +24 -12
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +101 -39
- package/lib/compiler/index.js +88 -59
- package/lib/compiler/resolver.js +455 -209
- package/lib/compiler/shared.js +57 -33
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +128 -99
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -127
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +74 -28
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +18 -4
- package/lib/gen/language.tokens +124 -118
- package/lib/gen/languageLexer.interp +13 -1
- package/lib/gen/languageLexer.js +870 -839
- package/lib/gen/languageLexer.tokens +116 -111
- package/lib/gen/languageParser.js +5894 -5614
- package/lib/json/from-csn.js +152 -67
- package/lib/json/to-csn.js +334 -135
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +24 -14
- package/lib/language/language.g4 +188 -128
- package/lib/main.d.ts +435 -0
- package/lib/main.js +31 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +463 -187
- package/lib/model/csnUtils.js +280 -136
- package/lib/model/enrichCsn.js +75 -4
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +70 -25
- package/lib/optionProcessor.js +13 -10
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +123 -40
- package/lib/render/toHdbcds.js +156 -65
- package/lib/render/toSql.js +87 -11
- package/lib/render/utils/common.js +55 -9
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/{sql → db}/.eslintrc.json +0 -0
- package/lib/transform/{sql → db}/assertUnique.js +7 -8
- package/lib/transform/{sql → db}/constraints.js +35 -20
- package/lib/transform/db/draft.js +353 -0
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
- package/lib/transform/{sql → db}/helpers.js +0 -0
- package/lib/transform/{sql → db}/transformExists.js +256 -60
- package/lib/transform/forHanaNew.js +216 -765
- package/lib/transform/forOdataNew.js +60 -56
- package/lib/transform/localized.js +48 -26
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
- package/lib/transform/odata/generateForeignKeyElements.js +13 -12
- package/lib/transform/odata/referenceFlattener.js +60 -36
- package/lib/transform/odata/sortByAssociationDependency.js +4 -4
- package/lib/transform/odata/structuralPath.js +76 -0
- package/lib/transform/odata/structureFlattener.js +21 -22
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +27 -17
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +141 -77
- package/lib/transform/translateAssocsToJoins.js +17 -14
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +0 -11
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/timetrace.js +6 -1
- package/package.json +2 -1
- package/lib/base/deepCopy.js +0 -66
- package/lib/json/walker.js +0 -26
- package/lib/utils/string.js +0 -17
package/lib/compiler/resolver.js
CHANGED
|
@@ -45,9 +45,7 @@ const {
|
|
|
45
45
|
dictAdd, dictAddArray,
|
|
46
46
|
} = require('../base/dictionaries');
|
|
47
47
|
const { dictLocation } = require('../base/location');
|
|
48
|
-
const {
|
|
49
|
-
makeMessageFunction, searchName, weakLocation,
|
|
50
|
-
} = require('../base/messages');
|
|
48
|
+
const { searchName, weakLocation } = require('../base/messages');
|
|
51
49
|
const { combinedLocation } = require('../base/location');
|
|
52
50
|
const { pushLink } = require('./utils');
|
|
53
51
|
const {
|
|
@@ -84,7 +82,7 @@ function resolve( model ) {
|
|
|
84
82
|
} = fns( model, environment );
|
|
85
83
|
const {
|
|
86
84
|
info, warning, error, message,
|
|
87
|
-
} =
|
|
85
|
+
} = model.$messageFunctions;
|
|
88
86
|
const {
|
|
89
87
|
initArtifact,
|
|
90
88
|
lateExtensions,
|
|
@@ -95,6 +93,8 @@ function resolve( model ) {
|
|
|
95
93
|
|
|
96
94
|
// behavior depending on option `deprecated`:
|
|
97
95
|
const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
|
|
96
|
+
// TODO: we should get rid of noElementsExpansion soon; both
|
|
97
|
+
// beta.nestedProjections and beta.universalCsn do not work with it.
|
|
98
98
|
const scopedRedirections
|
|
99
99
|
= enableExpandElements &&
|
|
100
100
|
!isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ) &&
|
|
@@ -253,9 +253,11 @@ function resolve( model ) {
|
|
|
253
253
|
}
|
|
254
254
|
else {
|
|
255
255
|
setProp( art, '_effectiveType', art );
|
|
256
|
+
if (art.expand && !art.value && !art.elements)
|
|
257
|
+
initFromColumns( art, art.expand );
|
|
256
258
|
// When not blocked (by future origin = false) and not REDIRECTED TO and not MIXIN
|
|
257
259
|
// try to implicitly redirect explicitly provided target:
|
|
258
|
-
if (art.target && art._origin == null && !art.value && art.kind !== 'mixin')
|
|
260
|
+
else if (art.target && art._origin == null && !art.value && art.kind !== 'mixin')
|
|
259
261
|
redirectImplicitly( art, art );
|
|
260
262
|
}
|
|
261
263
|
}
|
|
@@ -268,8 +270,13 @@ function resolve( model ) {
|
|
|
268
270
|
let eType = art;
|
|
269
271
|
if (eType._outer)
|
|
270
272
|
eType = effectiveType( eType._outer );
|
|
273
|
+
// collect the "latest" cardinality (calculate lazyly if necessary)
|
|
274
|
+
let cardinality = art.cardinality ||
|
|
275
|
+
art._effectiveType && (() => getCardinality( art._effectiveType ));
|
|
271
276
|
for (const a of chain) {
|
|
272
|
-
if (a.
|
|
277
|
+
if (a.cardinality)
|
|
278
|
+
cardinality = a.cardinality;
|
|
279
|
+
if (a.expand && expandFromColumns( a, art, cardinality ) ||
|
|
273
280
|
art.target && redirectImplicitly( a, art ) ||
|
|
274
281
|
art.elements && expandElements( a, art, eType ))
|
|
275
282
|
art = a;
|
|
@@ -279,21 +286,55 @@ function resolve( model ) {
|
|
|
279
286
|
return art;
|
|
280
287
|
}
|
|
281
288
|
|
|
289
|
+
function expandFromColumns( elem, assoc, cardinality ) {
|
|
290
|
+
const path = elem.value && elem.value.path;
|
|
291
|
+
if (!path || path.broken)
|
|
292
|
+
return null;
|
|
293
|
+
if (!assoc.target)
|
|
294
|
+
return initFromColumns( elem, elem.expand );
|
|
295
|
+
const { targetMax } = path[path.length - 1].cardinality ||
|
|
296
|
+
(cardinality instanceof Function ? cardinality() : cardinality);
|
|
297
|
+
if (targetMax && (targetMax.val === '*' || targetMax.val > 1))
|
|
298
|
+
elem.items = { location: dictLocation( elem.expand ) };
|
|
299
|
+
return initFromColumns( elem, elem.expand );
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function getCardinality( type ) {
|
|
303
|
+
// only to be called without cycles
|
|
304
|
+
while (type) {
|
|
305
|
+
if (type.cardinality)
|
|
306
|
+
return type.cardinality;
|
|
307
|
+
type = directType( type );
|
|
308
|
+
}
|
|
309
|
+
return {};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function userQuery( user ) {
|
|
313
|
+
// TODO: we need _query links set by the definer
|
|
314
|
+
while (user._main) {
|
|
315
|
+
if (user.kind === 'select' || user.kind === '$join')
|
|
316
|
+
return user;
|
|
317
|
+
user = user._parent;
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// TODO: test it in combination with top-level CAST function
|
|
282
323
|
function directType( art ) {
|
|
283
324
|
// Be careful when using it with art.target or art.enum or art.elements
|
|
284
|
-
if (art._origin)
|
|
325
|
+
if (art._origin || art.builtin)
|
|
285
326
|
return art._origin;
|
|
286
327
|
if (art.type)
|
|
287
328
|
return resolveType( art.type, art );
|
|
288
329
|
// console.log( 'EXPR-IN', art.kind, refString(art.name) )
|
|
289
|
-
if (!art._main)
|
|
330
|
+
if (!art._main || !art.value || !art.value.path)
|
|
290
331
|
return undefined;
|
|
291
332
|
if (art._pathHead && art.value) {
|
|
292
333
|
setProp( art, '_origin', resolvePath( art.value, 'expr', art, null ) );
|
|
293
334
|
return art._origin;
|
|
294
335
|
}
|
|
295
|
-
const query = art
|
|
296
|
-
if (query.kind !== 'select'
|
|
336
|
+
const query = userQuery( art ) || art._parent;
|
|
337
|
+
if (query.kind !== 'select')
|
|
297
338
|
return undefined;
|
|
298
339
|
// Reached an element in a query which is a simple ref -> return referred artifact
|
|
299
340
|
// TODO: remember that we still have to resolve path arguments and filters
|
|
@@ -384,10 +425,10 @@ function resolve( model ) {
|
|
|
384
425
|
info( 'query-missing-element', [ ielem.name.location, view ], { id },
|
|
385
426
|
'Element $(ID) is missing in specified elements' );
|
|
386
427
|
}
|
|
387
|
-
else
|
|
428
|
+
else {
|
|
388
429
|
for (const prop in selem) {
|
|
389
|
-
// just annotation assignments for the moment
|
|
390
|
-
if (prop.charAt(0) === '@')
|
|
430
|
+
// just annotation assignments and doc comments for the moment
|
|
431
|
+
if (prop.charAt(0) === '@' || prop === 'doc')
|
|
391
432
|
ielem[prop] = selem[prop];
|
|
392
433
|
}
|
|
393
434
|
selem.$replacement = true;
|
|
@@ -395,7 +436,7 @@ function resolve( model ) {
|
|
|
395
436
|
}
|
|
396
437
|
for (const id in view.elements$) {
|
|
397
438
|
const selem = view.elements$[id]; // specified element
|
|
398
|
-
if (!
|
|
439
|
+
if (!selem.$replacement) {
|
|
399
440
|
error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
|
|
400
441
|
'Element $(ID) does not result from the query' );
|
|
401
442
|
}
|
|
@@ -405,8 +446,7 @@ function resolve( model ) {
|
|
|
405
446
|
function traverseElementEnvironments( art ) {
|
|
406
447
|
populateView( art );
|
|
407
448
|
environment( art );
|
|
408
|
-
|
|
409
|
-
forEachGeneric( art, 'elements', traverseElementEnvironments );
|
|
449
|
+
forEachMember( art, traverseElementEnvironments );
|
|
410
450
|
}
|
|
411
451
|
|
|
412
452
|
function populateQuery( query ) {
|
|
@@ -414,14 +454,16 @@ function resolve( model ) {
|
|
|
414
454
|
// already done or $join query or parse error
|
|
415
455
|
return;
|
|
416
456
|
setProp( query, '_combined', Object.create(null) );
|
|
457
|
+
query.$inlines = [];
|
|
417
458
|
forEachGeneric( query, '$tableAliases', resolveTabRef );
|
|
418
459
|
|
|
419
460
|
initFromColumns( query, query.columns );
|
|
461
|
+
// TODO: already in definer: complain about EXCLUDING with no wildcard
|
|
462
|
+
// (would have been automatically with a good CDL syntax: `* without (...)`)
|
|
420
463
|
if (query.excludingDict) {
|
|
421
464
|
for (const name in query.excludingDict)
|
|
422
|
-
resolveExcluding( name );
|
|
465
|
+
resolveExcluding( name, query._combined, query.excludingDict, query );
|
|
423
466
|
}
|
|
424
|
-
forEachGeneric( query, 'elements', initElem );
|
|
425
467
|
return;
|
|
426
468
|
|
|
427
469
|
function resolveTabRef( alias ) {
|
|
@@ -455,46 +497,36 @@ function resolve( model ) {
|
|
|
455
497
|
dictAddArray( query._combined, name, elem, null ); // not dictAdd()
|
|
456
498
|
});
|
|
457
499
|
}
|
|
500
|
+
}
|
|
458
501
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const origin = setProp( elem, '_origin',
|
|
482
|
-
resolvePath( elem.value, 'expr', elem, query._combined ) );
|
|
483
|
-
// console.log( message( null, elem.location, elem, {art:query}, 'Info','RED').toString(),
|
|
484
|
-
// elem.value)
|
|
485
|
-
// TODO: make this resolvePath() also part of directType() ?!
|
|
486
|
-
if (!origin)
|
|
487
|
-
return;
|
|
502
|
+
function initElem( elem, query ) {
|
|
503
|
+
if (elem.type && !elem.type.$inferred)
|
|
504
|
+
return; // explicit type -> enough or directType()
|
|
505
|
+
if (elem.$inferred) {
|
|
506
|
+
// redirectImplicitly( elem, elem._origin );
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (!elem.value || !elem.value.path) // TODO: test $inferred
|
|
510
|
+
return; // no value ref or $inferred
|
|
511
|
+
// TODO: what about SELECT from E { $projection.a as a1, a } !!!!!!
|
|
512
|
+
|
|
513
|
+
const env = columnEnv( elem._pathHead, query );
|
|
514
|
+
const origin = setProp( elem, '_origin',
|
|
515
|
+
resolvePath( elem.value, 'expr', elem, env ) );
|
|
516
|
+
// console.log( message( null, elem.location, elem, {art:query}, 'Info','RED').toString(),
|
|
517
|
+
// elem.value)
|
|
518
|
+
// TODO: make this resolvePath() also part of directType() ?!
|
|
519
|
+
if (!origin)
|
|
520
|
+
return;
|
|
521
|
+
if (elem.foreignKeys) { // REDIRECTED with explicit foreign keys
|
|
522
|
+
forEachGeneric( elem, 'foreignKeys', (key, name) => initKey( key, name, elem ) );
|
|
523
|
+
}
|
|
488
524
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
495
|
-
if (elem.foreignKeys) { // REDIRECTED with explicit foreign keys
|
|
496
|
-
forEachGeneric( elem, 'foreignKeys', (key, name) => initKey( key, name, elem ) );
|
|
497
|
-
}
|
|
525
|
+
// now set things which are necessary for later sub phases:
|
|
526
|
+
const nav = pathNavigation( elem.value );
|
|
527
|
+
if (nav.navigation && nav.item === elem.value.path[elem.value.path.length - 1]) {
|
|
528
|
+
// redirectImplicitly( elem, origin );
|
|
529
|
+
pushLink( nav.navigation, '_projections', elem );
|
|
498
530
|
}
|
|
499
531
|
}
|
|
500
532
|
|
|
@@ -528,8 +560,12 @@ function resolve( model ) {
|
|
|
528
560
|
// or should we use orig.location? - TODO: try to find test to see message
|
|
529
561
|
.$inferred = 'expand-element';
|
|
530
562
|
}
|
|
531
|
-
|
|
532
|
-
//
|
|
563
|
+
// Set elements expansion status (the if condition is always true, as no
|
|
564
|
+
// elements expansion will take place on artifact with existing other
|
|
565
|
+
// member property):
|
|
566
|
+
if (!art.$expand)
|
|
567
|
+
art.$expand = 'origin'; // if value stays, elements won't appear in CSN
|
|
568
|
+
// TODO: have some art.elements[SYM.$inferred] = 'expand-elements';
|
|
533
569
|
return true;
|
|
534
570
|
}
|
|
535
571
|
|
|
@@ -555,14 +591,45 @@ function resolve( model ) {
|
|
|
555
591
|
return false;
|
|
556
592
|
}
|
|
557
593
|
|
|
558
|
-
|
|
594
|
+
// About Helper property $expand for faster the XSN-to-CSN transformation
|
|
595
|
+
// - null/undefined: artifact, member, items does not contain expanded members
|
|
596
|
+
// - 'origin': all expanded (sub) elements have no new target/on and no new annotations
|
|
597
|
+
// that value is only on elements, types, and params -> no other members
|
|
598
|
+
// when set, only on elem/art with expanded elements
|
|
599
|
+
// - 'target': all expanded (sub) elements might only have new target/on, but
|
|
600
|
+
// no indivual annotations on any (sub) member
|
|
601
|
+
// when set, traverse all parents where the value has been 'origin' before
|
|
602
|
+
// - 'annotate': at least one inferred (sub) member has an individual annotation,
|
|
603
|
+
// not counting propagated ones; set up to the definition (main artifact)
|
|
604
|
+
// (only set with anno on $inferred elem)
|
|
605
|
+
// Usage according to CSN flavor:
|
|
606
|
+
// - gensrc: do not render enferred elements (including expanded elements),
|
|
607
|
+
// collect annotate statements with value 'annotate'
|
|
608
|
+
// - client: do not render expanded sub elements if artifact/member is no type, has a type,
|
|
609
|
+
// has $expand = 'origin', and all its _origin also have $expand = 'origin'
|
|
610
|
+
// (might sometimes render the elements unnecessarily, which is not wrong)
|
|
611
|
+
// - universal: do not render expanded sub elements if $expand = 'origin'
|
|
612
|
+
function setExpandStatus( elem, status ) {
|
|
613
|
+
// set on element
|
|
559
614
|
while (elem._main) {
|
|
560
615
|
elem = elem._parent;
|
|
561
|
-
if (
|
|
616
|
+
if (elem.$expand !== 'origin')
|
|
562
617
|
return;
|
|
563
|
-
elem.$expand =
|
|
618
|
+
elem.$expand = status; // meaning: expanded, containing assocs
|
|
564
619
|
for (let line = elem.items; line; line = line.items)
|
|
565
|
-
line.$expand =
|
|
620
|
+
line.$expand = status; // to-csn just uses the innermost $expand
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function setExpandStatusAnnotate( elem, status ) {
|
|
624
|
+
for (;;) {
|
|
625
|
+
if (elem.$expand === status)
|
|
626
|
+
return; // already set
|
|
627
|
+
elem.$expand = status; // meaning: expanded, containing annos
|
|
628
|
+
for (let line = elem.items; line; line = line.items)
|
|
629
|
+
line.$expand = status; // to-csn just uses the innermost $expand
|
|
630
|
+
if (!elem._main)
|
|
631
|
+
return;
|
|
632
|
+
elem = elem._parent;
|
|
566
633
|
}
|
|
567
634
|
}
|
|
568
635
|
|
|
@@ -574,7 +641,7 @@ function resolve( model ) {
|
|
|
574
641
|
// PRE: elem has no target, assoc has target prop
|
|
575
642
|
if (elem.kind === '$tableAlias')
|
|
576
643
|
return false;
|
|
577
|
-
setExpandStatus( elem );
|
|
644
|
+
setExpandStatus( elem, 'target' );
|
|
578
645
|
let target = resolvePath( assoc.target, 'target', assoc );
|
|
579
646
|
// console.log( info( null, [ elem.location, elem ], {target,art:assoc,name:''+assoc.target},
|
|
580
647
|
// 'RED').toString())
|
|
@@ -635,7 +702,8 @@ function resolve( model ) {
|
|
|
635
702
|
setLink( elem, origin, '_origin' );
|
|
636
703
|
setLink( origin, elem._parent, '_parent' );
|
|
637
704
|
if (elem._main) // remark: the param `elem` can also be a type
|
|
638
|
-
setLink( origin, elem._main, '
|
|
705
|
+
setLink( origin, elem._main, '_main' );
|
|
706
|
+
setLink( origin, origin, '_effectiveType' );
|
|
639
707
|
setLink( origin, elem._block, '_block' );
|
|
640
708
|
if (elem.foreignKeys) {
|
|
641
709
|
origin.foreignKeys = elem.foreignKeys;
|
|
@@ -686,6 +754,7 @@ function resolve( model ) {
|
|
|
686
754
|
const nullScope = {
|
|
687
755
|
kind: 'namespace', name: { absolute: autoScopeName, location }, location,
|
|
688
756
|
};
|
|
757
|
+
model.definitions[autoScopeName] = nullScope;
|
|
689
758
|
initArtifact( nullScope );
|
|
690
759
|
return nullScope;
|
|
691
760
|
}
|
|
@@ -894,146 +963,215 @@ function resolve( model ) {
|
|
|
894
963
|
return art;
|
|
895
964
|
}
|
|
896
965
|
|
|
966
|
+
// TODO: probably do this already in definer.js
|
|
967
|
+
function ensureColumnName( col, query ) {
|
|
968
|
+
if (col.name)
|
|
969
|
+
return col.name.id;
|
|
970
|
+
if (col.inline || col.val === '*')
|
|
971
|
+
return '';
|
|
972
|
+
const path = col.value &&
|
|
973
|
+
(col.value.path || !col.value.args && col.value.func && col.value.func.path);
|
|
974
|
+
if (path) {
|
|
975
|
+
const last = !path.broken && path.length && path[path.length - 1];
|
|
976
|
+
if (last) {
|
|
977
|
+
col.name = { id: last.id, location: last.location, $inferred: 'as' };
|
|
978
|
+
return col.name.id;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
else if (col.value || col.expand) {
|
|
982
|
+
error( 'query-req-name', [ col.value && col.value.location || col.location, query ], {},
|
|
983
|
+
'Alias name is required for this select item' );
|
|
984
|
+
}
|
|
985
|
+
// invent a name for code completion in expression
|
|
986
|
+
col.name = {
|
|
987
|
+
id: '',
|
|
988
|
+
location: col.value && col.value.location || col.location,
|
|
989
|
+
$inferred: 'none',
|
|
990
|
+
};
|
|
991
|
+
return '';
|
|
992
|
+
}
|
|
993
|
+
|
|
897
994
|
// TODO: make this function shorter - make part of this (e.g. setting
|
|
898
995
|
// parent/name) also be part of definer.js
|
|
899
|
-
// TODO: query is actually the elemParent
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
996
|
+
// TODO: query is actually the elemParent, where the new elements are added to
|
|
997
|
+
// top-level: just query, columns
|
|
998
|
+
// inline: + elements (TODO: remove), colParent
|
|
999
|
+
// expand: just query (which is a column/element), columns=array of expand
|
|
1000
|
+
function initFromColumns( query, columns, inlineHead = undefined ) {
|
|
1001
|
+
const elemsParent = query.items || query;
|
|
1002
|
+
if (!inlineHead) {
|
|
1003
|
+
elemsParent.elements = Object.create(null);
|
|
904
1004
|
if (query._main._leadingQuery === query) // never the case for 'expand'
|
|
905
|
-
query._main.elements =
|
|
1005
|
+
query._main.elements = elemsParent.elements;
|
|
906
1006
|
}
|
|
907
|
-
let wildcard = false;
|
|
908
|
-
let inline = 0;
|
|
909
1007
|
|
|
910
1008
|
for (const col of columns || [ { val: '*' } ]) {
|
|
911
1009
|
if (col.val === '*') {
|
|
912
|
-
|
|
913
|
-
|
|
1010
|
+
const siblings = wildcardSiblings( columns, query );
|
|
1011
|
+
expandWildcard( col, siblings, inlineHead, query );
|
|
914
1012
|
}
|
|
915
|
-
col.kind = 'element';
|
|
916
1013
|
if ((col.expand || col.inline) && !isBetaEnabled( options, 'nestedProjections' )) {
|
|
917
1014
|
error( null, [ col.location, query ], { prop: (col.expand ? 'expand' : 'inline') },
|
|
918
1015
|
'Unsupported nested $(PROP)' );
|
|
919
1016
|
}
|
|
920
|
-
if (!col.value)
|
|
1017
|
+
if (!col.value && !col.expand)
|
|
921
1018
|
continue; // error should have been reported by parser
|
|
922
1019
|
if (col.inline) {
|
|
923
1020
|
col.kind = '$inline';
|
|
924
1021
|
col.name = {};
|
|
925
1022
|
// a name for this internal symtab entry (e.g. '.2' to avoid clashes
|
|
926
1023
|
// with real elements) is only relevant for for `cdsc -R`/debugging
|
|
927
|
-
|
|
928
|
-
|
|
1024
|
+
const q = userQuery( query );
|
|
1025
|
+
q.$inlines.push( col );
|
|
1026
|
+
// or use userQuery( query ) in the following, too?
|
|
1027
|
+
setMemberParent( col, `.${ q.$inlines.length }`, query );
|
|
1028
|
+
initFromColumns( query, col.inline, col );
|
|
929
1029
|
continue;
|
|
930
1030
|
}
|
|
931
|
-
if (!col
|
|
932
|
-
const
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
const last = path[path.length - 1];
|
|
939
|
-
if (last)
|
|
940
|
-
col.name = { id: last.id, location: last.location, $inferred: 'as' };
|
|
941
|
-
}
|
|
942
|
-
if (!col.name) {
|
|
943
|
-
// invent a name for code completion in expression
|
|
944
|
-
col.name = { id: '', location: col.value.location, $inferred: 'none' };
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
const { id } = col.name;
|
|
948
|
-
dictAdd( elements, id, col, ( name, location ) => {
|
|
949
|
-
error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
|
|
950
|
-
});
|
|
951
|
-
setMemberParent( col, id, query );
|
|
952
|
-
if (!wildcard) {
|
|
953
|
-
if (col.$duplicates !== true)
|
|
954
|
-
dictAdd( query.elements, id, col );
|
|
955
|
-
col.$replacement = true;
|
|
1031
|
+
else if (!col.$replacement) {
|
|
1032
|
+
const id = ensureColumnName( col, query );
|
|
1033
|
+
col.kind = 'element';
|
|
1034
|
+
dictAdd( elemsParent.elements, id, col, ( name, location ) => {
|
|
1035
|
+
error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
|
|
1036
|
+
});
|
|
1037
|
+
setMemberParent( col, id, query );
|
|
956
1038
|
}
|
|
957
1039
|
}
|
|
958
|
-
|
|
959
|
-
expandWildcard( elements, wildcard );
|
|
1040
|
+
forEachGeneric( query, 'elements', e => initElem( e, query ) );
|
|
960
1041
|
return true;
|
|
1042
|
+
}
|
|
961
1043
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1044
|
+
// col ($replacement set before *)
|
|
1045
|
+
// false if two cols have same name
|
|
1046
|
+
function wildcardSiblings( columns, query ) {
|
|
1047
|
+
const siblings = Object.create(null);
|
|
1048
|
+
if (!columns)
|
|
1049
|
+
return siblings;
|
|
1050
|
+
|
|
1051
|
+
let seenWildcard = null;
|
|
1052
|
+
for (const col of columns) {
|
|
1053
|
+
const id = ensureColumnName( col, query );
|
|
1054
|
+
if (id) {
|
|
1055
|
+
col.$replacement = !seenWildcard;
|
|
1056
|
+
siblings[id] = !(id in siblings) && col;
|
|
1057
|
+
}
|
|
1058
|
+
else if (col.val === '*') {
|
|
1059
|
+
seenWildcard = true;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
return siblings;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// TODO: make struct.* are to be added at place, not sub-wildcards first,
|
|
1066
|
+
// see test3/Queries/ExpandInlineCreate/Excluding.cds
|
|
1067
|
+
// TODO: disallow $self.elem.* and $self.*, toSelf.* (circular dependency)
|
|
1068
|
+
function expandWildcard( wildcard, siblingElements, colParent, query ) {
|
|
1069
|
+
const { elements } = query.items || query;
|
|
1070
|
+
let location = wildcard.location || query.from && query.from.location || query.location;
|
|
1071
|
+
const inferred = query._main.$inferred;
|
|
1072
|
+
const excludingDict = (colParent || query).excludingDict || Object.create(null);
|
|
1073
|
+
|
|
1074
|
+
const envParent = wildcard._pathHead; // TODO: rename _pathHead to _pathEnv
|
|
1075
|
+
// console.log('S1:',location.line,location.col,
|
|
1076
|
+
// envParent&&!!envParent._origin&&envParent._origin.name)
|
|
1077
|
+
const env = columnEnv( envParent, query );
|
|
1078
|
+
// console.log('S2:',location.line,location.col,
|
|
1079
|
+
// envParent&&!!envParent._origin&&envParent._origin.name,
|
|
1080
|
+
// Object.keys(env),Object.keys(elements))
|
|
1081
|
+
for (const name in env) {
|
|
1082
|
+
const navElem = env[name];
|
|
1083
|
+
// TODO: if it is an array, filter out those with masked
|
|
1084
|
+
if (excludingDict[name] || navElem.masked && navElem.masked.val)
|
|
1085
|
+
continue;
|
|
1086
|
+
const sibling = siblingElements[name];
|
|
1087
|
+
if (sibling) { // is explicitly provided (without duplicate)
|
|
1088
|
+
if (!inferred && !envParent) // not yet for expand/inline
|
|
1089
|
+
reportReplacement( sibling, navElem, query );
|
|
1090
|
+
if (!sibling.$replacement) {
|
|
1091
|
+
sibling.$replacement = true;
|
|
1092
|
+
sibling.kind = 'element';
|
|
1093
|
+
dictAdd( elements, name, sibling, ( _name, loc ) => {
|
|
1094
|
+
// there can be a definition from a previous inline with the same name:
|
|
1095
|
+
error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
|
|
1096
|
+
});
|
|
1097
|
+
setMemberParent( sibling, name, query );
|
|
1009
1098
|
}
|
|
1010
|
-
else
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
const origin = colParent ? navElem : navElem._origin;
|
|
1021
|
-
const elem = linkToOrigin( origin, name, query, 'elements', location );
|
|
1022
|
-
elem.$inferred = '*';
|
|
1023
|
-
elem.name.$inferred = '*';
|
|
1024
|
-
if (colParent)
|
|
1025
|
-
setWildcardExpandInline( elem, colParent, origin, name, location );
|
|
1026
|
-
else
|
|
1027
|
-
setElementOrigin( elem, navElem, name, location );
|
|
1099
|
+
// else {
|
|
1100
|
+
// sibling.$inferred = 'query';
|
|
1101
|
+
// }
|
|
1102
|
+
}
|
|
1103
|
+
else if (Array.isArray(navElem)) {
|
|
1104
|
+
const names = navElem.filter( e => !e.$duplicates)
|
|
1105
|
+
.map( e => `${ e.name.alias }.${ e.name.element }` );
|
|
1106
|
+
if (names.length) {
|
|
1107
|
+
error( 'wildcard-ambiguous', [ location, query ], { id: name, names },
|
|
1108
|
+
'Ambiguous wildcard, select $(ID) explicitly with $(NAMES)' );
|
|
1028
1109
|
}
|
|
1029
1110
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1111
|
+
else {
|
|
1112
|
+
location = weakLocation( location );
|
|
1113
|
+
const origin = envParent ? navElem : navElem._origin;
|
|
1114
|
+
const elem = linkToOrigin( origin, name, query, null, location );
|
|
1115
|
+
// TODO: check assocToMany { * }
|
|
1116
|
+
dictAdd( elements, name, elem, ( _name, loc ) => {
|
|
1117
|
+
// there can be a definition from a previous inline with the same name:
|
|
1118
|
+
error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
|
|
1119
|
+
});
|
|
1120
|
+
elem.$inferred = '*';
|
|
1121
|
+
elem.name.$inferred = '*';
|
|
1122
|
+
if (envParent)
|
|
1123
|
+
setWildcardExpandInline( elem, envParent, origin, name, location );
|
|
1124
|
+
else
|
|
1125
|
+
setElementOrigin( elem, navElem, name, location );
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (envParent || query.kind !== 'select') {
|
|
1129
|
+
// already done in populateQuery (TODO: change that and check whether
|
|
1130
|
+
// `*` is allowed at all in definer)
|
|
1131
|
+
const user = colParent || query;
|
|
1132
|
+
for (const name in user.excludingDict)
|
|
1133
|
+
resolveExcluding( name, env, excludingDict, query );
|
|
1034
1134
|
}
|
|
1035
1135
|
}
|
|
1036
1136
|
|
|
1137
|
+
function reportReplacement( sibling, navElem, query ) {
|
|
1138
|
+
// TODO: bring this much less often = only if shadowed elem does not appear
|
|
1139
|
+
// in expr and if not projected as other name.
|
|
1140
|
+
// Probably needs to be reported at a later phase
|
|
1141
|
+
const path = sibling.value && sibling.value.path;
|
|
1142
|
+
if (!sibling.target || sibling.target.$inferred || // not explicit REDIRECTED TO
|
|
1143
|
+
path && path[path.length - 1].id !== sibling.name.id) { // or renamed
|
|
1144
|
+
const { id } = sibling.name;
|
|
1145
|
+
if (Array.isArray(navElem)) {
|
|
1146
|
+
info( 'wildcard-excluding-many', [ sibling.name.location, query ], { id },
|
|
1147
|
+
'This select item replaces $(ID) from two or more sources' );
|
|
1148
|
+
}
|
|
1149
|
+
else {
|
|
1150
|
+
info( 'wildcard-excluding-one', [ sibling.name.location, query ],
|
|
1151
|
+
{ id, alias: navElem._parent.name.id },
|
|
1152
|
+
'This select item replaces $(ID) from table alias $(ALIAS)' );
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function columnEnv( envParent, query ) { // etc. wildcard._pathHead;
|
|
1158
|
+
return (envParent)
|
|
1159
|
+
? environment( directType( envParent ) || envParent )
|
|
1160
|
+
: userQuery( query )._combined;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
function resolveExcluding( name, env, excludingDict, user ) {
|
|
1164
|
+
if (env[name])
|
|
1165
|
+
return;
|
|
1166
|
+
/** @type {object} */
|
|
1167
|
+
// console.log(name,Object.keys(env),Object.keys(excludingDict))
|
|
1168
|
+
const compileMessageRef = info(
|
|
1169
|
+
'ref-undefined-excluding', [ excludingDict[name].location, user ], { name },
|
|
1170
|
+
'Element $(NAME) has not been found'
|
|
1171
|
+
);
|
|
1172
|
+
attachAndEmitValidNames( compileMessageRef, env );
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1037
1175
|
function setElementOrigin( queryElem, navElem, name, location ) {
|
|
1038
1176
|
const sourceElem = navElem._origin;
|
|
1039
1177
|
const alias = navElem._parent;
|
|
@@ -1276,6 +1414,7 @@ function resolve( model ) {
|
|
|
1276
1414
|
}
|
|
1277
1415
|
}
|
|
1278
1416
|
if (obj.target) {
|
|
1417
|
+
// console.log(obj.name,obj._origin.name)
|
|
1279
1418
|
if (obj._origin && obj._origin.$inferred === 'REDIRECTED')
|
|
1280
1419
|
resolveTarget( art, obj._origin );
|
|
1281
1420
|
// console.log(message( null, obj.location, obj, {target:obj.target}, 'Info','TARGET')
|
|
@@ -1301,6 +1440,7 @@ function resolve( model ) {
|
|
|
1301
1440
|
}
|
|
1302
1441
|
// Resolve projections/views
|
|
1303
1442
|
// if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );
|
|
1443
|
+
// TODO: here, any order should be ok, i.e. just loop over $queries
|
|
1304
1444
|
traverseQueryPost( art.query, false, resolveQuery );
|
|
1305
1445
|
|
|
1306
1446
|
if (obj.type || obj._origin || obj.value && obj.value.path || obj.elements) // typed artifacts
|
|
@@ -1320,7 +1460,7 @@ function resolve( model ) {
|
|
|
1320
1460
|
}
|
|
1321
1461
|
|
|
1322
1462
|
resolveExpr( art.default, 'default', art );
|
|
1323
|
-
resolveExpr( art.value, 'expr', art );
|
|
1463
|
+
resolveExpr( art.value, 'expr', art, undefined, art.expand || art.inline );
|
|
1324
1464
|
if (art.value && !art.type && !art.target && !art.elements)
|
|
1325
1465
|
inferTypeFromCast( art );
|
|
1326
1466
|
|
|
@@ -1446,14 +1586,23 @@ function resolve( model ) {
|
|
|
1446
1586
|
}
|
|
1447
1587
|
}
|
|
1448
1588
|
if (art && art._annotate) {
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
//
|
|
1452
|
-
|
|
1453
|
-
|
|
1589
|
+
const aor = art.returns || art;
|
|
1590
|
+
const obj = aor.items || aor.targetAspect || aor;
|
|
1591
|
+
// Currently(?), effectiveType() does not calculate the effective type of
|
|
1592
|
+
// its line item:
|
|
1593
|
+
effectiveType( obj );
|
|
1594
|
+
if (art._annotate.elements)
|
|
1595
|
+
setExpandStatusAnnotate( aor, 'annotate' );
|
|
1454
1596
|
annotate( obj, 'element', 'elements', 'enum', art );
|
|
1455
1597
|
annotate( art, 'action', 'actions' );
|
|
1456
1598
|
annotate( art, 'param', 'params' );
|
|
1599
|
+
// const { returns } = art._annotate;
|
|
1600
|
+
// if (returns) {
|
|
1601
|
+
// const dict = returns.elements;
|
|
1602
|
+
// const env = obj.returns && obj.returns.elements || null;
|
|
1603
|
+
// for (const n in dict)
|
|
1604
|
+
// annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
|
|
1605
|
+
// }
|
|
1457
1606
|
}
|
|
1458
1607
|
return;
|
|
1459
1608
|
|
|
@@ -1509,6 +1658,8 @@ function resolve( model ) {
|
|
|
1509
1658
|
ext.kind = 'annotate'; // after setMemberParent()!
|
|
1510
1659
|
setProp( art, '_extension', ext );
|
|
1511
1660
|
setProp( ext.name, '_artifact', art );
|
|
1661
|
+
if (art.returns)
|
|
1662
|
+
ext.$syntax = 'returns';
|
|
1512
1663
|
return ext;
|
|
1513
1664
|
}
|
|
1514
1665
|
|
|
@@ -1528,8 +1679,13 @@ function resolve( model ) {
|
|
|
1528
1679
|
function chooseAssignment( annoName, art ) {
|
|
1529
1680
|
// TODO: getPath an all names
|
|
1530
1681
|
const anno = art[annoName];
|
|
1531
|
-
if (!Array.isArray(anno)) // just one assignment -> use it
|
|
1682
|
+
if (!Array.isArray(anno)) { // just one assignment -> use it
|
|
1683
|
+
if (removeEllipsis( anno )) {
|
|
1684
|
+
error( 'anno-unexpected-ellipsis',
|
|
1685
|
+
[ anno.name.location, art ], { code: '...' } );
|
|
1686
|
+
}
|
|
1532
1687
|
return;
|
|
1688
|
+
}
|
|
1533
1689
|
// sort assignment according to layer
|
|
1534
1690
|
const layerAnnos = Object.create(null);
|
|
1535
1691
|
for (const a of anno) {
|
|
@@ -1541,33 +1697,107 @@ function resolve( model ) {
|
|
|
1541
1697
|
else
|
|
1542
1698
|
layerAnnos[name] = { layer, annos: [ a ] };
|
|
1543
1699
|
}
|
|
1700
|
+
mergeArrayInSCCs();
|
|
1701
|
+
art[annoName] = mergeLayeredArrays( findLayerCandidate( ) );
|
|
1702
|
+
return;
|
|
1703
|
+
|
|
1704
|
+
function mergeArrayInSCCs( ) {
|
|
1705
|
+
let pos = 0;
|
|
1706
|
+
Object.values( layerAnnos ).forEach( (layer) => {
|
|
1707
|
+
const mergeSource
|
|
1708
|
+
= layer.annos.find(v => (v.$priority === undefined ||
|
|
1709
|
+
annotationPriorities[v.$priority] === annotationPriorities.define));
|
|
1710
|
+
if (mergeSource) {
|
|
1711
|
+
if (removeEllipsis( mergeSource )) {
|
|
1712
|
+
error( 'anno-unexpected-ellipsis',
|
|
1713
|
+
[ mergeSource.name.location, art ], { code: '...' } );
|
|
1714
|
+
}
|
|
1715
|
+
// merge source into elipsis array annotates
|
|
1716
|
+
layer.annos.forEach( (mergeTarget) => {
|
|
1717
|
+
if (mergeTarget.$priority &&
|
|
1718
|
+
annotationPriorities[mergeTarget.$priority] > annotationPriorities.define) {
|
|
1719
|
+
pos = findEllipsis( mergeTarget );
|
|
1720
|
+
if (pos > -1) {
|
|
1721
|
+
if (mergeSource.literal !== 'array') {
|
|
1722
|
+
error( 'anno-mismatched-ellipsis',
|
|
1723
|
+
[ mergeSource.name.location, art ], { code: '...' } );
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
mergeTarget.val.splice(pos, 1, ...mergeSource.val);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
function mergeLayeredArrays( mergeTarget ) {
|
|
1735
|
+
if (mergeTarget.literal === 'array') {
|
|
1736
|
+
let layer = layers.layer( mergeTarget._block );
|
|
1737
|
+
delete layerAnnos[(layer) ? layer.realname : ''];
|
|
1738
|
+
let pos = findEllipsis( mergeTarget );
|
|
1739
|
+
while (pos > -1 && Object.keys( layerAnnos ).length ) {
|
|
1740
|
+
const mergeSource = findLayerCandidate();
|
|
1741
|
+
if (mergeSource.literal !== 'array') {
|
|
1742
|
+
error( 'anno-mismatched-ellipsis',
|
|
1743
|
+
[ mergeSource.name.location, art ], { code: '...' } );
|
|
1744
|
+
return mergeTarget;
|
|
1745
|
+
}
|
|
1746
|
+
mergeTarget.val.splice(pos, 1, ...mergeSource.val);
|
|
1747
|
+
layer = layers.layer( mergeSource._block );
|
|
1748
|
+
delete layerAnnos[(layer) ? layer.realname : ''];
|
|
1749
|
+
pos = findEllipsis( mergeTarget );
|
|
1750
|
+
}
|
|
1751
|
+
// remove excess ellipsis
|
|
1752
|
+
removeEllipsis( mergeTarget, pos );
|
|
1753
|
+
}
|
|
1754
|
+
return mergeTarget;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
function removeEllipsis(a, pos = findEllipsis( a )) {
|
|
1758
|
+
let count = 0;
|
|
1759
|
+
while (a.literal === 'array' && pos > -1) {
|
|
1760
|
+
count++;
|
|
1761
|
+
a.val.splice(pos, 1);
|
|
1762
|
+
pos = findEllipsis( a );
|
|
1763
|
+
}
|
|
1764
|
+
return count;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
function findEllipsis(a) {
|
|
1768
|
+
return (a.literal === 'array' && a.val)
|
|
1769
|
+
? a.val.findIndex(v => v.literal === 'token' && v.val === '...') : -1;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
function findLayerCandidate() {
|
|
1544
1773
|
// collect assignments of upper layers (are in no _layerExtends)
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1774
|
+
const exts = Object.keys( layerAnnos ).map( layerExtends );
|
|
1775
|
+
const allExtends = Object.assign( Object.create(null), ...exts );
|
|
1776
|
+
const collected = [];
|
|
1777
|
+
for (const name in layerAnnos) {
|
|
1778
|
+
if (!(name in allExtends))
|
|
1779
|
+
collected.push( prioritizedAnnos( layerAnnos[name].annos ) );
|
|
1780
|
+
}
|
|
1781
|
+
// inspect collected assignments - choose the one or signal error
|
|
1782
|
+
const justOnePerLayer = collected.every( annos => annos.length === 1);
|
|
1783
|
+
if (!justOnePerLayer || collected.length > 1) {
|
|
1784
|
+
for (const annos of collected) {
|
|
1785
|
+
for (const a of annos ) {
|
|
1557
1786
|
// Only the message ID is different.
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1787
|
+
if (justOnePerLayer) {
|
|
1788
|
+
message( 'anno-duplicate-unrelated-layer',
|
|
1789
|
+
[ a.name.location, art ], { anno: annoName },
|
|
1790
|
+
'Duplicate assignment with $(ANNO)' );
|
|
1791
|
+
}
|
|
1792
|
+
else {
|
|
1793
|
+
message( 'anno-duplicate', [ a.name.location, art ], { anno: annoName },
|
|
1794
|
+
'Duplicate assignment with $(ANNO)' );
|
|
1795
|
+
}
|
|
1565
1796
|
}
|
|
1566
1797
|
}
|
|
1567
1798
|
}
|
|
1799
|
+
return collected[0][0]; // just choose any one with error
|
|
1568
1800
|
}
|
|
1569
|
-
art[annoName] = collected[0][0]; // just choose any one with error
|
|
1570
|
-
return;
|
|
1571
1801
|
|
|
1572
1802
|
function layerExtends( name ) {
|
|
1573
1803
|
const { layer } = layerAnnos[name];
|
|
@@ -1605,8 +1835,11 @@ function resolve( model ) {
|
|
|
1605
1835
|
// pure path has been resolved, resolve args and filter now:
|
|
1606
1836
|
resolveExpr( alias, 'from', query._parent );
|
|
1607
1837
|
} );
|
|
1838
|
+
for (const col of query.$inlines)
|
|
1839
|
+
resolveExpr( col.value, 'expr', col, undefined, true );
|
|
1840
|
+
// for (const col of query.$inlines)
|
|
1841
|
+
// if (!col.value.path) throw Error(col.name.element)
|
|
1608
1842
|
if (query !== query._main._leadingQuery) // will be done later
|
|
1609
|
-
// TODO: rethink elements(view) === elements(view._leadingQuery)
|
|
1610
1843
|
forEachGeneric( query, 'elements', resolveRefs );
|
|
1611
1844
|
if (query.from)
|
|
1612
1845
|
resolveJoinOn( query.from );
|
|
@@ -1630,7 +1863,7 @@ function resolve( model ) {
|
|
|
1630
1863
|
return;
|
|
1631
1864
|
|
|
1632
1865
|
function resolveJoinOn( join ) {
|
|
1633
|
-
if (join.args) {
|
|
1866
|
+
if (join && join.args) { // JOIN
|
|
1634
1867
|
for (const j of join.args)
|
|
1635
1868
|
resolveJoinOn( j );
|
|
1636
1869
|
if (join.on)
|
|
@@ -1667,7 +1900,10 @@ function resolve( model ) {
|
|
|
1667
1900
|
}
|
|
1668
1901
|
const target = resolvePath( obj.target, 'target', art );
|
|
1669
1902
|
if (obj.on) {
|
|
1670
|
-
if (!art._main || !art._parent.elements && !art._parent.items) {
|
|
1903
|
+
if (!art._main || !art._parent.elements && !art._parent.items && !art._parent.targetAspect) {
|
|
1904
|
+
// TODO: test of .items a bit unclear - we should somehow restrict the
|
|
1905
|
+
// use of unmanaged assocs in MANY, at least with $self
|
|
1906
|
+
// TODO: $self usage in anonymous aspects to be corrected in Core Compiler
|
|
1671
1907
|
const isComposition = obj.type && obj.type.path && obj.type.path[0] &&
|
|
1672
1908
|
obj.type.path[0].id === 'cds.Composition';
|
|
1673
1909
|
message( 'assoc-as-type', [ obj.on.location, art ],
|
|
@@ -2108,6 +2344,9 @@ function resolve( model ) {
|
|
|
2108
2344
|
|
|
2109
2345
|
// TODO: there is no need to rewrite the on condition of non-leading queries,
|
|
2110
2346
|
// i.e. we could just have on = {…}
|
|
2347
|
+
// TODO: re-check $self rewrite (with managed composition of aspects),
|
|
2348
|
+
// and actually also $self inside anonymous aspect definitions
|
|
2349
|
+
// (not entirely urgent as we do not analyse it further, at least sole "$self")
|
|
2111
2350
|
function rewriteCondition( elem, assoc ) {
|
|
2112
2351
|
if (enableExpandElements && elem._parent && elem._parent.kind === 'element') {
|
|
2113
2352
|
// managed association as sub element not supported yet
|
|
@@ -2311,7 +2550,8 @@ function resolve( model ) {
|
|
|
2311
2550
|
resolveTypeArguments( art, typeArt, user );
|
|
2312
2551
|
}
|
|
2313
2552
|
|
|
2314
|
-
function resolveExpr( expr, expected, user, extDict) {
|
|
2553
|
+
function resolveExpr( expr, expected, user, extDict, expandOrInline) {
|
|
2554
|
+
// TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`
|
|
2315
2555
|
if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
|
|
2316
2556
|
return;
|
|
2317
2557
|
if (Array.isArray(expr)) {
|
|
@@ -2337,7 +2577,7 @@ function resolve( model ) {
|
|
|
2337
2577
|
}
|
|
2338
2578
|
resolvePath( expr, expected, user, extDict );
|
|
2339
2579
|
|
|
2340
|
-
const last = expr.path[expr.path.length - 1];
|
|
2580
|
+
const last = !expandOrInline && expr.path[expr.path.length - 1];
|
|
2341
2581
|
for (const step of expr.path) {
|
|
2342
2582
|
if (step && (step.args || step.where || step.cardinality) &&
|
|
2343
2583
|
step._artifact && !Array.isArray( step._artifact ) )
|
|
@@ -2358,12 +2598,18 @@ function resolve( model ) {
|
|
|
2358
2598
|
const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
|
|
2359
2599
|
args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
|
|
2360
2600
|
}
|
|
2601
|
+
if (expr.suffix && !isBetaEnabled( options, 'windowFunctions' )) {
|
|
2602
|
+
const { location } = expr.suffix[0] || expr;
|
|
2603
|
+
error( null, [ location, user ], 'Window functions are not supported' );
|
|
2604
|
+
}
|
|
2605
|
+
if (expr.suffix)
|
|
2606
|
+
expr.suffix.forEach( s => s && resolveExpr( s, expected, user, extDict ) );
|
|
2361
2607
|
}
|
|
2362
2608
|
|
|
2363
2609
|
function resolveParamsAndWhere( step, expected, user, extDict, isLast ) {
|
|
2364
2610
|
const alias = step._navigation && step._navigation.kind === '$tableAlias' && step._navigation;
|
|
2365
2611
|
const type = alias || effectiveType( step._artifact );
|
|
2366
|
-
const art = type && type.target
|
|
2612
|
+
const art = (type && type.target) ? type.target._artifact : type;
|
|
2367
2613
|
if (!art)
|
|
2368
2614
|
return;
|
|
2369
2615
|
const entity = (art.kind === 'entity') &&
|
|
@@ -2374,7 +2620,7 @@ function resolve( model ) {
|
|
|
2374
2620
|
if (step.where)
|
|
2375
2621
|
resolveExpr( step.where, 'filter', user, environment( type ) );
|
|
2376
2622
|
}
|
|
2377
|
-
else if (step.where || step.cardinality) {
|
|
2623
|
+
else if (step.where && step.where.location || step.cardinality ) {
|
|
2378
2624
|
const location = combinedLocation( step.where, step.cardinality );
|
|
2379
2625
|
// XSN TODO: filter$location including […]
|
|
2380
2626
|
message( 'expr-no-filter', [ location, user ], { '#': expected },
|