@sap/cds-compiler 5.2.0 → 5.3.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 (54) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/bin/cdsc.js +5 -0
  3. package/bin/cdshi.js +8 -8
  4. package/doc/CHANGELOG_BETA.md +9 -4
  5. package/lib/api/validate.js +5 -0
  6. package/lib/base/message-registry.js +25 -1
  7. package/lib/base/messages.js +1 -1
  8. package/lib/base/model.js +0 -1
  9. package/lib/compiler/assert-consistency.js +2 -2
  10. package/lib/compiler/builtins.js +1 -1
  11. package/lib/compiler/checks.js +25 -6
  12. package/lib/compiler/define.js +24 -28
  13. package/lib/compiler/extend.js +11 -13
  14. package/lib/compiler/generate.js +3 -3
  15. package/lib/compiler/populate.js +13 -7
  16. package/lib/compiler/propagator.js +2 -2
  17. package/lib/compiler/resolve.js +58 -60
  18. package/lib/compiler/shared.js +5 -5
  19. package/lib/compiler/tweak-assocs.js +247 -34
  20. package/lib/compiler/utils.js +40 -32
  21. package/lib/compiler/xpr-rewrite.js +44 -58
  22. package/lib/edm/annotations/genericTranslation.js +4 -4
  23. package/lib/edm/csn2edm.js +2 -2
  24. package/lib/edm/edm.js +46 -21
  25. package/lib/edm/edmInboundChecks.js +0 -1
  26. package/lib/edm/edmPreprocessor.js +40 -27
  27. package/lib/edm/edmUtils.js +1 -1
  28. package/lib/gen/BaseParser.js +180 -122
  29. package/lib/gen/CdlParser.js +2226 -2170
  30. package/lib/gen/language.checksum +1 -1
  31. package/lib/gen/language.interp +1 -1
  32. package/lib/gen/languageParser.js +3820 -3777
  33. package/lib/inspect/inspectPropagation.js +1 -1
  34. package/lib/json/from-csn.js +5 -3
  35. package/lib/json/to-csn.js +7 -10
  36. package/lib/language/antlrParser.js +38 -4
  37. package/lib/language/errorStrategy.js +1 -1
  38. package/lib/language/genericAntlrParser.js +4 -4
  39. package/lib/language/multiLineStringParser.js +1 -1
  40. package/lib/main.d.ts +23 -0
  41. package/lib/model/cloneCsn.js +22 -13
  42. package/lib/optionProcessor.js +7 -7
  43. package/lib/parsers/AstBuildingParser.js +155 -37
  44. package/lib/parsers/CdlGrammar.g4 +154 -81
  45. package/lib/parsers/Lexer.js +20 -10
  46. package/lib/render/toCdl.js +23 -18
  47. package/lib/transform/addTenantFields.js +4 -4
  48. package/lib/transform/db/rewriteCalculatedElements.js +11 -5
  49. package/lib/transform/db/transformExists.js +43 -18
  50. package/lib/transform/effective/main.js +1 -1
  51. package/lib/transform/forRelationalDB.js +8 -7
  52. package/lib/utils/moduleResolve.js +1 -1
  53. package/package.json +1 -1
  54. package/share/messages/redirected-to-complex.md +6 -3
@@ -41,10 +41,13 @@ function tweakAssocs( model ) {
41
41
  extendForeignKeys,
42
42
  createRemainingAnnotateStatements,
43
43
  mergeSpecifiedForeignKeys,
44
+ navigationEnv,
45
+ redirectionChain,
44
46
  } = model.$functions;
45
47
 
46
48
  Object.assign(model.$functions, {
47
- firstProjectionForPath,
49
+ findRewriteTarget,
50
+ cachedRedirectionChain,
48
51
  });
49
52
 
50
53
  // Phase 5: rewrite associations
@@ -125,7 +128,7 @@ function tweakAssocs( model ) {
125
128
  // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
126
129
  info( 'assoc-outside-service', loc, { '#': text, target, service: main._service }, {
127
130
  std: 'Association target $(TARGET) is outside any service',
128
- // eslint-disable-next-line max-len
131
+ // eslint-disable-next-line @stylistic/js/max-len
129
132
  exposed: 'If association is published in service $(SERVICE), its target $(TARGET) is outside any service',
130
133
  } );
131
134
  }
@@ -142,7 +145,7 @@ function tweakAssocs( model ) {
142
145
  if (assoc && assoc.foreignKeys) {
143
146
  error( 'rewrite-key-for-unmanaged', [ elem.on.location, elem ],
144
147
  { keyword: 'on', art: assocWithExplicitSpec( assoc ) },
145
- // eslint-disable-next-line max-len
148
+ // eslint-disable-next-line @stylistic/js/max-len
146
149
  'Do not specify an $(KEYWORD) condition when redirecting the managed association $(ART)' );
147
150
  }
148
151
  checkIgnoredFilter( elem );
@@ -398,11 +401,18 @@ function tweakAssocs( model ) {
398
401
  // same (TODO later: set status whether rewrite changes anything),
399
402
  // especially problematic are refs starting with $self:
400
403
  setExpandStatus( elem, 'target' );
404
+
405
+ // There were previous issues in resolving the target artifact.
406
+ // Avoid further compiler messages.
407
+ if (!elem.target._artifact)
408
+ return;
409
+
401
410
  if (elem._parent?.kind === 'element') {
402
411
  // managed association as sub element not supported yet
403
412
  // TODO: Only report once for multi-include chains, see
404
413
  // Associations/SubElements/UnmanagedInSubElement.err.cds
405
414
  error( 'type-unsupported-rewrite', [ elem.location, elem ], { '#': 'sub-element' } );
415
+ removeArtifactLinks();
406
416
  return;
407
417
  }
408
418
  const nav = (elem._main?.query && elem.value)
@@ -415,18 +425,11 @@ function tweakAssocs( model ) {
415
425
 
416
426
  const { navigation } = nav;
417
427
  if (!navigation) { // TODO: what about $projection.assoc as myAssoc ?
418
- if (elem._columnParent)
428
+ if (elem._columnParent) {
419
429
  error( 'rewrite-not-supported', [ elem.target.location, elem ], { '#': 'inline-expand' } );
420
- return; // should not happen: $projection, $magic, or ref to const
421
- }
422
- const isAssocInStruct = (navigation !== assoc && navigation._origin !== assoc);
423
- if (isAssocInStruct) {
424
- // For "[sub.]assoc1.assoc2": not supported, yet (#3977)
425
- const multipleAssoc = elem.value.path.slice(0, -1).some(segment => segment._artifact?.target);
426
- if (multipleAssoc && elem._redirected !== null) { // null = already reported
427
- error('rewrite-not-supported', [ elem.target.location, elem ], { '#': 'secondary' });
428
- return;
430
+ removeArtifactLinks();
429
431
  }
432
+ return; // should not happen: $projection, $magic, or ref to const
430
433
  }
431
434
 
432
435
  if (!nav.tableAlias || nav.tableAlias.path) {
@@ -436,17 +439,30 @@ function tweakAssocs( model ) {
436
439
  }
437
440
  else if (elem._columnParent) {
438
441
  error( 'rewrite-not-supported', [ elem.target.location, elem ], { '#': 'inline-expand' } );
442
+ removeArtifactLinks();
439
443
  return;
440
444
  }
441
445
  else {
442
446
  // TODO: support that, now that the ON condition is rewritten in the right order
443
447
  error( null, [ elem.value.location, elem ], {},
444
448
  'Selecting unmanaged associations from a sub query is not supported' );
449
+ removeArtifactLinks();
445
450
  return;
446
451
  }
447
452
 
448
453
  addConditionFromAssocPublishing( elem, assoc, nav );
449
454
  elem.on.$inferred = 'rewrite';
455
+
456
+ /**
457
+ * Clear all `_artifact` links in the ON-condition to avoid follow-up
458
+ * issues during ON-condition rewriting of associations that inherit
459
+ * the ON-condition.
460
+ */
461
+ function removeArtifactLinks() {
462
+ traverseExpr( elem.on, 'rewrite-on', elem, (expr) => {
463
+ setArtifactLink( expr, null );
464
+ } );
465
+ }
450
466
  }
451
467
 
452
468
  /**
@@ -537,6 +553,7 @@ function tweakAssocs( model ) {
537
553
  function filterToCondition( assocPathStep, elem, nav ) {
538
554
  const cond = copyExpr( assocPathStep.where );
539
555
  cond.$parens = [ assocPathStep.location ];
556
+ const navEnv = nav && followNavigationPath( elem.value?.path, nav ) || nav?.tableAlias;
540
557
  traverseExpr( cond, 'rewrite-filter', elem, (expr) => {
541
558
  if (!expr.path || expr.path.length === 0)
542
559
  return;
@@ -560,7 +577,7 @@ function tweakAssocs( model ) {
560
577
  setLink( expr.path[0], '_navigation', assocPathStep._navigation );
561
578
  }
562
579
  // up to here, filter is relative to original association
563
- rewriteExpr( expr, elem, nav?.tableAlias );
580
+ rewriteExpr( expr, elem, nav?.tableAlias, navEnv );
564
581
  }
565
582
  } );
566
583
 
@@ -664,28 +681,21 @@ function tweakAssocs( model ) {
664
681
  // element in included structure / element in source ref/d by table alias.
665
682
 
666
683
  // TODO: complain about $self (unclear semantics)
667
- // console.log( info(null, [assoc.name.location, assoc],
668
- // { art: expr._artifact, names: expr.path.map(i=>i.id) }, 'A').toString(), expr.path)
669
684
 
670
685
  if (!expr.path || !expr._artifact)
671
686
  return;
672
687
  if (!assoc._main)
673
688
  return;
674
689
  if (navEnv) { // from ON cond of element in source ref/d by table alias
675
- const source = tableAlias._origin;
676
690
  const root = expr.path[0]._navigation || expr.path[0]._artifact;
677
- if (!root || root._main !== source)
678
- return; // not $self or source element
691
+ if (!root || root.kind === 'builtin')
692
+ return; // not $self or source element, e.g. builtin
693
+
694
+ // parameters are not allowed in ON-conditions; error emitted elsewhere already
679
695
  if (expr.scope === 'param' || root.kind === '$parameters')
680
- return; // are not allowed anyway - there was an error before
681
- const startIndex = (root.kind === '$self' ? 1 : 0);
682
- const exprNavigation = (root.kind === '$self' ? tableAlias : navEnv);
683
- const result = firstProjectionForPath( expr.path, startIndex, exprNavigation, assoc );
684
- // For `assoc[…]`, ensure that we don't rewrite to another projection on `assoc`.
685
- if (result.item && assoc._origin === result.item._artifact)
686
- result.elem = assoc;
696
+ return;
687
697
 
688
- rewritePath( expr, result.item, assoc, result.elem, assoc.value.location );
698
+ rewritePathForEnv( expr, navEnv, assoc );
689
699
  }
690
700
  else if (assoc._main.query) { // from ON cond of mixin element in query
691
701
  const root = expr.path[0]._navigation || expr.path[0]._artifact;
@@ -693,7 +703,7 @@ function tweakAssocs( model ) {
693
703
  if (assoc.$errorReported !== 'assoc-unexpected-scope') {
694
704
  error( 'assoc-unexpected-scope', [ assoc.value.location, assoc ],
695
705
  { id: assoc.value._artifact.name.id },
696
- // eslint-disable-next-line max-len
706
+ // eslint-disable-next-line @stylistic/js/max-len
697
707
  'Association $(ID) can\'t be projected because its ON-condition refers to a parameter' );
698
708
  assoc.$errorReported = 'assoc-unexpected-scope';
699
709
  }
@@ -702,6 +712,7 @@ function tweakAssocs( model ) {
702
712
  if (expr.path[0]._navigation) { // rewrite src elem, mixin, $self[.elem]
703
713
  const nav = pathNavigation( expr );
704
714
  const elem = (assoc._origin === root) ? assoc : navProjection( nav.navigation, assoc );
715
+ // TODO: Use rewritePathForEnv(); make it handle mixins
705
716
  rewritePath( expr, nav.item, assoc, elem,
706
717
  nav.item ? nav.item.location : expr.path[0].location );
707
718
  }
@@ -725,11 +736,11 @@ function tweakAssocs( model ) {
725
736
  if (!(elem === item._artifact || // redirection for explicit def
726
737
  elem._origin === item._artifact)) {
727
738
  const art = assoc._origin;
728
- // eslint-disable-next-line max-len
739
+ // eslint-disable-next-line @stylistic/js/max-len
729
740
  warning( 'rewrite-shadowed', [ elem.name.location, elem ], { art: art && effectiveType( art ) }, {
730
- // eslint-disable-next-line max-len
741
+ // eslint-disable-next-line @stylistic/js/max-len
731
742
  std: 'This element is not originally referred to in the ON-condition of association $(ART)',
732
- // eslint-disable-next-line max-len
743
+ // eslint-disable-next-line @stylistic/js/max-len
733
744
  element: 'This element is not originally referred to in the ON-condition of association $(MEMBER) of $(ART)',
734
745
  } );
735
746
  }
@@ -737,6 +748,138 @@ function tweakAssocs( model ) {
737
748
  }
738
749
  }
739
750
 
751
+ /**
752
+ * Rewrite the given reference by using projected elements of the given
753
+ * navigation environment.
754
+ *
755
+ * @param {XSN.Expression} ref
756
+ * @param {object} navEnv
757
+ * @param {XSN.Artifact} user
758
+ */
759
+ function rewritePathForEnv( ref, navEnv, user ) {
760
+ // TODO: combine with rewriteGenericAnnoPath() of xpr-rewrite
761
+
762
+ // reset artifact link; we'll set it again if there are no errors
763
+ setArtifactLink( ref, null );
764
+
765
+ const rootItem = ref.path[0];
766
+ const root = ref.path[0]._navigation || ref.path[0]._artifact;
767
+ const startIndex = (root.kind === '$self' ? 1 : 0);
768
+
769
+ if (root.kind === '$self') {
770
+ let rootEnv = navEnv;
771
+ while (rootEnv?.kind === '$navElement') {
772
+ if (rootEnv._origin?.target?._artifact === root._origin)
773
+ break;
774
+ rootEnv = rootEnv._parent;
775
+ }
776
+ navEnv = rootEnv;
777
+ }
778
+
779
+ // Store the original artifact, so that we can use it to
780
+ // calculate a redirection chain later on.
781
+ ref.path.forEach((item) => {
782
+ if (item._artifact)
783
+ setLink( item, '_originalArtifact', item._artifact );
784
+ });
785
+
786
+ let env = navEnv;
787
+ let art = rootItem._artifact;
788
+ let isTargetSide = null;
789
+
790
+ for (let i = startIndex; i < ref.path.length; ++i) {
791
+ if (i > startIndex && art.target) {
792
+ // if the current artifact is an association, we need to respect the redirection
793
+ // chain from original target to new one.
794
+ // FIXME: Won't work with associations in projected structures.
795
+ const origTarget = ref.path[i - 1]?._originalArtifact?.target?._artifact;
796
+ const chain = cachedRedirectionChain( art, origTarget );
797
+ if (!chain) {
798
+ missingProjection( ref, i, user, false );
799
+ return;
800
+ }
801
+ for (const alias of chain) {
802
+ art = rewritePathItemForEnv( ref, alias, i, user );
803
+ isTargetSide ??= (art === user);
804
+ if (!art) {
805
+ missingProjection( ref, i, user, isTargetSide );
806
+ return;
807
+ }
808
+ }
809
+ }
810
+
811
+ art = rewritePathItemForEnv( ref, env, i, user );
812
+ isTargetSide ??= (art === user);
813
+ if (!art) {
814
+ missingProjection( ref, i, user, isTargetSide );
815
+ return;
816
+ }
817
+ env = navigationEnv( art, null, null, 'nav' );
818
+ }
819
+ setArtifactLink( ref, art );
820
+
821
+ if (startIndex === 0 && rootItem.id.startsWith('$')) {
822
+ // TODO: What about filters? Also rewritten there?
823
+ // After rewriting, if an element starts with `$` -> add root prefix
824
+ // FIXME: "user" not correct for association inside sub-element,
825
+ // because `user._parent` is assumed to be the query
826
+ prependSelfToPath( ref.path, user );
827
+ }
828
+ }
829
+
830
+ function rewritePathItemForEnv( ref, navEnv, index, user ) {
831
+ const rewriteTarget = findRewriteTarget( ref, index, navEnv, user );
832
+ const found = rewriteTarget[0];
833
+ if (!found) {
834
+ setArtifactLink( ref.path[index], found );
835
+ return found;
836
+ }
837
+
838
+ if (rewriteTarget[1] > index) {
839
+ // we keep the last segment, in case it has non-enumerable properties
840
+ ref.path[index] = ref.path[rewriteTarget[1]];
841
+ ref.path.splice(index + 1, rewriteTarget[1] - index);
842
+ }
843
+
844
+ const item = ref.path[index];
845
+ if (item.id !== found.name.id || (rewriteTarget[1] - index) !== 0)
846
+ item.id = found.name.id;
847
+
848
+ return setArtifactLink( ref.path[index], found );
849
+ }
850
+
851
+ /**
852
+ * @param {XSN.Path} ref
853
+ * @param {number} index
854
+ * @param {XSN.Artifact} user
855
+ * @param {boolean} isTargetSide
856
+ */
857
+ function missingProjection( ref, index, user, isTargetSide ) {
858
+ const item = ref.path[index];
859
+ if (!isTargetSide) {
860
+ const { location } = user.value;
861
+ const rootItem = ref.path[0];
862
+ const elemref = rootItem._navigation?.kind === '$self' ? ref.path.slice(1) : ref.path;
863
+ // TODO: Fix message for sub-elements: `s: { a: Association on x=1, x: Integer};` for x
864
+ error( 'rewrite-not-projected', [ location, user ], {
865
+ name: user.name.id,
866
+ art: item._artifact || item._originalArtifact,
867
+ elemref: { ref: elemref },
868
+ } );
869
+ }
870
+ else {
871
+ const isExplicit = user.target && !user.target.$inferred;
872
+ const loc = isExplicit ? user.target.location : item.location;
873
+ error( 'query-undefined-element', [ loc, user ], {
874
+ '#': isExplicit ? 'redirected' : 'std',
875
+ id: item.id,
876
+ name: user.name.id,
877
+ target: user.target._artifact,
878
+ keyword: 'redirected to',
879
+ } );
880
+ }
881
+ }
882
+
740
883
  function rewritePath( ref, item, assoc, elem, location ) {
741
884
  const { path } = ref;
742
885
  const root = path[0];
@@ -746,9 +889,6 @@ function tweakAssocs( model ) {
746
889
  // TODO: Fix message for sub-elements: `s: { a: Association on x=1, x: Integer};` for x
747
890
  error( 'rewrite-not-projected', [ location, assoc ], {
748
891
  name: assoc.name.id, art: elemref[0]._artifact, elemref: { ref: elemref },
749
- }, {
750
- std: 'Projected association $(NAME) uses non-projected element $(ELEMREF)',
751
- element: 'Projected association $(NAME) uses non-projected element $(ELEMREF) of $(ART)',
752
892
  } );
753
893
  }
754
894
  delete root._navigation;
@@ -841,6 +981,38 @@ function tweakAssocs( model ) {
841
981
  } );
842
982
  return null;
843
983
  }
984
+
985
+ /**
986
+ * Get the redirection chain between the element's target and the original target.
987
+ * Returns `null` if there is no valid chain.
988
+ * Uses `_redirected` if valid.
989
+ *
990
+ * @param {XSN.Artifact} elem
991
+ * @param {XSN.Artifact} origTarget
992
+ * @returns {null|XSN.Artifact[]}
993
+ */
994
+ function cachedRedirectionChain( elem, origTarget ) {
995
+ const target = elem.target?._artifact;
996
+ if (!target || !origTarget)
997
+ return null;
998
+ if (target === origTarget)
999
+ return [];
1000
+
1001
+ if (elem._redirected === null) {
1002
+ // means: "don't touch paths after assoc"
1003
+ // TODO: figure out if we can assume that here as well
1004
+ return [];
1005
+ }
1006
+
1007
+ if (elem._redirected) {
1008
+ // No need to recalculate if the original target is already in '_redirected'.
1009
+ const i = elem._redirected.findIndex(ta => ta._origin === origTarget);
1010
+ if (i > -1)
1011
+ return elem._redirected.slice(i); // TODO: check if it is always "i===0".
1012
+ }
1013
+
1014
+ return redirectionChain( elem, target, origTarget, true );
1015
+ }
844
1016
  }
845
1017
 
846
1018
  function navProjection( navigation, preferred ) {
@@ -861,6 +1033,46 @@ function navProjection( navigation, preferred ) {
861
1033
  return navigation._projections?.[0] || null;
862
1034
  }
863
1035
 
1036
+ function findRewriteTarget( expr, index, env, user ) {
1037
+ if (env.kind === '$navElement' || env.kind === '$tableAlias') {
1038
+ const r = firstProjectionForPath( expr.path, index, env, user );
1039
+ return [ r.elem, r.index ];
1040
+ }
1041
+
1042
+ const item = expr.path[index];
1043
+ // If the artifact is already in the same definition, we must not check the query.
1044
+ // Or if it is not a query -> no $navElement -> use `elements`
1045
+ if (item._artifact?._main === env || !env.query && env.kind !== 'select') {
1046
+ if (env.elements?.[item.id])
1047
+ return [ env.elements[item.id], index ];
1048
+ return [ null, expr.path.length ];
1049
+ }
1050
+ const items = (env._leadingQuery || env)._combined?.[item.id];
1051
+ const allNavs = !items || Array.isArray(items) ? items : [ items ];
1052
+
1053
+ // If the annotation target itself has a table alias, require projections of that
1054
+ // table alias. Of course, that only works if we're talking about the same query.
1055
+ const tableAlias = (user._main?._origin === item._artifact?._main &&
1056
+ user.value?.path[0]?._navigation?.kind === '$tableAlias')
1057
+ ? user.value.path[0]._navigation : null;
1058
+
1059
+ // Look at all table aliase that could project `item` and only select
1060
+ // those that have actual projections.
1061
+ const navs = allNavs?.filter(p => p._origin === item._artifact &&
1062
+ (!tableAlias || tableAlias === p._parent));
1063
+ if (!navs || navs.length === 0)
1064
+ return [ null, expr.path.length ];
1065
+
1066
+ // If there are multiple navigations for the element, just use the first that matches.
1067
+ // In case of table aliases, it's just one.
1068
+ for (const nav of navs) {
1069
+ const r = firstProjectionForPath( expr.path, index, nav._parent, user );
1070
+ if (r.elem)
1071
+ return [ r.elem, r.index ];
1072
+ }
1073
+
1074
+ return [ null, expr.path.length ];
1075
+ }
864
1076
 
865
1077
  /**
866
1078
  * For a path `a.b.c.d`, return a projection for the first path item that is projected,
@@ -961,6 +1173,7 @@ function followNavigationPath( path, nav ) {
961
1173
  return navItem;
962
1174
  }
963
1175
 
1176
+
964
1177
  /**
965
1178
  * Return condensed info about reference in select item
966
1179
  * - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
@@ -212,9 +212,11 @@ function storeExtension( elem, name, prop, parent, block ) {
212
212
  /** @type {(a: any, b: any) => boolean} */
213
213
  const testFunctionPlaceholder = () => true;
214
214
 
215
- // Return path step if the path navigates along an association whose final type
216
- // satisfies function `test`; "navigates along" = last path item not considered
217
- // without truthy optional argument `alsoTestLast`.
215
+ /**
216
+ * Return path step if the path navigates along an association whose final type
217
+ * satisfies function `test`; "navigates along" = last path item not considered
218
+ * without truthy optional argument `alsoTestLast`.
219
+ */
218
220
  function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = false ) {
219
221
  for (const item of ref.path || []) {
220
222
  const art = item && item._artifact; // item can be null with parse error
@@ -435,7 +437,7 @@ function forEachJoinOn( query, from, callback ) {
435
437
  }
436
438
 
437
439
  function forEachExprArray( query, array, refContext, exprContext, callback ) {
438
- for (const expr of array ) {
440
+ for (const expr of array) {
439
441
  if (expr)
440
442
  callback( expr, (expr.path ? refContext : exprContext), query );
441
443
  }
@@ -512,8 +514,10 @@ function traverseQueryExtra( main, callback ) {
512
514
  }
513
515
  }
514
516
 
515
- // Returns what was available at view._from[0] before:
516
- // (think first whether to really use this function)
517
+ /**
518
+ * Returns what was available at view._from[0] before:
519
+ * (think first whether to really use this function)
520
+ */
517
521
  function viewFromPrimary( view ) {
518
522
  let query = view.$queries?.[0];
519
523
  while (query?._origin?.kind === 'select') // sub query in from
@@ -521,24 +525,26 @@ function viewFromPrimary( view ) {
521
525
  return dictFirst( query?.$tableAliases );
522
526
  }
523
527
 
524
- // About Helper property $expand for faster the XSN-to-CSN transformation
525
- // - null/undefined: artifact, member, items does not contain expanded members
526
- // - 'origin': all expanded (sub) elements have no new target/on and no new annotations
527
- // that value is only on elements, types, and params -> no other members
528
- // when set, only on elem/art with expanded elements
529
- // - 'target': all expanded (sub) elements might only have new target/on, but
530
- // no individual annotations on any (sub) member
531
- // when set, traverse all parents where the value has been 'origin' before
532
- // - 'annotate': at least one inferred (sub) member has an individual annotation,
533
- // not counting propagated ones; set up to the definition (main artifact)
534
- // (only set with anno on $inferred elem), annotate “beats” target
535
- // Usage according to CSN flavor:
536
- // - gensrc: do not render inferred elements (including expanded elements),
537
- // collect annotate statements with value 'annotate'
538
- // - client: do not render expanded sub elements if artifact/member is no type, has a type,
539
- // has $expand = 'origin', and all its _origin also have $expand = 'origin'
540
- // (might sometimes render the elements unnecessarily, which is not wrong)
541
- // - universal: do not render expanded sub elements if $expand = 'origin'
528
+ /**
529
+ * About Helper property $expand for faster the XSN-to-CSN transformation
530
+ * - null/undefined: artifact, member, items does not contain expanded members
531
+ * - 'origin': all expanded (sub) elements have no new target/on and no new annotations
532
+ * that value is only on elements, types, and params -> no other members
533
+ * when set, only on elem/art with expanded elements
534
+ * - 'target': all expanded (sub) elements might only have new target/on, but
535
+ * no individual annotations on any (sub) member
536
+ * when set, traverse all parents where the value has been 'origin' before
537
+ * - 'annotate': at least one inferred (sub) member has an individual annotation,
538
+ * not counting propagated ones; set up to the definition (main artifact)
539
+ * (only set with anno on $inferred elem), annotate “beats” target
540
+ * Usage according to CSN flavor:
541
+ * - gensrc: do not render inferred elements (including expanded elements),
542
+ * collect annotate statements with value 'annotate'
543
+ * - client: do not render expanded sub elements if artifact/member is no type, has a type,
544
+ * has $expand = 'origin', and all its _origin also have $expand = 'origin'
545
+ * (might sometimes render the elements unnecessarily, which is not wrong)
546
+ * - universal: do not render expanded sub elements if $expand = 'origin'
547
+ */
542
548
  function setExpandStatus( elem, status ) {
543
549
  // set on element
544
550
  while (elem._main) {
@@ -618,12 +624,14 @@ function columnRefStartsWithSelf( col ) {
618
624
  return false;
619
625
  }
620
626
 
621
- // Remark: this function is based on an early check that no target element is
622
- // covered more than once by a foreign key: then…
623
- // we only need to check that all foreign key references are primary keys and
624
- // that the number of foreign and primary keys are the same.
627
+ /**
628
+ * Remark: this function is based on an early check that no target element is
629
+ * covered more than once by a foreign key: then…
630
+ * we only need to check that all foreign key references are primary keys and
631
+ * that the number of foreign and primary keys are the same.
632
+ */
625
633
  function isAssocToPrimaryKeys( assoc ) {
626
- let fkeys = 0;
634
+ let keyCount = 0;
627
635
  const { foreignKeys } = assoc;
628
636
  if (!foreignKeys)
629
637
  return undefined;
@@ -634,7 +642,7 @@ function isAssocToPrimaryKeys( assoc ) {
634
642
  return undefined;
635
643
  if (!elem.key?.val)
636
644
  return false;
637
- ++fkeys;
645
+ ++keyCount;
638
646
  }
639
647
 
640
648
  const elements = assoc.target._artifact?.elements;
@@ -642,9 +650,9 @@ function isAssocToPrimaryKeys( assoc ) {
642
650
  return undefined;
643
651
  for (const name in elements) {
644
652
  if (elements[name].key?.val)
645
- --fkeys;
653
+ --keyCount;
646
654
  }
647
- return fkeys === 0;
655
+ return keyCount === 0;
648
656
  }
649
657
 
650
658
  // only if _effectiveType has been computed: