@sap/cds-compiler 2.7.0 → 2.11.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 (87) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/bin/cdsc.js +42 -25
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +10 -0
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +17 -33
  7. package/lib/api/options.js +25 -13
  8. package/lib/api/validate.js +33 -9
  9. package/lib/backends.js +9 -8
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +26 -2
  13. package/lib/base/messages.js +25 -9
  14. package/lib/base/model.js +5 -3
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/onConditions.js +5 -0
  17. package/lib/checks/selectItems.js +4 -0
  18. package/lib/checks/types.js +26 -2
  19. package/lib/checks/unknownMagic.js +41 -0
  20. package/lib/checks/validator.js +7 -2
  21. package/lib/compiler/assert-consistency.js +18 -5
  22. package/lib/compiler/base.js +65 -0
  23. package/lib/compiler/builtins.js +30 -1
  24. package/lib/compiler/checks.js +5 -2
  25. package/lib/compiler/definer.js +145 -120
  26. package/lib/compiler/index.js +16 -4
  27. package/lib/compiler/propagator.js +5 -2
  28. package/lib/compiler/resolver.js +207 -47
  29. package/lib/compiler/shared.js +47 -200
  30. package/lib/compiler/utils.js +173 -0
  31. package/lib/edm/annotations/genericTranslation.js +183 -187
  32. package/lib/edm/csn2edm.js +94 -98
  33. package/lib/edm/edm.js +16 -20
  34. package/lib/edm/edmPreprocessor.js +302 -115
  35. package/lib/edm/edmUtils.js +31 -12
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +28 -1
  38. package/lib/gen/language.tokens +79 -69
  39. package/lib/gen/languageLexer.interp +28 -1
  40. package/lib/gen/languageLexer.js +879 -805
  41. package/lib/gen/languageLexer.tokens +71 -62
  42. package/lib/gen/languageParser.js +5308 -4308
  43. package/lib/json/from-csn.js +59 -30
  44. package/lib/json/to-csn.js +354 -105
  45. package/lib/language/antlrParser.js +11 -0
  46. package/lib/language/errorStrategy.js +1 -0
  47. package/lib/language/genericAntlrParser.js +81 -14
  48. package/lib/language/language.g4 +163 -31
  49. package/lib/main.d.ts +136 -17
  50. package/lib/main.js +7 -1
  51. package/lib/model/api.js +78 -0
  52. package/lib/model/csnRefs.js +115 -32
  53. package/lib/model/csnUtils.js +71 -33
  54. package/lib/model/enrichCsn.js +36 -9
  55. package/lib/model/revealInternalProperties.js +20 -4
  56. package/lib/modelCompare/compare.js +2 -1
  57. package/lib/optionProcessor.js +33 -16
  58. package/lib/render/.eslintrc.json +3 -1
  59. package/lib/render/DuplicateChecker.js +1 -1
  60. package/lib/render/toCdl.js +60 -17
  61. package/lib/render/toHdbcds.js +122 -74
  62. package/lib/render/toSql.js +57 -32
  63. package/lib/render/utils/common.js +6 -10
  64. package/lib/sql-identifier.js +6 -1
  65. package/lib/transform/db/constraints.js +273 -119
  66. package/lib/transform/db/draft.js +9 -6
  67. package/lib/transform/db/expansion.js +19 -7
  68. package/lib/transform/db/flattening.js +31 -7
  69. package/lib/transform/db/transformExists.js +344 -66
  70. package/lib/transform/db/views.js +438 -0
  71. package/lib/transform/forHanaNew.js +65 -436
  72. package/lib/transform/forOdataNew.js +21 -10
  73. package/lib/transform/localized.js +2 -0
  74. package/lib/transform/odata/attachPath.js +19 -4
  75. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  76. package/lib/transform/odata/referenceFlattener.js +44 -38
  77. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  78. package/lib/transform/odata/structuralPath.js +72 -0
  79. package/lib/transform/odata/structureFlattener.js +13 -10
  80. package/lib/transform/odata/typesExposure.js +22 -12
  81. package/lib/transform/transformUtilsNew.js +55 -9
  82. package/lib/transform/translateAssocsToJoins.js +11 -17
  83. package/lib/transform/universalCsnEnricher.js +67 -0
  84. package/lib/utils/file.js +5 -3
  85. package/lib/utils/term.js +65 -42
  86. package/lib/utils/timetrace.js +48 -26
  87. package/package.json +1 -1
@@ -47,25 +47,28 @@ 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,
53
55
  augmentPath,
54
56
  splitIntoPath,
55
- } = require('./definer');
57
+ linkToOrigin,
58
+ setMemberParent,
59
+ withAssociation,
60
+ storeExtension,
61
+ dependsOn,
62
+ dependsOnSilent,
63
+ } = require('./utils');
56
64
 
57
65
  const detectCycles = require('./cycle-detector');
58
66
  const layers = require('./moduleLayers');
59
67
 
60
- const {
61
- kindProperties, fns, setLink, linkToOrigin, setMemberParent, withAssociation, storeExtension,
62
- dependsOn, dependsOnSilent,
63
- } = require('./shared');
64
-
65
68
  const annotationPriorities = {
66
69
  define: 1, extend: 2, annotate: 2, edmx: 3,
67
70
  };
68
-
71
+ const $inferred = Symbol.for('cds.$inferred');
69
72
 
70
73
  // Export function of this file. Resolve type references in augmented CSN
71
74
  // `model`. If the model has a property argument `messages`, do not throw
@@ -73,21 +76,21 @@ const annotationPriorities = {
73
76
  // that property (should be a vector).
74
77
  function resolve( model ) {
75
78
  const { options } = model;
76
- // Get shared "resolve" functionality and the message function:
79
+ // Get shared functionality and the message function:
80
+ const {
81
+ info, warning, error, message,
82
+ } = model.$messageFunctions;
77
83
  const {
78
84
  resolvePath,
79
85
  resolveTypeArguments,
80
86
  defineAnnotations,
81
87
  attachAndEmitValidNames,
82
- } = fns( model, environment );
83
- const {
84
- info, warning, error, message,
85
- } = model.$messageFunctions;
86
- const {
87
88
  initArtifact,
88
89
  lateExtensions,
89
90
  projectionAncestor,
90
- } = getDefinerFunctions(model);
91
+ } = model.$functions;
92
+ model.$volatileFunctions.environment = environment;
93
+
91
94
  /** @type {any} may also be a boolean */
92
95
  let newAutoExposed = [];
93
96
 
@@ -242,6 +245,7 @@ function resolve( model ) {
242
245
  const chain = [];
243
246
  while (art && !('_effectiveType' in art) &&
244
247
  (art.type || art._origin || art.value && art.value.path) &&
248
+ // TODO: really stop at art.enum?
245
249
  !art.target && !art.enum && !art.elements && !art.items) {
246
250
  chain.push( art );
247
251
  setProp( art, '_effectiveType', 0 ); // initial setting in case of cycles
@@ -273,13 +277,17 @@ function resolve( model ) {
273
277
  // collect the "latest" cardinality (calculate lazyly if necessary)
274
278
  let cardinality = art.cardinality ||
275
279
  art._effectiveType && (() => getCardinality( art._effectiveType ));
280
+ let prev = art;
276
281
  for (const a of chain) {
277
282
  if (a.cardinality)
278
283
  cardinality = a.cardinality;
279
284
  if (a.expand && expandFromColumns( a, art, cardinality ) ||
280
285
  art.target && redirectImplicitly( a, art ) ||
281
- art.elements && expandElements( a, art, eType ))
286
+ art.elements && expandElements( a, art, eType ) ||
287
+ art.items && expandItems( a, art, eType ))
282
288
  art = a;
289
+ else if (art.enum && expandEnum( a, prev ))
290
+ prev = a; // do not set art - effective type is base
283
291
  setProp( a, '_effectiveType', art );
284
292
  }
285
293
  }
@@ -405,7 +413,7 @@ function resolve( model ) {
405
413
  for (const view of resolveChain.reverse()) {
406
414
  if (view._status !== '_query' ) { // not already resolved
407
415
  setProp( view, '_status', '_query' );
408
- traverseQueryPost( view.query, false, populateQuery );
416
+ traverseQueryPost( view.query, null, populateQuery );
409
417
  if (view.elements$) // specified elements
410
418
  mergeSpecifiedElements( view );
411
419
  if (!view.$entity) {
@@ -494,7 +502,8 @@ function resolve( model ) {
494
502
  });
495
503
  }
496
504
  forEachGeneric( { elements: alias.elements }, 'elements', ( elem, name ) => {
497
- dictAddArray( query._combined, name, elem, null ); // not dictAdd()
505
+ if (elem.$duplicates !== true)
506
+ dictAddArray( query._combined, name, elem, null ); // not dictAdd()
498
507
  });
499
508
  }
500
509
  }
@@ -535,13 +544,31 @@ function resolve( model ) {
535
544
  setMemberParent( key, name, elem ); // TODO: set _block here if not present?
536
545
  }
537
546
 
547
+ function expandItems( art, origin, eType ) {
548
+ if (!enableExpandElements || art.items)
549
+ return false;
550
+ if (isInParents( art, eType )) {
551
+ art.items = 0; // circular
552
+ return true;
553
+ }
554
+ const ref = art.type || art.value || art.name;
555
+ const location = ref && ref.location || art.location;
556
+ art.items = { $inferred: 'expand-element', location };
557
+ setProp( art.items, '_outer', art );
558
+ setProp( art.items, '_origin', origin.items );
559
+ if (!art.$expand)
560
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
561
+ return true;
562
+ }
563
+
538
564
  function expandElements( art, struct, eType ) {
539
565
  if (!enableExpandElements)
540
566
  return false;
541
567
  if (art.elements || art.kind === '$tableAlias' ||
542
568
  // no element expansions for "non-proper" types like
543
569
  // entities (as parameter types) etc:
544
- struct.kind !== 'type' && struct.kind !== 'element' && !struct._outer)
570
+ struct.kind !== 'type' && struct.kind !== 'element' && struct.kind !== 'param' &&
571
+ !struct._outer)
545
572
  return false;
546
573
  if (struct.elements === 0 || isInParents( art, eType )) {
547
574
  art.elements = 0; // circular
@@ -560,8 +587,33 @@ function resolve( model ) {
560
587
  // or should we use orig.location? - TODO: try to find test to see message
561
588
  .$inferred = 'expand-element';
562
589
  }
563
- art.$expand = 'origin'; // if value stays, elements won't appear in CSN
564
- // TODO: should we also set 'virtual' here?
590
+ // Set elements expansion status (the if condition is always true, as no
591
+ // elements expansion will take place on artifact with existing other
592
+ // member property):
593
+ if (!art.$expand)
594
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
595
+ // TODO: have some art.elements[SYM.$inferred] = 'expand-element';
596
+ return true;
597
+ }
598
+
599
+ function expandEnum( art, origin ) {
600
+ if (!enableExpandElements || art.enum)
601
+ return false;
602
+ const ref = art.type || art.value || art.name;
603
+ const location = weakLocation( ref && ref.location || art.location );
604
+ art.enum = Object.create(null);
605
+ for (const name in origin.enum) {
606
+ const orig = origin.enum[name];
607
+ linkToOrigin( orig, name, art, 'enum', location, true )
608
+ // or should we use orig.location? - TODO: try to find test to see message
609
+ .$inferred = 'expand-element';
610
+ }
611
+ // Set elements expansion status (the if condition is always true, as no
612
+ // elements expansion will take place on artifact with existing other
613
+ // member property):
614
+ if (!art.$expand)
615
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
616
+ art.enum[$inferred] = 'expand-element';
565
617
  return true;
566
618
  }
567
619
 
@@ -587,14 +639,45 @@ function resolve( model ) {
587
639
  return false;
588
640
  }
589
641
 
590
- function setExpandStatus( elem ) {
642
+ // About Helper property $expand for faster the XSN-to-CSN transformation
643
+ // - null/undefined: artifact, member, items does not contain expanded members
644
+ // - 'origin': all expanded (sub) elements have no new target/on and no new annotations
645
+ // that value is only on elements, types, and params -> no other members
646
+ // when set, only on elem/art with expanded elements
647
+ // - 'target': all expanded (sub) elements might only have new target/on, but
648
+ // no indivual annotations on any (sub) member
649
+ // when set, traverse all parents where the value has been 'origin' before
650
+ // - 'annotate': at least one inferred (sub) member has an individual annotation,
651
+ // not counting propagated ones; set up to the definition (main artifact)
652
+ // (only set with anno on $inferred elem)
653
+ // Usage according to CSN flavor:
654
+ // - gensrc: do not render inferred elements (including expanded elements),
655
+ // collect annotate statements with value 'annotate'
656
+ // - client: do not render expanded sub elements if artifact/member is no type, has a type,
657
+ // has $expand = 'origin', and all its _origin also have $expand = 'origin'
658
+ // (might sometimes render the elements unnecessarily, which is not wrong)
659
+ // - universal: do not render expanded sub elements if $expand = 'origin'
660
+ function setExpandStatus( elem, status ) {
661
+ // set on element
591
662
  while (elem._main) {
592
663
  elem = elem._parent;
593
- if (!elem.$expand)
664
+ if (elem.$expand !== 'origin')
594
665
  return;
595
- elem.$expand = 'target'; // meaning: expanded, containing assocs
666
+ elem.$expand = status; // meaning: expanded, containing assocs
596
667
  for (let line = elem.items; line; line = line.items)
597
- line.$expand = 'target'; // to-csn just uses the innermost $expand
668
+ line.$expand = status; // to-csn just uses the innermost $expand
669
+ }
670
+ }
671
+ function setExpandStatusAnnotate( elem, status ) {
672
+ for (;;) {
673
+ if (elem.$expand === status)
674
+ return; // already set
675
+ elem.$expand = status; // meaning: expanded, containing annos
676
+ for (let line = elem.items; line; line = line.items)
677
+ line.$expand = status; // to-csn just uses the innermost $expand
678
+ if (!elem._main)
679
+ return;
680
+ elem = elem._parent;
598
681
  }
599
682
  }
600
683
 
@@ -606,7 +689,7 @@ function resolve( model ) {
606
689
  // PRE: elem has no target, assoc has target prop
607
690
  if (elem.kind === '$tableAlias')
608
691
  return false;
609
- setExpandStatus( elem );
692
+ setExpandStatus( elem, 'target' );
610
693
  let target = resolvePath( assoc.target, 'target', assoc );
611
694
  // console.log( info( null, [ elem.location, elem ], {target,art:assoc,name:''+assoc.target},
612
695
  // 'RED').toString())
@@ -719,6 +802,7 @@ function resolve( model ) {
719
802
  const nullScope = {
720
803
  kind: 'namespace', name: { absolute: autoScopeName, location }, location,
721
804
  };
805
+ model.definitions[autoScopeName] = nullScope;
722
806
  initArtifact( nullScope );
723
807
  return nullScope;
724
808
  }
@@ -1404,8 +1488,9 @@ function resolve( model ) {
1404
1488
  }
1405
1489
  // Resolve projections/views
1406
1490
  // if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );
1407
- // TODO: here, any order should be ok, i.e. just loop over $queries
1408
- traverseQueryPost( art.query, false, resolveQuery );
1491
+
1492
+ if (art.$queries)
1493
+ art.$queries.forEach( resolveQuery );
1409
1494
 
1410
1495
  if (obj.type || obj._origin || obj.value && obj.value.path || obj.elements) // typed artifacts
1411
1496
  effectiveType(obj); // set _effectiveType if appropriate, (future?): copy elems if extended
@@ -1550,14 +1635,28 @@ function resolve( model ) {
1550
1635
  }
1551
1636
  }
1552
1637
  if (art && art._annotate) {
1553
- if (art.$expand && art._annotate.elements)
1554
- art.$expand = 'annotate'; // do not omit expanded elements when they are annotated
1555
- // TODO: returns
1556
- let obj = art.returns || art; // why the extra `returns` for actions?
1557
- obj = obj.items || obj;
1638
+ if (art.kind === 'action' || art.kind === 'function') {
1639
+ expandParameters( art );
1640
+ if (art.returns)
1641
+ effectiveType( art.returns );
1642
+ }
1643
+ const aor = art.returns || art;
1644
+ const obj = aor.items || aor.targetAspect || aor;
1645
+ // Currently(?), effectiveType() does not calculate the effective type of
1646
+ // its line item:
1647
+ effectiveType( obj );
1648
+ if (art._annotate.elements)
1649
+ setExpandStatusAnnotate( aor, 'annotate' );
1558
1650
  annotate( obj, 'element', 'elements', 'enum', art );
1559
1651
  annotate( art, 'action', 'actions' );
1560
1652
  annotate( art, 'param', 'params' );
1653
+ // const { returns } = art._annotate;
1654
+ // if (returns) {
1655
+ // const dict = returns.elements;
1656
+ // const env = obj.returns && obj.returns.elements || null;
1657
+ // for (const n in dict)
1658
+ // annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
1659
+ // }
1561
1660
  }
1562
1661
  return;
1563
1662
 
@@ -1575,6 +1674,44 @@ function resolve( model ) {
1575
1674
  annotateMembers( env && env[n], dict[n], prop, n, parent, kind );
1576
1675
  }
1577
1676
  }
1677
+ function expandParameters( action ) {
1678
+ // see also expandElements()
1679
+ if (!enableExpandElements || !effectiveType( action ))
1680
+ return;
1681
+ const chain = [];
1682
+ // Should we be able to consider params and returns separately?
1683
+ // Probably not, let to-csn omit unchanged params/returns.
1684
+ while (action._origin && !action.params) {
1685
+ chain.push( action );
1686
+ action = action._origin;
1687
+ }
1688
+ chain.reverse();
1689
+ for (const art of chain) {
1690
+ const origin = art._origin;
1691
+ if (!art.params && origin.params) {
1692
+ for (const name in origin.params) {
1693
+ // TODO: we could check _annotate here to decide whether we really
1694
+ // not to create proxies
1695
+ const orig = origin.params[name];
1696
+ linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
1697
+ .$inferred = 'expand-param';
1698
+ }
1699
+ }
1700
+ if (!art.returns && origin.returns) {
1701
+ // TODO: make linkToOrigin() work for returns, kind/name?
1702
+ const location = weakLocation( origin.returns.location );
1703
+ art.returns = {
1704
+ name: Object.assign( {}, art.name, { id: '', param: '', location } ),
1705
+ kind: 'param',
1706
+ location,
1707
+ $inferred: 'expand-param',
1708
+ };
1709
+ setProp( art.returns, '_parent', art );
1710
+ setProp( art.returns, '_main', art._main || art );
1711
+ setProp( art.returns, '_origin', origin.returns );
1712
+ }
1713
+ }
1714
+ }
1578
1715
 
1579
1716
  function extensionFor( art ) {
1580
1717
  if (art.kind === 'annotate')
@@ -1613,6 +1750,8 @@ function resolve( model ) {
1613
1750
  ext.kind = 'annotate'; // after setMemberParent()!
1614
1751
  setProp( art, '_extension', ext );
1615
1752
  setProp( ext.name, '_artifact', art );
1753
+ if (art.returns)
1754
+ ext.$syntax = 'returns';
1616
1755
  return ext;
1617
1756
  }
1618
1757
 
@@ -1779,7 +1918,7 @@ function resolve( model ) {
1779
1918
  function resolveQuery( query ) {
1780
1919
  if (!query._main) // parse error
1781
1920
  return;
1782
- populateQuery( query );
1921
+ traverseQueryPost( query, null, populateQuery );
1783
1922
  forEachGeneric( query, '$tableAliases', ( alias ) => {
1784
1923
  // console.log( info( null, [alias.location,alias], 'SQA:' ).toString() );
1785
1924
  if (alias.kind === 'mixin')
@@ -1816,7 +1955,7 @@ function resolve( model ) {
1816
1955
  return;
1817
1956
 
1818
1957
  function resolveJoinOn( join ) {
1819
- if (join.args) { // JOIN
1958
+ if (join && join.args) { // JOIN
1820
1959
  for (const j of join.args)
1821
1960
  resolveJoinOn( j );
1822
1961
  if (join.on)
@@ -1853,7 +1992,10 @@ function resolve( model ) {
1853
1992
  }
1854
1993
  const target = resolvePath( obj.target, 'target', art );
1855
1994
  if (obj.on) {
1856
- if (!art._main || !art._parent.elements && !art._parent.items) {
1995
+ if (!art._main || !art._parent.elements && !art._parent.items && !art._parent.targetAspect) {
1996
+ // TODO: test of .items a bit unclear - we should somehow restrict the
1997
+ // use of unmanaged assocs in MANY, at least with $self
1998
+ // TODO: $self usage in anonymous aspects to be corrected in Core Compiler
1857
1999
  const isComposition = obj.type && obj.type.path && obj.type.path[0] &&
1858
2000
  obj.type.path[0].id === 'cds.Composition';
1859
2001
  message( 'assoc-as-type', [ obj.on.location, art ],
@@ -2294,6 +2436,9 @@ function resolve( model ) {
2294
2436
 
2295
2437
  // TODO: there is no need to rewrite the on condition of non-leading queries,
2296
2438
  // i.e. we could just have on = {…}
2439
+ // TODO: re-check $self rewrite (with managed composition of aspects),
2440
+ // and actually also $self inside anonymous aspect definitions
2441
+ // (not entirely urgent as we do not analyse it further, at least sole "$self")
2297
2442
  function rewriteCondition( elem, assoc ) {
2298
2443
  if (enableExpandElements && elem._parent && elem._parent.kind === 'element') {
2299
2444
  // managed association as sub element not supported yet
@@ -2498,6 +2643,8 @@ function resolve( model ) {
2498
2643
  }
2499
2644
 
2500
2645
  function resolveExpr( expr, expected, user, extDict, expandOrInline) {
2646
+ // TODO: when we have rewritten the resolvePath functions,
2647
+ // define a traverseExpr() in ./utils.js
2501
2648
  // TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`
2502
2649
  if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
2503
2650
  return;
@@ -2534,7 +2681,7 @@ function resolve( model ) {
2534
2681
  else if (expr.query) {
2535
2682
  const { query } = expr;
2536
2683
  if (query.kind || query._leadingQuery) { // UNION has _leadingQuery
2537
- traverseQueryPost( query, false, resolveQuery );
2684
+ // traverseQueryPost( query, false, resolveQuery );
2538
2685
  }
2539
2686
  else {
2540
2687
  error( 'expr-no-subquery', [ expr.location, user ], {},
@@ -2545,12 +2692,19 @@ function resolve( model ) {
2545
2692
  const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
2546
2693
  args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
2547
2694
  }
2695
+ if (expr.suffix && isDeprecatedEnabled( options )) {
2696
+ const { location } = expr.suffix[0] || expr;
2697
+ error( null, [ location, user ], { prop: 'deprecated' },
2698
+ 'Window functions are not supported if $(PROP) options are set' );
2699
+ }
2700
+ if (expr.suffix)
2701
+ expr.suffix.forEach( s => s && resolveExpr( s, expected, user, extDict ) );
2548
2702
  }
2549
2703
 
2550
2704
  function resolveParamsAndWhere( step, expected, user, extDict, isLast ) {
2551
2705
  const alias = step._navigation && step._navigation.kind === '$tableAlias' && step._navigation;
2552
2706
  const type = alias || effectiveType( step._artifact );
2553
- const art = type && type.target && type.target._artifact || type;
2707
+ const art = (type && type.target) ? type.target._artifact : type;
2554
2708
  if (!art)
2555
2709
  return;
2556
2710
  const entity = (art.kind === 'entity') &&
@@ -2561,7 +2715,7 @@ function resolve( model ) {
2561
2715
  if (step.where)
2562
2716
  resolveExpr( step.where, 'filter', user, environment( type ) );
2563
2717
  }
2564
- else if (step.where || step.cardinality ) {
2718
+ else if (step.where && step.where.location || step.cardinality ) {
2565
2719
  const location = combinedLocation( step.where, step.cardinality );
2566
2720
  // XSN TODO: filter$location including […]
2567
2721
  message( 'expr-no-filter', [ location, user ], { '#': expected },
@@ -2729,8 +2883,6 @@ function navProjection( navigation, preferred ) {
2729
2883
  // Query tree post-order traversal - called for everything which makes a query
2730
2884
  // except "real ones": operands of UNION etc, JOIN with ON, and sub queries in FROM
2731
2885
  function traverseQueryPost( query, simpleOnly, callback ) {
2732
- while (Array.isArray(query)) // query in parentheses, TODO: remove
2733
- query = query[0];
2734
2886
  if (!query) // parser error
2735
2887
  return;
2736
2888
  if (!query.op) { // in FROM (not JOIN)
@@ -2750,11 +2902,19 @@ function traverseQueryPost( query, simpleOnly, callback ) {
2750
2902
  // console.log('FE:')
2751
2903
  }
2752
2904
  else if (query.args) { // JOIN, UNION, INTERSECT
2753
- for (const q of query.args)
2754
- traverseQueryPost( q, simpleOnly, callback );
2755
- // The ON condition has to be traversed extra, because it must be evaluated
2756
- // after the complete FROM has been traversed. It is also not necessary to
2757
- // evaluate it in populateQuery().
2905
+ if (!query.join && simpleOnly == null) {
2906
+ // enough for elements: traverse only first args for UNION/INTERSECT
2907
+ // TODO: we might use this also when we do not rewrite associations
2908
+ // in non-referred sub queries
2909
+ traverseQueryPost( query.args[0], simpleOnly, callback );
2910
+ }
2911
+ else {
2912
+ for (const q of query.args)
2913
+ traverseQueryPost( q, simpleOnly, callback );
2914
+ // The ON condition has to be traversed extra, because it must be evaluated
2915
+ // after the complete FROM has been traversed. It is also not necessary to
2916
+ // evaluate it in populateQuery().
2917
+ }
2758
2918
  }
2759
2919
  // else: with parse error (`select from <EOF>`, `select distinct from;`)
2760
2920
  }