@sap/cds-compiler 4.1.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +101 -1
  2. package/bin/cdsc.js +6 -3
  3. package/doc/CHANGELOG_BETA.md +5 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +2 -2
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +24 -24
  8. package/lib/base/message-registry.js +41 -6
  9. package/lib/base/messages.js +7 -0
  10. package/lib/base/model.js +37 -8
  11. package/lib/checks/elements.js +11 -10
  12. package/lib/checks/manyNavigations.js +33 -0
  13. package/lib/checks/onConditions.js +5 -2
  14. package/lib/checks/queryNoDbArtifacts.js +2 -3
  15. package/lib/checks/selectItems.js +4 -55
  16. package/lib/checks/utils.js +3 -2
  17. package/lib/checks/validator.js +3 -1
  18. package/lib/compiler/.eslintrc.json +2 -1
  19. package/lib/compiler/assert-consistency.js +27 -24
  20. package/lib/compiler/base.js +6 -2
  21. package/lib/compiler/builtins.js +34 -34
  22. package/lib/compiler/checks.js +179 -208
  23. package/lib/compiler/classes.js +2 -2
  24. package/lib/compiler/cycle-detector.js +6 -6
  25. package/lib/compiler/define.js +66 -45
  26. package/lib/compiler/extend.js +81 -72
  27. package/lib/compiler/finalize-parse-cdl.js +26 -26
  28. package/lib/compiler/generate.js +61 -45
  29. package/lib/compiler/index.js +47 -49
  30. package/lib/compiler/kick-start.js +8 -7
  31. package/lib/compiler/moduleLayers.js +1 -1
  32. package/lib/compiler/populate.js +42 -35
  33. package/lib/compiler/propagator.js +6 -6
  34. package/lib/compiler/resolve.js +170 -126
  35. package/lib/compiler/shared.js +122 -45
  36. package/lib/compiler/tweak-assocs.js +93 -40
  37. package/lib/compiler/utils.js +15 -12
  38. package/lib/edm/.eslintrc.json +40 -1
  39. package/lib/edm/annotations/genericTranslation.js +721 -707
  40. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  41. package/lib/edm/csn2edm.js +389 -378
  42. package/lib/edm/edm.js +678 -772
  43. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  44. package/lib/edm/edmInboundChecks.js +29 -27
  45. package/lib/edm/edmPreprocessor.js +686 -646
  46. package/lib/edm/edmUtils.js +277 -296
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +1 -1
  49. package/lib/gen/languageParser.js +1253 -1276
  50. package/lib/json/from-csn.js +34 -4
  51. package/lib/json/to-csn.js +4 -4
  52. package/lib/language/language.g4 +2 -5
  53. package/lib/main.d.ts +61 -1
  54. package/lib/model/csnUtils.js +31 -2
  55. package/lib/model/revealInternalProperties.js +1 -1
  56. package/lib/modelCompare/compare.js +37 -2
  57. package/lib/modelCompare/utils/filter.js +1 -1
  58. package/lib/optionProcessor.js +15 -3
  59. package/lib/render/toCdl.js +30 -4
  60. package/lib/render/toSql.js +5 -9
  61. package/lib/render/utils/common.js +8 -6
  62. package/lib/transform/db/applyTransformations.js +1 -1
  63. package/lib/transform/db/cdsPersistence.js +1 -1
  64. package/lib/transform/db/constraints.js +47 -17
  65. package/lib/transform/db/expansion.js +121 -47
  66. package/lib/transform/db/flattening.js +75 -7
  67. package/lib/transform/forOdata.js +4 -1
  68. package/lib/transform/forRelationalDB.js +80 -62
  69. package/lib/transform/localized.js +91 -54
  70. package/lib/transform/transformUtils.js +9 -10
  71. package/lib/utils/file.js +7 -7
  72. package/lib/utils/moduleResolve.js +210 -121
  73. package/lib/utils/objectUtils.js +1 -1
  74. package/package.json +5 -5
@@ -62,13 +62,14 @@ const {
62
62
  testExpr,
63
63
  targetMaxNotOne,
64
64
  traverseQueryPost,
65
+ linkToOrigin,
65
66
  } = require('./utils');
66
67
 
67
68
  const detectCycles = require('./cycle-detector');
68
69
 
69
- const $location = Symbol.for('cds.$location');
70
+ const $location = Symbol.for( 'cds.$location' );
70
71
 
71
- const $inferred = Symbol.for('cds.$inferred');
72
+ const $inferred = Symbol.for( 'cds.$inferred' );
72
73
 
73
74
  // TODO: make this part of specExpected in shared.js
74
75
  // (standard: not possible on last if !ref.$expected → ref.scope: '$exists')
@@ -99,7 +100,7 @@ function resolve( model ) {
99
100
  } );
100
101
 
101
102
  const ignoreSpecifiedElements
102
- = isDeprecatedEnabled(model.options, 'ignoreSpecifiedQueryElements');
103
+ = isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' );
103
104
 
104
105
  return doResolve();
105
106
 
@@ -134,9 +135,9 @@ function resolve( model ) {
134
135
  std: 'Illegal circular reference to $(ART)',
135
136
  element: 'Illegal circular reference to element $(MEMBER) of $(ART)',
136
137
  target: 'Illegal circular reference to target $(ART)',
137
- });
138
+ } );
138
139
  }
139
- });
140
+ } );
140
141
  if (model.$assert) {
141
142
  error( '$internal-expecting-cyclic', null, {},
142
143
  'INTERNAL: the compiler should have issued an Error[ref-cyclic]' );
@@ -157,22 +158,52 @@ function resolve( model ) {
157
158
  return;
158
159
  // TODO: what about elements where _origin is set without value?
159
160
  // TODO: or should we push elems with `expand` sibling to extra list for
160
- // better messages? (Whatever that means exactly.)
161
+ // better messages? (Whatever that means exactly.)
161
162
  const nav = pathNavigation( elem.value );
162
163
  const { path } = elem.value;
163
- const item = path[path.length - 1];
164
- if (nav.navigation && nav.item === item) {
165
- // sourceElem, alias.sourceElem, mixin:
166
- // redirectImplicitly( elem, origin );
167
- pushLink( nav.navigation, '_projections', elem );
168
- }
169
- else if (elem._pathHead?.kind === '$inline' && path.length === 1) {
164
+
165
+ if (elem._pathHead?.kind === '$inline' && path.length === 1) {
166
+ const item = path[0];
170
167
  const hpath = elem._pathHead.value?.path;
171
168
  const head = hpath?.length === 1 && hpath[0]._navigation;
172
169
  // Alias .{ elem } - but not with Alias.{ $magic }: consider for ON-rewrite
173
170
  if (head?.kind === '$tableAlias' && item.id.charAt(0) !== '$')
174
171
  pushLink( head.elements[item.id], '_projections', elem );
175
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
+ }
176
207
  } );
177
208
  }
178
209
  }
@@ -361,7 +392,7 @@ function resolve( model ) {
361
392
  for (const include of art.includes) {
362
393
  const struct = include._artifact;
363
394
  if (struct && struct.kind !== 'type' && struct.elements &&
364
- Object.values( struct.elements ).some( e => e.targetAspect)) {
395
+ Object.values( struct.elements ).some( e => e.targetAspect )) {
365
396
  message( 'type-managed-composition', [ include.location, art ],
366
397
  { '#': struct.kind, art: struct } );
367
398
  }
@@ -385,7 +416,7 @@ function resolve( model ) {
385
416
  }
386
417
  if (obj.items) { // TODO: make this a while in v2 (also items proxy)
387
418
  obj = obj.items || obj; // the object which has type properties
388
- effectiveType(obj);
419
+ effectiveType( obj );
389
420
  }
390
421
  if (obj.type) { // TODO: && !obj.type.$inferred ?
391
422
  if (obj !== (art.returns || art)) // not already checked
@@ -412,13 +443,13 @@ function resolve( model ) {
412
443
  if (elemtype.category === 'relation' && obj.type.path.length > 0 &&
413
444
  !obj.target && !obj.targetAspect) {
414
445
  const isCsn = (obj._block && obj._block.$frontend === 'json');
415
- error('type-missing-target', [ obj.type.location, obj ],
416
- { '#': isCsn ? 'csn' : 'std', type: elemtype.name.absolute }, {
417
- // We don't say "use 'association to <target>" because the type could be used
418
- // in action parameters, etc. as well.
419
- std: 'The type $(TYPE) can\'t be used directly because it\'s compiler internal',
420
- csn: 'Type $(TYPE) is missing a target',
421
- });
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
+ } );
422
453
  }
423
454
  }
424
455
  }
@@ -454,7 +485,7 @@ function resolve( model ) {
454
485
  }
455
486
  if (obj.foreignKeys) { // silent dependencies
456
487
  // Avoid strange ref-cyclic if managed composition is key (check comes later)
457
- // TODO: the following is already done by addimplicitforeignkeys()!
488
+ // TODO: the following is already done by addImplicitForeignKeys()!
458
489
  if (obj.$inferred !== 'aspect-composition') {
459
490
  forEachGeneric( obj, 'foreignKeys', (elem) => {
460
491
  dependsOnSilent( art, elem );
@@ -470,9 +501,9 @@ function resolve( model ) {
470
501
  inferTypePropertiesFromCast( art );
471
502
  if (art.value) {
472
503
  if (art.$syntax === 'calc')
473
- checkCalculatedElement(art);
504
+ checkCalculatedElement( art );
474
505
  else if (art.type && !art.$inferred )
475
- checkStructureCast(art);
506
+ checkStructureCast( art );
476
507
  }
477
508
 
478
509
  forEachMember( art, resolveRefs, art.targetAspect );
@@ -482,8 +513,8 @@ function resolve( model ) {
482
513
 
483
514
  if (!ignoreSpecifiedElements && art.elements$ && art.elements) {
484
515
  for (const id in art.elements$) {
485
- resolveRefs(art.elements$[id]);
486
- checkSpecifiedElement(art.elements[id], art.elements$[id]);
516
+ resolveRefs( art.elements$[id] );
517
+ checkSpecifiedElement( art.elements[id], art.elements$[id] );
487
518
  }
488
519
  }
489
520
 
@@ -508,96 +539,97 @@ function resolve( model ) {
508
539
 
509
540
  // Check explicit types: If either side has one, so must the other.
510
541
  const sType = specifiedElement.type?._artifact;
511
- const iType = getInferredPropFromOrigin('type')?._artifact || inferredElement;
542
+ const iType = getInferredPropFromOrigin( 'type' )?._artifact || inferredElement;
512
543
 
513
544
  // xor: could be missing a type;
514
545
  // FIXME: The coding above returns incorrect iType for expand on associations
515
546
  if (!specifiedElement.type && inferredElement.type) {
516
- error('query-mismatched-element', [ specifiedElement.location, user ], {
547
+ error( 'query-mismatched-element', [ specifiedElement.location, user ], {
517
548
  '#': !specifiedElement.type ? 'missing' : 'extra', name: user.name.id, prop: 'type',
518
- });
549
+ } );
519
550
  return;
520
551
  }
521
552
  // If specified type is `null`, type could not be resolved.
522
553
  else if (sType && sType !== iType) {
523
554
  const typeName = iType?.name && sType?.name ? 'typeName' : 'type';
524
- error('query-mismatched-element', [
555
+ const othertype = typeName !== 'type' && iType || '';
556
+ error( 'query-mismatched-element', [
525
557
  specifiedElement.type.location || specifiedElement.location, user,
526
558
  ], {
527
559
  '#': typeName,
528
560
  name: user.name.id,
529
561
  type: sType,
530
- othertype: iType,
531
- });
562
+ othertype,
563
+ } );
532
564
  return;
533
565
  }
534
566
 
535
567
  // This relies on (element) expansion! Check that both sides have the following properties.
536
568
  // On the inferred side, they are likely expanded.
537
- if (!hasXorPropMismatch('elements') && !hasXorPropMismatch('items') &&
538
- !hasXorPropMismatch('target') && !hasXorPropMismatch('enum')) {
569
+ if (!hasXorPropMismatch( 'elements' ) && !hasXorPropMismatch( 'items' ) &&
570
+ !hasXorPropMismatch( 'target' ) && !hasXorPropMismatch( 'enum' )) {
539
571
  // Element are already traversed via elements$ merging.
540
572
 
541
573
  // only check items, if the specified one is not expanded/inferred
542
574
  if (specifiedElement.items && !specifiedElement.items.$inferred)
543
- checkSpecifiedElement(inferredElement.items, specifiedElement.items, specifiedElement);
575
+ checkSpecifiedElement( inferredElement.items, specifiedElement.items, specifiedElement );
544
576
 
545
- if (specifiedElement.target &&
577
+ if (specifiedElement.target?._artifact && inferredElement.target?._artifact &&
546
578
  specifiedElement.target._artifact !== inferredElement.target._artifact) {
547
- error('query-mismatched-element', [
579
+ error( 'query-mismatched-element', [
548
580
  specifiedElement.target.location || specifiedElement.location, user,
549
581
  ], {
550
582
  '#': 'target',
551
583
  name: user.name.id,
552
584
  target: specifiedElement.target,
553
585
  art: inferredElement.target,
554
- });
586
+ } );
555
587
  }
556
588
 
557
589
  if (specifiedElement.foreignKeys) {
558
- const sKeys = Object.keys(specifiedElement.foreignKeys);
590
+ const sKeys = Object.keys( specifiedElement.foreignKeys );
559
591
  /** @type {any} */
560
592
  let iKeys = inferredElement;
561
593
  if (inferredElement._effectiveType !== 0) {
562
594
  while (iKeys._origin && !iKeys.foreignKeys)
563
595
  iKeys = iKeys._origin;
564
596
  }
565
- iKeys = Object.keys(iKeys.foreignKeys || {});
566
- if (sKeys.length !== iKeys.length || sKeys.some( fkey => !iKeys.includes(fkey))) {
567
- 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', [
568
600
  specifiedElement.foreignKeys.location || specifiedElement.location, user,
569
601
  ], {
570
602
  '#': 'foreignKeys',
571
603
  name: user.name.id,
572
604
  target: specifiedElement.target,
573
605
  art: inferredElement.target,
574
- });
606
+ } );
575
607
  }
576
608
  }
577
609
 
578
610
  if (specifiedElement.virtual) {
579
- const iVirtual = getInferredPropFromOrigin('virtual')?.val || false;
611
+ const iVirtual = getInferredPropFromOrigin( 'virtual' )?.val || false;
580
612
  if (!specifiedElement.virtual.val !== !iVirtual) {
581
- error('query-mismatched-element', [
613
+ error( 'query-mismatched-element', [
582
614
  specifiedElement.virtual.location || specifiedElement.location, user,
583
615
  ], {
584
616
  '#': 'prop', prop: 'virtual', name: user.name.id,
585
- });
617
+ } );
586
618
  }
587
619
  }
588
620
 
589
621
  // If cardinality is not specified, the compiler uses the inferred one.
590
622
  if (specifiedElement.cardinality) {
591
623
  const sCardinality = specifiedElement.cardinality;
592
- const iCardinality = getInferredPropFromOrigin('cardinality');
624
+ const iCardinality = getInferredPropFromOrigin( 'cardinality' );
593
625
  if (!iCardinality) {
594
- error('query-mismatched-element', [
626
+ error( 'query-mismatched-element', [
595
627
  sCardinality.location || specifiedElement.location, user,
596
628
  ], {
597
629
  '#': 'extra',
598
630
  prop: 'cardinality',
599
631
  name: user.name.id,
600
- });
632
+ } );
601
633
  }
602
634
  else {
603
635
  // Note: Cardinality does not have sourceMin (CSN "srcmin").
@@ -609,7 +641,7 @@ function resolve( model ) {
609
641
  for (const prop in props) {
610
642
  if (sCardinality[prop]?.val === iCardinality[prop]?.val)
611
643
  continue;
612
- error('query-mismatched-element', [
644
+ error( 'query-mismatched-element', [
613
645
  sCardinality[prop]?.location || sCardinality.location || specifiedElement.location,
614
646
  user,
615
647
  ], {
@@ -617,29 +649,29 @@ function resolve( model ) {
617
649
  '#': !sCardinality[prop] ? 'missing' : (iCardinality[prop] ? 'prop' : 'extra'),
618
650
  prop: `cardinality.${ props[prop] }`,
619
651
  name: user.name.id,
620
- });
652
+ } );
621
653
  }
622
654
  }
623
655
  }
624
656
 
625
657
  if (specifiedElement.value) {
626
- error('query-unexpected-property', [
658
+ error( 'query-unexpected-property', [
627
659
  specifiedElement.value.location || specifiedElement.location, user,
628
660
  ], {
629
661
  '#': 'calculatedElement', prop: 'value', name: user.name.id,
630
- });
662
+ } );
631
663
  }
632
664
 
633
665
  if (specifiedElement.key) { // TODO: `|| inferredElement.key?.val`, once to.sql is fixed
634
666
  // TODO: Do not use _origin chain for key; has been propagated in propagateKeyProps().
635
- const iKey = getInferredPropFromOrigin('key')?.val;
667
+ const iKey = getInferredPropFromOrigin( 'key' )?.val;
636
668
  // If "key" is specified or truthy in the inferred element, the values must match.
637
669
  if (!iKey !== !specifiedElement.key?.val) {
638
- error('query-mismatched-element', [
670
+ error( 'query-mismatched-element', [
639
671
  specifiedElement.key?.location || specifiedElement.location, user,
640
672
  ], {
641
673
  '#': specifiedElement.key ? 'prop' : 'missing', prop: 'key', name: user.name.id,
642
- });
674
+ } );
643
675
  }
644
676
  }
645
677
 
@@ -653,9 +685,9 @@ function resolve( model ) {
653
685
  const sEnumEntry = sEnumValues[name];
654
686
  const iEnumEntry = iEnumValues[name]?._effectiveType || iEnumValues[name];
655
687
  if (!iEnumEntry) {
656
- error('query-mismatched-element', [ specifiedElement.location, user ], {
688
+ error( 'query-mismatched-element', [ specifiedElement.location, user ], {
657
689
  '#': 'enumExtra', name: user.name.id, id: name,
658
- });
690
+ } );
659
691
  break;
660
692
  }
661
693
  else {
@@ -663,9 +695,9 @@ function resolve( model ) {
663
695
  const iVal = iEnumEntry.value?.val || iEnumEntry.value?.['#'] || name;
664
696
  const sVal = sEnumEntry.value?.val || sEnumEntry.value?.['#'] || name;
665
697
  if (iVal !== sVal) {
666
- error('query-mismatched-element', [ specifiedElement.location, user ], {
698
+ error( 'query-mismatched-element', [ specifiedElement.location, user ], {
667
699
  '#': 'enumVal', name: user.name.id, id: name,
668
- });
700
+ } );
669
701
  break;
670
702
  }
671
703
  }
@@ -682,7 +714,7 @@ function resolve( model ) {
682
714
  !inferredElement.value?.[prop] !== !specifiedElement[prop]) {
683
715
  error( 'query-mismatched-element', [ specifiedElement.location, specifiedElement ], {
684
716
  '#': specifiedElement[prop] ? 'extra' : 'missing', name: user.name.id, prop,
685
- });
717
+ } );
686
718
  return true;
687
719
  }
688
720
  return false;
@@ -692,8 +724,8 @@ function resolve( model ) {
692
724
  // Inferred property via _origin chain (0 === circular).
693
725
  let element = inferredElement;
694
726
  if (element._effectiveType !== 0) {
695
- while (getOrigin(element) && !element[prop])
696
- element = getOrigin(element);
727
+ while (getOrigin( element ) && !element[prop])
728
+ element = getOrigin( element );
697
729
  }
698
730
  return element[prop];
699
731
  }
@@ -731,21 +763,21 @@ function resolve( model ) {
731
763
  while (parent.kind === 'element')
732
764
  parent = parent._parent;
733
765
 
734
- if (!allowedInKind.includes(art._main.kind)) {
766
+ if (!allowedInKind.includes( art._main.kind )) {
735
767
  if (art.$inferred === 'include') {
736
768
  // even for include-chains, we find the correct ref due to element-expansion.
737
- const include = art._main.includes.find(i => i._artifact === art._origin._main);
738
- error('ref-invalid-calc-elem', [ include.location || art.value.location, art ],
739
- { '#': art._main.kind });
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 } );
740
772
  }
741
773
  else {
742
774
  error( 'def-invalid-calc-elem', loc, { '#': art._main.kind } );
743
775
  }
744
776
  }
745
- else if (!allowedInKind.includes(parent.kind)) {
777
+ else if (!allowedInKind.includes( parent.kind )) {
746
778
  error( 'def-invalid-calc-elem', loc, { '#': parent.kind } );
747
779
  }
748
- else if (effectiveType(art)?.elements) {
780
+ else if (effectiveType( art )?.elements) {
749
781
  // For inferred (e.g. included) calc elements, this error is already emitted at the origin.
750
782
  if (!art.$inferred) {
751
783
  if (art.type)
@@ -754,7 +786,7 @@ function resolve( model ) {
754
786
  error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
755
787
  }
756
788
  }
757
- else if (effectiveType(art)?.items) {
789
+ else if (effectiveType( art )?.items) {
758
790
  // For inferred (e.g. included) calc elements, this error is already emitted at the origin.
759
791
  if (!art.$inferred) {
760
792
  const isCast = art.type?.$inferred === 'cast';
@@ -770,7 +802,7 @@ function resolve( model ) {
770
802
  if (art[prop]?.val) {
771
803
  // probably better than a parse error (which is good for DEFAULT vs calc),
772
804
  // also appears with parse-cdl:
773
- error('def-invalid-calc-elem', loc, { '#': prop });
805
+ error( 'def-invalid-calc-elem', loc, { '#': prop } );
774
806
  return; // one error is enough
775
807
  }
776
808
  }
@@ -782,22 +814,19 @@ function resolve( model ) {
782
814
  ? art.value.args[0]?._artifact
783
815
  : art.value._artifact;
784
816
  if (elem && art.type) { // has explicit type
785
- if (art.type._artifact?.elements) {
786
- error('type-cast-to-structured', [ art.type.location, art ], {},
787
- 'Can\'t cast to structured element');
788
- }
789
- else if (elem.elements) { // TODO: calc elements
790
- error('type-cast-structured', [ art.type.location, art ], {},
791
- 'Structured elements can\'t be cast to a different type');
792
- }
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' } );
793
821
  }
794
822
  }
795
823
 
796
- // Return type containing the assoc spec (keys, on); note that no
797
- // propagation/rewrite has been done yet, cyclic dependency must have been
798
- // checked before!
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
+ */
799
829
  function getAssocSpec( type ) {
800
- // only to be called without cycles
801
830
  let unmanaged = null;
802
831
  while (type) {
803
832
  if (type.on) // if unmanaged, continue trying to find targetAspect
@@ -857,6 +886,11 @@ function resolve( model ) {
857
886
  // (we could downgrade it to a warning if name is equal to unique source element name)
858
887
  // TODO: Some helping text mentioning an alias name would be useful
859
888
  }
889
+ for (const limit of query.$limit || []) // LIMIT from UNION:
890
+ resolveLimit( limit );
891
+ if (query.limit)
892
+ resolveLimit( query.limit );
893
+
860
894
  return;
861
895
 
862
896
  function resolveJoinOn( join ) {
@@ -864,27 +898,35 @@ function resolve( model ) {
864
898
  for (const j of join.args)
865
899
  resolveJoinOn( j );
866
900
  if (join.on)
867
- resolveExpr( join.on, 'join-on', query );
868
- // TODO: check restrictions according to join "query"
901
+ resolveExpr( join.on, 'join-on', join );
869
902
  }
870
903
  }
871
904
 
872
- // Note the strange name resolution (dynamic part) for ORDER BY: the same
873
- // as for select items if it is an expression, but first look at select
874
- // item alias (i.e. like `$projection.NAME` if it is a path. If it is an
875
- // ORDER BY of an UNION, do not allow any dynamic path in an expression,
876
- // and only allow the elements of the leading query if it is a path.
877
- //
878
- // This seems to be similar, but different in SQLite 3.22.0: ORDER BY seems
879
- // to bind stronger than UNION (see <SQLite>/src/parse.y), and the name
880
- // resolution seems to use select item aliases from all SELECTs of the
881
- // UNION (see <SQLite>/test/tkt2822.test).
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
+ */
882
917
  function resolveBy( array, refMode, exprMode ) {
883
918
  for (const value of array ) {
884
919
  if (value)
885
920
  resolveExpr( value, (value.path ? refMode : exprMode), query );
886
921
  }
887
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
+ }
888
930
  }
889
931
 
890
932
  function resolveTarget( art, obj ) {
@@ -915,7 +957,7 @@ function resolve( model ) {
915
957
  { '#': isComposition ? 'comp' : 'std' }, {
916
958
  std: 'An unmanaged association can\'t be defined as type',
917
959
  comp: 'An unmanaged composition can\'t be defined as type',
918
- });
960
+ } );
919
961
  // TODO: also warning if inside structure
920
962
  }
921
963
  else {
@@ -973,7 +1015,7 @@ function resolve( model ) {
973
1015
  const serviceKeys = keyElementNames( issue.target.elements );
974
1016
  const modelKeys = keyElementNames( modelTarget.elements );
975
1017
  if (modelKeys.length !== serviceKeys.length) {
976
- issue.id = modelKeys.find( id => !serviceKeys.includes( id ));
1018
+ issue.id = modelKeys.find( id => !serviceKeys.includes( id ) );
977
1019
  issue['#'] = 'missing';
978
1020
  }
979
1021
  else if (!modelKeys.every( (id, index) => id === serviceKeys[index] )) {
@@ -1026,7 +1068,7 @@ function resolve( model ) {
1026
1068
  }
1027
1069
 
1028
1070
  function addImplicitForeignKeys( art, obj, target ) {
1029
- obj.foreignKeys = Object.create(null);
1071
+ obj.foreignKeys = Object.create( null );
1030
1072
  forEachInOrder( target, 'elements', ( elem, name ) => {
1031
1073
  if (elem.key && elem.key.val) {
1032
1074
  const { location } = obj.target;
@@ -1042,14 +1084,14 @@ function resolve( model ) {
1042
1084
  // the following should be done automatically, since we run resolveRefs after that
1043
1085
  setArtifactLink( key.targetElement, elem );
1044
1086
  setArtifactLink( key.targetElement.path[0], elem );
1045
- setLink( key, '_effectiveType', effectiveType(elem) );
1046
- dependsOn(key, elem, location);
1087
+ setLink( key, '_effectiveType', effectiveType( elem ) );
1088
+ dependsOn( key, elem, location );
1047
1089
  // TODO TMP: instead, make managed composition of aspects and unmanaged
1048
1090
  // assocs not depend on their `on` condition (empty `_deps` after resolve)
1049
1091
  if (art.$inferred !== 'aspect-composition')
1050
1092
  dependsOnSilent( art, key );
1051
1093
  }
1052
- });
1094
+ } );
1053
1095
  obj.foreignKeys[$inferred] = 'keys';
1054
1096
  }
1055
1097
 
@@ -1078,7 +1120,7 @@ function resolve( model ) {
1078
1120
  * for derived association like for `type DerivedT: T`, or exposed ones.
1079
1121
  */
1080
1122
  function addForeignKeyNavigations( art, silent = false ) {
1081
- art.$keysNavigation = Object.create(null);
1123
+ art.$keysNavigation = Object.create( null );
1082
1124
  const keys = [];
1083
1125
  // Basically sort foreign keys according to length of target element ref.
1084
1126
  // This way, we complain about ref to sub element (`elem.sub`) even if it
@@ -1089,7 +1131,7 @@ function resolve( model ) {
1089
1131
  const arr = keys[path.length] || (keys[path.length] = []);
1090
1132
  arr.push( key );
1091
1133
  }
1092
- });
1134
+ } );
1093
1135
  for (const key of keys.flat()) {
1094
1136
  let dict = art.$keysNavigation;
1095
1137
  const { path } = key.targetElement;
@@ -1102,7 +1144,7 @@ function resolve( model ) {
1102
1144
  if (item === last)
1103
1145
  setArtifactLink( nav, key );
1104
1146
  else
1105
- nav.$keysNavigation = Object.create(null);
1147
+ nav.$keysNavigation = Object.create( null );
1106
1148
  }
1107
1149
  else if (item === last || nav._artifact) {
1108
1150
  if (silent)
@@ -1187,7 +1229,7 @@ function resolve( model ) {
1187
1229
  target: 'The redirected target $(ART) is a complex view',
1188
1230
  // eslint-disable-next-line max-len
1189
1231
  targetOp: 'The redirected target $(ART) is a complex view with $(KEYWORD)',
1190
- });
1232
+ } );
1191
1233
  break;
1192
1234
  }
1193
1235
  target = from._artifact;
@@ -1203,7 +1245,7 @@ function resolve( model ) {
1203
1245
  let redirected = null;
1204
1246
  chain.reverse();
1205
1247
  let news = [ { chain, sources: [ target ] } ];
1206
- const dict = Object.create(null);
1248
+ const dict = Object.create( null );
1207
1249
  while (news.length) {
1208
1250
  const outer = news;
1209
1251
  news = [];
@@ -1328,7 +1370,7 @@ function resolve( model ) {
1328
1370
  function resolveParamsAndWhere( step, expected, user, isLast ) {
1329
1371
  const alias = (step._navigation?.kind === '$tableAlias') ? step._navigation : null;
1330
1372
  const type = alias || effectiveType( step._artifact );
1331
- const art = (type && type.target) ? type.target._artifact : type;
1373
+ const art = type?.target ? type.target._artifact : type;
1332
1374
  if (!art)
1333
1375
  return;
1334
1376
  const entity = art.kind === 'entity' &&
@@ -1361,8 +1403,8 @@ function resolve( model ) {
1361
1403
 
1362
1404
  function resolveParams( dict, art, entity, expected, user, stepLocation ) {
1363
1405
  if (!entity || !entity.params) {
1364
- let first = dict[Object.keys(dict)[0]];
1365
- if (Array.isArray(first))
1406
+ let first = dict[Object.keys( dict )[0]];
1407
+ if (Array.isArray( first ))
1366
1408
  first = first[0];
1367
1409
  error( 'expr-unexpected-argument',
1368
1410
  [ dict[$location] || dictLocation( dict, first?.name?.location || stepLocation ),
@@ -1377,7 +1419,7 @@ function resolve( model ) {
1377
1419
  return;
1378
1420
  }
1379
1421
  const exp = (expected === 'from') ? 'from-args' : expected;
1380
- if (Array.isArray(dict)) {
1422
+ if (Array.isArray( dict )) {
1381
1423
  const loc = [ dict[0] && dict[0].location || stepLocation, user ];
1382
1424
  error( 'expr-expected-named-argument', loc, {},
1383
1425
  'Expected named parameters for the entity' );
@@ -1389,7 +1431,7 @@ function resolve( model ) {
1389
1431
  for (const name in dict) {
1390
1432
  const param = art.params[name];
1391
1433
  const arg = dict[name];
1392
- for (const a of Array.isArray(arg) ? arg : [ arg ]) {
1434
+ for (const a of Array.isArray( arg ) ? arg : [ arg ]) {
1393
1435
  setArtifactLink( a.name, param );
1394
1436
  if (!param) {
1395
1437
  error( 'expr-undefined-param', [ a.name.location, user ], { art, id: name },
@@ -1401,20 +1443,22 @@ function resolve( model ) {
1401
1443
  }
1402
1444
  }
1403
1445
 
1404
- // Return condensed info about reference in select item
1405
- // - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
1406
- // - sourceElem (in query) -> { navigation: navElem, item: path[0], tableAlias }
1407
- // - mixinElem -> { navigation: mixinElement, item: path[0] }
1408
- // - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
1409
- // - $self -> { item: undefined, tableAlias: $self }
1410
- // - $parameters.P, :P -> {}
1411
- // - $now, current_date -> {}
1412
- // - undef, redef -> {}
1413
- // With 'navigation': store that navigation._artifact is projected
1414
- // With 'navigation': rewrite its ON condition
1415
- // With navigation: Do KEY propagation
1416
- //
1417
- // TODO: re-think this function, copied in populate.js and tweak-assocs.js
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
+ */
1418
1462
  function pathNavigation( ref ) {
1419
1463
  // currently, indirectly projectable elements are not included - we might
1420
1464
  // keep it this way! If we want them to be included - be aware: cycles