@sap/cds-compiler 2.10.2 → 2.11.4

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 (82) hide show
  1. package/CHANGELOG.md +90 -5
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +3 -1
  4. package/bin/cdsc.js +49 -25
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_BETA.md +10 -0
  8. package/lib/api/.eslintrc.json +2 -0
  9. package/lib/api/main.js +8 -36
  10. package/lib/api/options.js +15 -6
  11. package/lib/api/validate.js +30 -3
  12. package/lib/backends.js +12 -13
  13. package/lib/base/dictionaries.js +2 -1
  14. package/lib/base/keywords.js +3 -2
  15. package/lib/base/message-registry.js +34 -10
  16. package/lib/base/messages.js +38 -18
  17. package/lib/base/model.js +5 -4
  18. package/lib/base/optionProcessorHelper.js +57 -23
  19. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  20. package/lib/checks/selectItems.js +4 -0
  21. package/lib/checks/unknownMagic.js +6 -3
  22. package/lib/compiler/assert-consistency.js +9 -2
  23. package/lib/compiler/base.js +65 -0
  24. package/lib/compiler/builtins.js +62 -16
  25. package/lib/compiler/checks.js +2 -1
  26. package/lib/compiler/definer.js +66 -108
  27. package/lib/compiler/index.js +29 -29
  28. package/lib/compiler/propagator.js +5 -2
  29. package/lib/compiler/resolver.js +225 -58
  30. package/lib/compiler/shared.js +53 -229
  31. package/lib/compiler/utils.js +184 -0
  32. package/lib/edm/annotations/genericTranslation.js +1 -1
  33. package/lib/edm/csn2edm.js +3 -2
  34. package/lib/edm/edmPreprocessor.js +34 -38
  35. package/lib/edm/edmUtils.js +3 -3
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +17 -1
  38. package/lib/gen/language.tokens +79 -73
  39. package/lib/gen/languageLexer.interp +19 -1
  40. package/lib/gen/languageLexer.js +779 -731
  41. package/lib/gen/languageLexer.tokens +71 -65
  42. package/lib/gen/languageParser.js +4668 -4072
  43. package/lib/json/from-csn.js +10 -10
  44. package/lib/json/to-csn.js +228 -47
  45. package/lib/language/antlrParser.js +11 -0
  46. package/lib/language/errorStrategy.js +26 -8
  47. package/lib/language/genericAntlrParser.js +73 -14
  48. package/lib/language/language.g4 +79 -3
  49. package/lib/main.d.ts +215 -18
  50. package/lib/main.js +3 -1
  51. package/lib/model/api.js +2 -2
  52. package/lib/model/csnRefs.js +117 -33
  53. package/lib/model/csnUtils.js +65 -133
  54. package/lib/model/enrichCsn.js +62 -37
  55. package/lib/model/revealInternalProperties.js +25 -8
  56. package/lib/model/sortViews.js +8 -1
  57. package/lib/modelCompare/compare.js +2 -1
  58. package/lib/optionProcessor.js +33 -18
  59. package/lib/render/.eslintrc.json +1 -2
  60. package/lib/render/DuplicateChecker.js +1 -1
  61. package/lib/render/toCdl.js +15 -8
  62. package/lib/render/toHdbcds.js +26 -49
  63. package/lib/render/toSql.js +61 -39
  64. package/lib/render/utils/common.js +1 -1
  65. package/lib/transform/db/applyTransformations.js +189 -0
  66. package/lib/transform/db/constraints.js +273 -119
  67. package/lib/transform/db/draft.js +3 -2
  68. package/lib/transform/db/expansion.js +6 -4
  69. package/lib/transform/db/flattening.js +19 -3
  70. package/lib/transform/db/transformExists.js +102 -9
  71. package/lib/transform/db/views.js +485 -0
  72. package/lib/transform/forHanaNew.js +93 -448
  73. package/lib/transform/forOdataNew.js +9 -2
  74. package/lib/transform/localized.js +2 -0
  75. package/lib/transform/odata/structuralPath.js +1 -5
  76. package/lib/transform/transformUtilsNew.js +22 -8
  77. package/lib/transform/translateAssocsToJoins.js +7 -15
  78. package/lib/utils/file.js +11 -5
  79. package/lib/utils/term.js +65 -42
  80. package/lib/utils/timetrace.js +48 -26
  81. package/package.json +1 -1
  82. package/lib/transform/db/helpers.js +0 -58
@@ -47,25 +47,29 @@ const {
47
47
  const { dictLocation } = require('../base/location');
48
48
  const { searchName, weakLocation } = require('../base/messages');
49
49
  const { combinedLocation } = require('../base/location');
50
- const { pushLink } = require('./utils');
50
+
51
+ const { kindProperties } = require('./base');
51
52
  const {
52
- getDefinerFunctions,
53
+ pushLink,
54
+ setLink,
55
+ annotationVal,
53
56
  augmentPath,
54
57
  splitIntoPath,
55
- } = require('./definer');
58
+ linkToOrigin,
59
+ setMemberParent,
60
+ withAssociation,
61
+ storeExtension,
62
+ dependsOn,
63
+ dependsOnSilent,
64
+ } = require('./utils');
56
65
 
57
66
  const detectCycles = require('./cycle-detector');
58
67
  const layers = require('./moduleLayers');
59
68
 
60
- const {
61
- kindProperties, fns, setLink, linkToOrigin, setMemberParent, withAssociation, storeExtension,
62
- dependsOn, dependsOnSilent,
63
- } = require('./shared');
64
-
65
69
  const annotationPriorities = {
66
70
  define: 1, extend: 2, annotate: 2, edmx: 3,
67
71
  };
68
-
72
+ const $inferred = Symbol.for('cds.$inferred');
69
73
 
70
74
  // Export function of this file. Resolve type references in augmented CSN
71
75
  // `model`. If the model has a property argument `messages`, do not throw
@@ -73,21 +77,21 @@ const annotationPriorities = {
73
77
  // that property (should be a vector).
74
78
  function resolve( model ) {
75
79
  const { options } = model;
76
- // Get shared "resolve" functionality and the message function:
80
+ // Get shared functionality and the message function:
81
+ const {
82
+ info, warning, error, message,
83
+ } = model.$messageFunctions;
77
84
  const {
78
85
  resolvePath,
79
86
  resolveTypeArguments,
80
87
  defineAnnotations,
81
88
  attachAndEmitValidNames,
82
- } = fns( model, environment );
83
- const {
84
- info, warning, error, message,
85
- } = model.$messageFunctions;
86
- const {
87
89
  initArtifact,
88
90
  lateExtensions,
89
91
  projectionAncestor,
90
- } = getDefinerFunctions(model);
92
+ } = model.$functions;
93
+ model.$volatileFunctions.environment = environment;
94
+
91
95
  /** @type {any} may also be a boolean */
92
96
  let newAutoExposed = [];
93
97
 
@@ -242,6 +246,7 @@ function resolve( model ) {
242
246
  const chain = [];
243
247
  while (art && !('_effectiveType' in art) &&
244
248
  (art.type || art._origin || art.value && art.value.path) &&
249
+ // TODO: really stop at art.enum?
245
250
  !art.target && !art.enum && !art.elements && !art.items) {
246
251
  chain.push( art );
247
252
  setProp( art, '_effectiveType', 0 ); // initial setting in case of cycles
@@ -273,13 +278,17 @@ function resolve( model ) {
273
278
  // collect the "latest" cardinality (calculate lazyly if necessary)
274
279
  let cardinality = art.cardinality ||
275
280
  art._effectiveType && (() => getCardinality( art._effectiveType ));
281
+ let prev = art;
276
282
  for (const a of chain) {
277
283
  if (a.cardinality)
278
284
  cardinality = a.cardinality;
279
285
  if (a.expand && expandFromColumns( a, art, cardinality ) ||
280
286
  art.target && redirectImplicitly( a, art ) ||
281
- art.elements && expandElements( a, art, eType ))
287
+ art.elements && expandElements( a, art, eType ) ||
288
+ art.items && expandItems( a, art, eType ))
282
289
  art = a;
290
+ else if (art.enum && expandEnum( a, prev ))
291
+ prev = a; // do not set art - effective type is base
283
292
  setProp( a, '_effectiveType', art );
284
293
  }
285
294
  }
@@ -351,13 +360,13 @@ function resolve( model ) {
351
360
  while (struct.kind === 'element')
352
361
  struct = struct._parent;
353
362
  if (struct.kind === 'select') {
354
- message( 'ref-invalid-typeof', [ ref.location, user ],
363
+ message( 'type-unexpected-typeof', [ ref.location, user ],
355
364
  { keyword: 'type of', '#': struct.kind } );
356
365
  // we actually refer to an element in _combined; TODO: return null if
357
366
  // not configurable; would produce illegal CSN with sub queries in FROM
358
367
  }
359
368
  else if (struct !== user._main) {
360
- message( 'ref-invalid-typeof', [ ref.location, user ],
369
+ message( 'type-unexpected-typeof', [ ref.location, user ],
361
370
  { keyword: 'type of', '#': struct.kind } );
362
371
  return setProp( ref, '_artifact', null );
363
372
  }
@@ -405,7 +414,8 @@ function resolve( model ) {
405
414
  for (const view of resolveChain.reverse()) {
406
415
  if (view._status !== '_query' ) { // not already resolved
407
416
  setProp( view, '_status', '_query' );
408
- traverseQueryPost( view.query, false, populateQuery );
417
+ // must be run in order “sub query in FROM first”:
418
+ traverseQueryPost( view.query, null, populateQuery );
409
419
  if (view.elements$) // specified elements
410
420
  mergeSpecifiedElements( view );
411
421
  if (!view.$entity) {
@@ -494,7 +504,8 @@ function resolve( model ) {
494
504
  });
495
505
  }
496
506
  forEachGeneric( { elements: alias.elements }, 'elements', ( elem, name ) => {
497
- dictAddArray( query._combined, name, elem, null ); // not dictAdd()
507
+ if (elem.$duplicates !== true)
508
+ dictAddArray( query._combined, name, elem, null ); // not dictAdd()
498
509
  });
499
510
  }
500
511
  }
@@ -535,13 +546,31 @@ function resolve( model ) {
535
546
  setMemberParent( key, name, elem ); // TODO: set _block here if not present?
536
547
  }
537
548
 
549
+ function expandItems( art, origin, eType ) {
550
+ if (!enableExpandElements || art.items)
551
+ return false;
552
+ if (isInParents( art, eType )) {
553
+ art.items = 0; // circular
554
+ return true;
555
+ }
556
+ const ref = art.type || art.value || art.name;
557
+ const location = ref && ref.location || art.location;
558
+ art.items = { $inferred: 'expand-element', location };
559
+ setProp( art.items, '_outer', art );
560
+ setProp( art.items, '_origin', origin.items );
561
+ if (!art.$expand)
562
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
563
+ return true;
564
+ }
565
+
538
566
  function expandElements( art, struct, eType ) {
539
567
  if (!enableExpandElements)
540
568
  return false;
541
569
  if (art.elements || art.kind === '$tableAlias' ||
542
570
  // no element expansions for "non-proper" types like
543
571
  // entities (as parameter types) etc:
544
- struct.kind !== 'type' && struct.kind !== 'element' && !struct._outer)
572
+ struct.kind !== 'type' && struct.kind !== 'element' && struct.kind !== 'param' &&
573
+ !struct._outer)
545
574
  return false;
546
575
  if (struct.elements === 0 || isInParents( art, eType )) {
547
576
  art.elements = 0; // circular
@@ -565,7 +594,28 @@ function resolve( model ) {
565
594
  // member property):
566
595
  if (!art.$expand)
567
596
  art.$expand = 'origin'; // if value stays, elements won't appear in CSN
568
- // TODO: have some art.elements[SYM.$inferred] = 'expand-elements';
597
+ // TODO: have some art.elements[SYM.$inferred] = 'expand-element';
598
+ return true;
599
+ }
600
+
601
+ function expandEnum( art, origin ) {
602
+ if (!enableExpandElements || art.enum)
603
+ return false;
604
+ const ref = art.type || art.value || art.name;
605
+ const location = weakLocation( ref && ref.location || art.location );
606
+ art.enum = Object.create(null);
607
+ for (const name in origin.enum) {
608
+ const orig = origin.enum[name];
609
+ linkToOrigin( orig, name, art, 'enum', location, true )
610
+ // or should we use orig.location? - TODO: try to find test to see message
611
+ .$inferred = 'expand-element';
612
+ }
613
+ // Set elements expansion status (the if condition is always true, as no
614
+ // elements expansion will take place on artifact with existing other
615
+ // member property):
616
+ if (!art.$expand)
617
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
618
+ art.enum[$inferred] = 'expand-element';
569
619
  return true;
570
620
  }
571
621
 
@@ -603,7 +653,7 @@ function resolve( model ) {
603
653
  // not counting propagated ones; set up to the definition (main artifact)
604
654
  // (only set with anno on $inferred elem)
605
655
  // Usage according to CSN flavor:
606
- // - gensrc: do not render enferred elements (including expanded elements),
656
+ // - gensrc: do not render inferred elements (including expanded elements),
607
657
  // collect annotate statements with value 'annotate'
608
658
  // - client: do not render expanded sub elements if artifact/member is no type, has a type,
609
659
  // has $expand = 'origin', and all its _origin also have $expand = 'origin'
@@ -671,35 +721,69 @@ function resolve( model ) {
671
721
  else if (exposed.length === 1) {
672
722
  target = exposed[0];
673
723
  }
674
- else {
675
- message( (elem !== assoc ? 'redirected-implicitly-ambiguous' : 'type-ambiguous-target'),
676
- [ (elem.value || elem.name).location, elem ],
724
+ else if (elem === assoc) {
725
+ // `assoc: Association to ModelEntity`: user-provided target is to be auto-redirected
726
+ warning( 'type-ambiguous-target',
727
+ [ elem.target.location, elem ],
677
728
  {
678
729
  target,
679
- service,
680
- art: definitionScope( target ),
730
+ // art: definitionScope( target ), - TODO extra debug info in message
681
731
  sorted_arts: exposed,
682
- '#': ( elemScope !== true ? 'std' : 'scoped' ),
683
732
  }, {
684
733
  // eslint-disable-next-line max-len
685
- std: 'Target $(TARGET) is exposed in service $(SERVICE) by multiple projections $(SORTED_ARTS) - no implicit redirection',
734
+ std: 'Replace target $(TARGET) by one of $(SORTED_ARTS); can\'t auto-redirect this association if multiple projections exist in this service',
686
735
  // eslint-disable-next-line max-len
687
- scoped: 'Target $(TARGET) is defined in scope $(ART) which exposed in service $(SERVICE) by multiple projections - no implicit redirection',
736
+ two: 'Replace target $(TARGET) by $(SORTED_ARTS) or $(SECOND); can\'t auto-redirect this association if multiple projections exist in this service',
688
737
  });
738
+ // continuation semantics: no auto-redirection
739
+ }
740
+ else {
741
+ // referred (and probably inferred) assoc (without a user-provided target at that place)
742
+ // HINT: consider bin/cdsv2m.js when changing the following message text
743
+ // No grouped and sub messages yet (TODO v3): mention at all target places with all assocs
744
+ const withAnno = annotationVal( exposed[0]['@cds.redirection.target'] );
745
+ for (const proj of exposed) {
746
+ // TODO: def-ambiguous-target (just v3, as the current is infamous and used in options),
747
+ message( 'redirected-implicitly-ambiguous',
748
+ [ weakLocation( proj.location ), proj ],
749
+ {
750
+ '#': withAnno && 'justOne',
751
+ target,
752
+ art: elem,
753
+ // art: definitionScope( target ), - TODO extra debug info in message
754
+ anno: 'cds.redirection.target',
755
+ sorted_arts: exposed,
756
+ }, {
757
+ // eslint-disable-next-line max-len
758
+ std: 'Add $(ANNO) to one of $(SORTED_ARTS) to select the entity as redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
759
+ // eslint-disable-next-line max-len
760
+ two: 'Add $(ANNO) to either $(SORTED_ARTS) or $(SECOND) to select the entity as redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
761
+ // eslint-disable-next-line max-len
762
+ justOne: 'Remove $(ANNO) from all but one of $(SORTED_ARTS) to have a unique redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
763
+ } );
764
+ }
689
765
  // continuation semantics: no implicit redirections
690
766
  }
691
767
  }
692
768
  if (elem.target) { // redirection for Association to / Composition of
693
769
  if (elem.target._artifact === target) // no change (due to no implicit redirection)
694
770
  return true;
771
+ const type = resolvePath( elem.type, 'type', elem ); // cds.Association or cds.Composition
695
772
  const origin = {
696
- kind: elem.kind,
773
+ kind: elem.kind, // necessary for rewrite, '$user-provided' would be best
697
774
  name: elem.name,
775
+ type: {
776
+ path: [ { id: type.name.absolute, location: elem.type.location } ],
777
+ scope: 'global',
778
+ location: elem.type.location,
779
+ },
698
780
  target: elem.target,
699
781
  $inferred: 'REDIRECTED',
700
782
  location: elem.target.location,
701
783
  };
702
784
  setLink( elem, origin, '_origin' );
785
+ setLink( elem.type, type, '_artifact' );
786
+ setLink( origin, elem, '_outer' );
703
787
  setLink( origin, elem._parent, '_parent' );
704
788
  if (elem._main) // remark: the param `elem` can also be a type
705
789
  setLink( origin, elem._main, '_main' );
@@ -707,11 +791,11 @@ function resolve( model ) {
707
791
  setLink( origin, elem._block, '_block' );
708
792
  if (elem.foreignKeys) {
709
793
  origin.foreignKeys = elem.foreignKeys;
710
- delete elem.foreignKeys;
794
+ delete elem.foreignKeys; // will be rewritten
711
795
  }
712
796
  if (elem.on) {
713
797
  origin.on = elem.on;
714
- delete elem.on;
798
+ delete elem.on; // will be rewritten
715
799
  }
716
800
  }
717
801
  elem.target = {
@@ -770,10 +854,7 @@ function resolve( model ) {
770
854
  target._descendants[service.name.absolute] ||
771
855
  [],
772
856
  elemScope, target );
773
- const preferred = descendants.filter( ( d ) => {
774
- const anno = d['@cds.redirection.target'];
775
- return anno && (anno.val === undefined || anno.val );
776
- } );
857
+ const preferred = descendants.filter( d => annotationVal( d['@cds.redirection.target'] ) );
777
858
  const exposed = preferred.length ? preferred : descendants;
778
859
  if (exposed.length < 2)
779
860
  return exposed || [];
@@ -812,7 +893,7 @@ function resolve( model ) {
812
893
  // Need to filter out auto-exposed, otherwise the behavior is
813
894
  // processing-order dependent (not storing the autoexposed in
814
895
  // _descendents would only be an alternative w/o recompilation)
815
- return descendants.filter( d => !d['@cds.autoexposed'] );
896
+ return descendants.filter( d => !annotationVal( d['@cds.autoexposed'] ) );
816
897
  }
817
898
  // try scope as target first, even if it has @cds.redirection.target: false
818
899
  if (isDirectProjection( elemScope, target ))
@@ -943,6 +1024,7 @@ function resolve( model ) {
943
1024
  $inferred: 'autoexposed',
944
1025
  '@cds.autoexposed': {
945
1026
  name: { path: [ { id: 'cds.autoexposed', location } ], location },
1027
+ $inferred: 'autoexposed',
946
1028
  },
947
1029
  };
948
1030
  // TODO: do we need to tag the generated entity with elemScope = 'auto'?
@@ -1440,8 +1522,9 @@ function resolve( model ) {
1440
1522
  }
1441
1523
  // Resolve projections/views
1442
1524
  // 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
1444
- traverseQueryPost( art.query, false, resolveQuery );
1525
+
1526
+ if (art.$queries)
1527
+ art.$queries.forEach( resolveQuery );
1445
1528
 
1446
1529
  if (obj.type || obj._origin || obj.value && obj.value.path || obj.elements) // typed artifacts
1447
1530
  effectiveType(obj); // set _effectiveType if appropriate, (future?): copy elems if extended
@@ -1586,6 +1669,11 @@ function resolve( model ) {
1586
1669
  }
1587
1670
  }
1588
1671
  if (art && art._annotate) {
1672
+ if (art.kind === 'action' || art.kind === 'function') {
1673
+ expandParameters( art );
1674
+ if (art.returns)
1675
+ effectiveType( art.returns );
1676
+ }
1589
1677
  const aor = art.returns || art;
1590
1678
  const obj = aor.items || aor.targetAspect || aor;
1591
1679
  // Currently(?), effectiveType() does not calculate the effective type of
@@ -1620,6 +1708,44 @@ function resolve( model ) {
1620
1708
  annotateMembers( env && env[n], dict[n], prop, n, parent, kind );
1621
1709
  }
1622
1710
  }
1711
+ function expandParameters( action ) {
1712
+ // see also expandElements()
1713
+ if (!enableExpandElements || !effectiveType( action ))
1714
+ return;
1715
+ const chain = [];
1716
+ // Should we be able to consider params and returns separately?
1717
+ // Probably not, let to-csn omit unchanged params/returns.
1718
+ while (action._origin && !action.params) {
1719
+ chain.push( action );
1720
+ action = action._origin;
1721
+ }
1722
+ chain.reverse();
1723
+ for (const art of chain) {
1724
+ const origin = art._origin;
1725
+ if (!art.params && origin.params) {
1726
+ for (const name in origin.params) {
1727
+ // TODO: we could check _annotate here to decide whether we really
1728
+ // not to create proxies
1729
+ const orig = origin.params[name];
1730
+ linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
1731
+ .$inferred = 'expand-param';
1732
+ }
1733
+ }
1734
+ if (!art.returns && origin.returns) {
1735
+ // TODO: make linkToOrigin() work for returns, kind/name?
1736
+ const location = weakLocation( origin.returns.location );
1737
+ art.returns = {
1738
+ name: Object.assign( {}, art.name, { id: '', param: '', location } ),
1739
+ kind: 'param',
1740
+ location,
1741
+ $inferred: 'expand-param',
1742
+ };
1743
+ setProp( art.returns, '_parent', art );
1744
+ setProp( art.returns, '_main', art._main || art );
1745
+ setProp( art.returns, '_origin', origin.returns );
1746
+ }
1747
+ }
1748
+ }
1623
1749
 
1624
1750
  function extensionFor( art ) {
1625
1751
  if (art.kind === 'annotate')
@@ -1826,7 +1952,7 @@ function resolve( model ) {
1826
1952
  function resolveQuery( query ) {
1827
1953
  if (!query._main) // parse error
1828
1954
  return;
1829
- populateQuery( query );
1955
+ traverseQueryPost( query, null, populateQuery );
1830
1956
  forEachGeneric( query, '$tableAliases', ( alias ) => {
1831
1957
  // console.log( info( null, [alias.location,alias], 'SQA:' ).toString() );
1832
1958
  if (alias.kind === 'mixin')
@@ -1967,6 +2093,7 @@ function resolve( model ) {
1967
2093
  dependsOnSilent(art, key);
1968
2094
  }
1969
2095
  });
2096
+ obj.foreignKeys[$inferred] = 'keys';
1970
2097
  }
1971
2098
 
1972
2099
  function addForeignKeyNavigations( art ) {
@@ -2135,8 +2262,6 @@ function resolve( model ) {
2135
2262
  // Only top-level queries and sub queries in FROM
2136
2263
 
2137
2264
  function rewriteSimple( art ) {
2138
- // If we have a proper seperation of view elements and elements of the
2139
- // primary query, we can delete this function.
2140
2265
  // return;
2141
2266
  if (!art.includes && !art.query) {
2142
2267
  // console.log(message( null, art.location, art, {target:art._target},
@@ -2149,7 +2274,7 @@ function resolve( model ) {
2149
2274
  }
2150
2275
 
2151
2276
  function rewriteView( view ) {
2152
- traverseQueryPost( view.query, false, ( query ) => {
2277
+ traverseQueryExtra( view, ( query ) => {
2153
2278
  forEachGeneric( query, 'elements', rewriteAssociation );
2154
2279
  } );
2155
2280
  if (view.includes) // entities with structure includes:
@@ -2157,6 +2282,7 @@ function resolve( model ) {
2157
2282
  }
2158
2283
 
2159
2284
  // Check explicit ON / keys with REDIRECTED TO
2285
+ // TODO: run on all queries, but this is potentially incompatible
2160
2286
  function rewriteViewCheck( view ) {
2161
2287
  traverseQueryPost( view.query, false, ( query ) => {
2162
2288
  forEachGeneric( query, 'elements', rewriteAssociationCheck );
@@ -2382,7 +2508,7 @@ function resolve( model ) {
2382
2508
  resolveExpr( cond, rewriteExpr, elem, nav.tableAlias );
2383
2509
  }
2384
2510
  else {
2385
- // TODO: support that
2511
+ // TODO: support that, now that the ON condition is rewritten in the right order
2386
2512
  error( null, [ elem.value.location, elem ],
2387
2513
  'Selecting unmanaged associations from a sub query is not supported' );
2388
2514
  }
@@ -2551,6 +2677,8 @@ function resolve( model ) {
2551
2677
  }
2552
2678
 
2553
2679
  function resolveExpr( expr, expected, user, extDict, expandOrInline) {
2680
+ // TODO: when we have rewritten the resolvePath functions,
2681
+ // define a traverseExpr() in ./utils.js
2554
2682
  // TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`
2555
2683
  if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
2556
2684
  return;
@@ -2587,7 +2715,7 @@ function resolve( model ) {
2587
2715
  else if (expr.query) {
2588
2716
  const { query } = expr;
2589
2717
  if (query.kind || query._leadingQuery) { // UNION has _leadingQuery
2590
- traverseQueryPost( query, false, resolveQuery );
2718
+ // traverseQueryPost( query, false, resolveQuery );
2591
2719
  }
2592
2720
  else {
2593
2721
  error( 'expr-no-subquery', [ expr.location, user ], {},
@@ -2598,9 +2726,10 @@ function resolve( model ) {
2598
2726
  const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
2599
2727
  args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
2600
2728
  }
2601
- if (expr.suffix && !isBetaEnabled( options, 'windowFunctions' )) {
2729
+ if (expr.suffix && isDeprecatedEnabled( options )) {
2602
2730
  const { location } = expr.suffix[0] || expr;
2603
- error( null, [ location, user ], 'Window functions are not supported' );
2731
+ error( null, [ location, user ], { prop: 'deprecated' },
2732
+ 'Window functions are not supported if $(PROP) options are set' );
2604
2733
  }
2605
2734
  if (expr.suffix)
2606
2735
  expr.suffix.forEach( s => s && resolveExpr( s, expected, user, extDict ) );
@@ -2785,11 +2914,11 @@ function navProjection( navigation, preferred ) {
2785
2914
  : navigation._projections[0] || null;
2786
2915
  }
2787
2916
 
2788
- // Query tree post-order traversal - called for everything which makes a query
2917
+ // Query tree post-order traversal - called for everything which contributes to the query
2918
+ // i.e. is necessary to calculate the elements of the query
2789
2919
  // except "real ones": operands of UNION etc, JOIN with ON, and sub queries in FROM
2920
+ // NOTE: does not run on non-referred sub queries! Consider using ‹main›.$queries instead!
2790
2921
  function traverseQueryPost( query, simpleOnly, callback ) {
2791
- while (Array.isArray(query)) // query in parentheses, TODO: remove
2792
- query = query[0];
2793
2922
  if (!query) // parser error
2794
2923
  return;
2795
2924
  if (!query.op) { // in FROM (not JOIN)
@@ -2809,13 +2938,51 @@ function traverseQueryPost( query, simpleOnly, callback ) {
2809
2938
  // console.log('FE:')
2810
2939
  }
2811
2940
  else if (query.args) { // JOIN, UNION, INTERSECT
2812
- for (const q of query.args)
2813
- traverseQueryPost( q, simpleOnly, callback );
2814
- // The ON condition has to be traversed extra, because it must be evaluated
2815
- // after the complete FROM has been traversed. It is also not necessary to
2816
- // evaluate it in populateQuery().
2941
+ if (!query.join && simpleOnly == null) {
2942
+ // enough for elements: traverse only first args for UNION/INTERSECT
2943
+ // TODO: we might use this also when we do not rewrite associations
2944
+ // in non-referred sub queries
2945
+ traverseQueryPost( query.args[0], simpleOnly, callback );
2946
+ }
2947
+ else {
2948
+ for (const q of query.args)
2949
+ traverseQueryPost( q, simpleOnly, callback );
2950
+ // The ON condition has to be traversed extra, because it must be evaluated
2951
+ // after the complete FROM has been traversed. It is also not necessary to
2952
+ // evaluate it in populateQuery().
2953
+ }
2817
2954
  }
2818
2955
  // else: with parse error (`select from <EOF>`, `select distinct from;`)
2819
2956
  }
2820
2957
 
2958
+ // Call callback on all queries in dependency order, i.e. starting with query Q
2959
+ // 1. sub queries in FROM sources of Q
2960
+ // 2. Q itself, except if non-referred query, but with right UNION parts
2961
+ // 3. sub queries in ON in FROM of Q
2962
+ // 4. sub queries in columns, WHERE, HAVING
2963
+ function traverseQueryExtra( main, callback ) {
2964
+ if (!main.$queries)
2965
+ return;
2966
+ // with a top-level UNION, $queries[0] is just the left
2967
+ traverseQueryPost( main.query, false, (q) => { // also with right of UNION (to be compatible)
2968
+ setProp( q, '_status', 'extra' );
2969
+ callback( q );
2970
+ } );
2971
+ for (const query of main.$queries.slice(1)) {
2972
+ if (query._status === 'extra' || query._parent.kind === '$tableAlias')
2973
+ continue; // if parent is alias, query is FROM source -> run by traverseQueryPost
2974
+ // we are now in the top-level (parent is entity) or a non-referred query (parent is query)
2975
+ setProp( query, '_status', 'extra' ); // do not call callback() in non-referred query
2976
+ // console.log( 'A:', query.name,query._status)
2977
+ traverseQueryPost( query, null, (q) => {
2978
+ if (q._status !== 'extra') {
2979
+ // console.log( 'T:', q.name)
2980
+ setProp( q, '_status', 'extra' );
2981
+ callback( q );
2982
+ }
2983
+ // else console.log( 'E:', q.name)
2984
+ } );
2985
+ }
2986
+ }
2987
+
2821
2988
  module.exports = resolve;