@sap/cds-compiler 2.10.4 → 2.12.0

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 (103) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +58 -35
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +16 -0
  9. package/lib/api/.eslintrc.json +2 -0
  10. package/lib/api/main.js +10 -36
  11. package/lib/api/options.js +17 -8
  12. package/lib/api/validate.js +30 -3
  13. package/lib/backends.js +12 -13
  14. package/lib/base/dictionaries.js +2 -1
  15. package/lib/base/keywords.js +3 -2
  16. package/lib/base/message-registry.js +64 -11
  17. package/lib/base/messages.js +38 -18
  18. package/lib/base/model.js +6 -4
  19. package/lib/base/optionProcessorHelper.js +148 -86
  20. package/lib/checks/.eslintrc.json +2 -0
  21. package/lib/checks/actionsFunctions.js +2 -1
  22. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  23. package/lib/checks/foreignKeys.js +4 -4
  24. package/lib/checks/managedInType.js +4 -4
  25. package/lib/checks/queryNoDbArtifacts.js +1 -3
  26. package/lib/checks/selectItems.js +4 -0
  27. package/lib/checks/sql-snippets.js +93 -0
  28. package/lib/checks/unknownMagic.js +6 -3
  29. package/lib/checks/validator.js +8 -0
  30. package/lib/compiler/assert-consistency.js +14 -5
  31. package/lib/compiler/base.js +64 -0
  32. package/lib/compiler/builtins.js +62 -16
  33. package/lib/compiler/checks.js +34 -10
  34. package/lib/compiler/definer.js +91 -112
  35. package/lib/compiler/index.js +30 -30
  36. package/lib/compiler/propagator.js +8 -4
  37. package/lib/compiler/resolver.js +279 -63
  38. package/lib/compiler/shared.js +65 -230
  39. package/lib/compiler/utils.js +191 -0
  40. package/lib/edm/annotations/genericTranslation.js +35 -18
  41. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  42. package/lib/edm/csn2edm.js +4 -3
  43. package/lib/edm/edm.js +8 -8
  44. package/lib/edm/edmPreprocessor.js +61 -59
  45. package/lib/edm/edmUtils.js +14 -15
  46. package/lib/gen/Dictionary.json +82 -40
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +19 -1
  49. package/lib/gen/language.tokens +80 -73
  50. package/lib/gen/languageLexer.interp +27 -1
  51. package/lib/gen/languageLexer.js +925 -826
  52. package/lib/gen/languageLexer.tokens +72 -65
  53. package/lib/gen/languageParser.js +4817 -4102
  54. package/lib/json/from-csn.js +57 -26
  55. package/lib/json/to-csn.js +244 -51
  56. package/lib/language/antlrParser.js +12 -1
  57. package/lib/language/docCommentParser.js +1 -1
  58. package/lib/language/errorStrategy.js +26 -8
  59. package/lib/language/genericAntlrParser.js +106 -30
  60. package/lib/language/language.g4 +200 -70
  61. package/lib/language/multiLineStringParser.js +536 -0
  62. package/lib/main.d.ts +220 -21
  63. package/lib/main.js +6 -3
  64. package/lib/model/api.js +2 -2
  65. package/lib/model/csnRefs.js +218 -86
  66. package/lib/model/csnUtils.js +99 -178
  67. package/lib/model/enrichCsn.js +84 -43
  68. package/lib/model/revealInternalProperties.js +25 -8
  69. package/lib/model/sortViews.js +8 -1
  70. package/lib/modelCompare/compare.js +2 -1
  71. package/lib/optionProcessor.js +33 -18
  72. package/lib/render/.eslintrc.json +1 -2
  73. package/lib/render/DuplicateChecker.js +2 -2
  74. package/lib/render/manageConstraints.js +1 -1
  75. package/lib/render/toCdl.js +202 -82
  76. package/lib/render/toHdbcds.js +194 -135
  77. package/lib/render/toRename.js +7 -10
  78. package/lib/render/toSql.js +91 -51
  79. package/lib/render/utils/common.js +24 -5
  80. package/lib/render/utils/sql.js +6 -4
  81. package/lib/transform/braceExpression.js +4 -2
  82. package/lib/transform/db/applyTransformations.js +189 -0
  83. package/lib/transform/db/associations.js +389 -0
  84. package/lib/transform/db/cdsPersistence.js +150 -0
  85. package/lib/transform/db/constraints.js +275 -119
  86. package/lib/transform/db/draft.js +6 -4
  87. package/lib/transform/db/expansion.js +10 -9
  88. package/lib/transform/db/flattening.js +23 -8
  89. package/lib/transform/db/temporal.js +236 -0
  90. package/lib/transform/db/transformExists.js +106 -25
  91. package/lib/transform/db/views.js +485 -0
  92. package/lib/transform/forHanaNew.js +90 -1036
  93. package/lib/transform/forOdataNew.js +11 -3
  94. package/lib/transform/localized.js +5 -14
  95. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  96. package/lib/transform/transformUtilsNew.js +34 -20
  97. package/lib/transform/translateAssocsToJoins.js +15 -23
  98. package/lib/transform/universalCsnEnricher.js +217 -47
  99. package/lib/utils/file.js +13 -6
  100. package/lib/utils/term.js +65 -42
  101. package/lib/utils/timetrace.js +55 -27
  102. package/package.json +1 -1
  103. package/lib/transform/db/helpers.js +0 -58
@@ -47,25 +47,30 @@ 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,
57
+ pathName,
54
58
  splitIntoPath,
55
- } = require('./definer');
59
+ linkToOrigin,
60
+ setMemberParent,
61
+ withAssociation,
62
+ storeExtension,
63
+ dependsOn,
64
+ dependsOnSilent,
65
+ } = require('./utils');
56
66
 
57
67
  const detectCycles = require('./cycle-detector');
58
68
  const layers = require('./moduleLayers');
59
69
 
60
- const {
61
- kindProperties, fns, setLink, linkToOrigin, setMemberParent, withAssociation, storeExtension,
62
- dependsOn, dependsOnSilent,
63
- } = require('./shared');
64
-
65
70
  const annotationPriorities = {
66
71
  define: 1, extend: 2, annotate: 2, edmx: 3,
67
72
  };
68
-
73
+ const $inferred = Symbol.for('cds.$inferred');
69
74
 
70
75
  // Export function of this file. Resolve type references in augmented CSN
71
76
  // `model`. If the model has a property argument `messages`, do not throw
@@ -73,21 +78,21 @@ const annotationPriorities = {
73
78
  // that property (should be a vector).
74
79
  function resolve( model ) {
75
80
  const { options } = model;
76
- // Get shared "resolve" functionality and the message function:
81
+ // Get shared functionality and the message function:
82
+ const {
83
+ info, warning, error, message,
84
+ } = model.$messageFunctions;
77
85
  const {
78
86
  resolvePath,
79
87
  resolveTypeArguments,
80
88
  defineAnnotations,
81
89
  attachAndEmitValidNames,
82
- } = fns( model, environment );
83
- const {
84
- info, warning, error, message,
85
- } = model.$messageFunctions;
86
- const {
87
90
  initArtifact,
88
91
  lateExtensions,
89
92
  projectionAncestor,
90
- } = getDefinerFunctions(model);
93
+ } = model.$functions;
94
+ model.$volatileFunctions.environment = environment;
95
+
91
96
  /** @type {any} may also be a boolean */
92
97
  let newAutoExposed = [];
93
98
 
@@ -242,6 +247,7 @@ function resolve( model ) {
242
247
  const chain = [];
243
248
  while (art && !('_effectiveType' in art) &&
244
249
  (art.type || art._origin || art.value && art.value.path) &&
250
+ // TODO: really stop at art.enum?
245
251
  !art.target && !art.enum && !art.elements && !art.items) {
246
252
  chain.push( art );
247
253
  setProp( art, '_effectiveType', 0 ); // initial setting in case of cycles
@@ -273,13 +279,17 @@ function resolve( model ) {
273
279
  // collect the "latest" cardinality (calculate lazyly if necessary)
274
280
  let cardinality = art.cardinality ||
275
281
  art._effectiveType && (() => getCardinality( art._effectiveType ));
282
+ let prev = art;
276
283
  for (const a of chain) {
277
284
  if (a.cardinality)
278
285
  cardinality = a.cardinality;
279
286
  if (a.expand && expandFromColumns( a, art, cardinality ) ||
280
287
  art.target && redirectImplicitly( a, art ) ||
281
- art.elements && expandElements( a, art, eType ))
288
+ art.elements && expandElements( a, art, eType ) ||
289
+ art.items && expandItems( a, art, eType ))
282
290
  art = a;
291
+ else if (art.enum && expandEnum( a, prev ))
292
+ prev = a; // do not set art - effective type is base
283
293
  setProp( a, '_effectiveType', art );
284
294
  }
285
295
  }
@@ -351,13 +361,13 @@ function resolve( model ) {
351
361
  while (struct.kind === 'element')
352
362
  struct = struct._parent;
353
363
  if (struct.kind === 'select') {
354
- message( 'ref-invalid-typeof', [ ref.location, user ],
364
+ message( 'type-unexpected-typeof', [ ref.location, user ],
355
365
  { keyword: 'type of', '#': struct.kind } );
356
366
  // we actually refer to an element in _combined; TODO: return null if
357
367
  // not configurable; would produce illegal CSN with sub queries in FROM
358
368
  }
359
369
  else if (struct !== user._main) {
360
- message( 'ref-invalid-typeof', [ ref.location, user ],
370
+ message( 'type-unexpected-typeof', [ ref.location, user ],
361
371
  { keyword: 'type of', '#': struct.kind } );
362
372
  return setProp( ref, '_artifact', null );
363
373
  }
@@ -405,7 +415,8 @@ function resolve( model ) {
405
415
  for (const view of resolveChain.reverse()) {
406
416
  if (view._status !== '_query' ) { // not already resolved
407
417
  setProp( view, '_status', '_query' );
408
- traverseQueryPost( view.query, false, populateQuery );
418
+ // must be run in order “sub query in FROM first”:
419
+ traverseQueryPost( view.query, null, populateQuery );
409
420
  if (view.elements$) // specified elements
410
421
  mergeSpecifiedElements( view );
411
422
  if (!view.$entity) {
@@ -494,7 +505,8 @@ function resolve( model ) {
494
505
  });
495
506
  }
496
507
  forEachGeneric( { elements: alias.elements }, 'elements', ( elem, name ) => {
497
- dictAddArray( query._combined, name, elem, null ); // not dictAdd()
508
+ if (elem.$duplicates !== true)
509
+ dictAddArray( query._combined, name, elem, null ); // not dictAdd()
498
510
  });
499
511
  }
500
512
  }
@@ -535,6 +547,23 @@ function resolve( model ) {
535
547
  setMemberParent( key, name, elem ); // TODO: set _block here if not present?
536
548
  }
537
549
 
550
+ function expandItems( art, origin, eType ) {
551
+ if (!enableExpandElements || art.items)
552
+ return false;
553
+ if (isInParents( art, eType )) {
554
+ art.items = 0; // circular
555
+ return true;
556
+ }
557
+ const ref = art.type || art.value || art.name;
558
+ const location = ref && ref.location || art.location;
559
+ art.items = { $inferred: 'expand-element', location };
560
+ setProp( art.items, '_outer', art );
561
+ setProp( art.items, '_origin', origin.items );
562
+ if (!art.$expand)
563
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
564
+ return true;
565
+ }
566
+
538
567
  function expandElements( art, struct, eType ) {
539
568
  if (!enableExpandElements)
540
569
  return false;
@@ -566,7 +595,28 @@ function resolve( model ) {
566
595
  // member property):
567
596
  if (!art.$expand)
568
597
  art.$expand = 'origin'; // if value stays, elements won't appear in CSN
569
- // TODO: have some art.elements[SYM.$inferred] = 'expand-elements';
598
+ // TODO: have some art.elements[SYM.$inferred] = 'expand-element';
599
+ return true;
600
+ }
601
+
602
+ function expandEnum( art, origin ) {
603
+ if (!enableExpandElements || art.enum)
604
+ return false;
605
+ const ref = art.type || art.value || art.name;
606
+ const location = weakLocation( ref && ref.location || art.location );
607
+ art.enum = Object.create(null);
608
+ for (const name in origin.enum) {
609
+ const orig = origin.enum[name];
610
+ linkToOrigin( orig, name, art, 'enum', location, true )
611
+ // or should we use orig.location? - TODO: try to find test to see message
612
+ .$inferred = 'expand-element';
613
+ }
614
+ // Set elements expansion status (the if condition is always true, as no
615
+ // elements expansion will take place on artifact with existing other
616
+ // member property):
617
+ if (!art.$expand)
618
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
619
+ art.enum[$inferred] = 'expand-element';
570
620
  return true;
571
621
  }
572
622
 
@@ -604,7 +654,7 @@ function resolve( model ) {
604
654
  // not counting propagated ones; set up to the definition (main artifact)
605
655
  // (only set with anno on $inferred elem)
606
656
  // Usage according to CSN flavor:
607
- // - gensrc: do not render enferred elements (including expanded elements),
657
+ // - gensrc: do not render inferred elements (including expanded elements),
608
658
  // collect annotate statements with value 'annotate'
609
659
  // - client: do not render expanded sub elements if artifact/member is no type, has a type,
610
660
  // has $expand = 'origin', and all its _origin also have $expand = 'origin'
@@ -672,35 +722,69 @@ function resolve( model ) {
672
722
  else if (exposed.length === 1) {
673
723
  target = exposed[0];
674
724
  }
675
- else {
676
- message( (elem !== assoc ? 'redirected-implicitly-ambiguous' : 'type-ambiguous-target'),
677
- [ (elem.value || elem.name).location, elem ],
725
+ else if (elem === assoc) {
726
+ // `assoc: Association to ModelEntity`: user-provided target is to be auto-redirected
727
+ warning( 'type-ambiguous-target',
728
+ [ elem.target.location, elem ],
678
729
  {
679
730
  target,
680
- service,
681
- art: definitionScope( target ),
731
+ // art: definitionScope( target ), - TODO extra debug info in message
682
732
  sorted_arts: exposed,
683
- '#': ( elemScope !== true ? 'std' : 'scoped' ),
684
733
  }, {
685
734
  // eslint-disable-next-line max-len
686
- std: 'Target $(TARGET) is exposed in service $(SERVICE) by multiple projections $(SORTED_ARTS) - no implicit redirection',
735
+ std: 'Replace target $(TARGET) by one of $(SORTED_ARTS); can\'t auto-redirect this association if multiple projections exist in this service',
687
736
  // eslint-disable-next-line max-len
688
- scoped: 'Target $(TARGET) is defined in scope $(ART) which exposed in service $(SERVICE) by multiple projections - no implicit redirection',
737
+ two: 'Replace target $(TARGET) by $(SORTED_ARTS) or $(SECOND); can\'t auto-redirect this association if multiple projections exist in this service',
689
738
  });
739
+ // continuation semantics: no auto-redirection
740
+ }
741
+ else {
742
+ // referred (and probably inferred) assoc (without a user-provided target at that place)
743
+ // HINT: consider bin/cdsv2m.js when changing the following message text
744
+ // No grouped and sub messages yet (TODO v3): mention at all target places with all assocs
745
+ const withAnno = annotationVal( exposed[0]['@cds.redirection.target'] );
746
+ for (const proj of exposed) {
747
+ // TODO: def-ambiguous-target (just v3, as the current is infamous and used in options),
748
+ message( 'redirected-implicitly-ambiguous',
749
+ [ weakLocation( proj.location ), proj ],
750
+ {
751
+ '#': withAnno && 'justOne',
752
+ target,
753
+ art: elem,
754
+ // art: definitionScope( target ), - TODO extra debug info in message
755
+ anno: 'cds.redirection.target',
756
+ sorted_arts: exposed,
757
+ }, {
758
+ // eslint-disable-next-line max-len
759
+ 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',
760
+ // eslint-disable-next-line max-len
761
+ 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',
762
+ // eslint-disable-next-line max-len
763
+ 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',
764
+ } );
765
+ }
690
766
  // continuation semantics: no implicit redirections
691
767
  }
692
768
  }
693
769
  if (elem.target) { // redirection for Association to / Composition of
694
770
  if (elem.target._artifact === target) // no change (due to no implicit redirection)
695
771
  return true;
772
+ const type = resolvePath( elem.type, 'type', elem ); // cds.Association or cds.Composition
696
773
  const origin = {
697
- kind: elem.kind,
774
+ kind: elem.kind, // necessary for rewrite, '$user-provided' would be best
698
775
  name: elem.name,
776
+ type: {
777
+ path: [ { id: type.name.absolute, location: elem.type.location } ],
778
+ scope: 'global',
779
+ location: elem.type.location,
780
+ },
699
781
  target: elem.target,
700
782
  $inferred: 'REDIRECTED',
701
783
  location: elem.target.location,
702
784
  };
703
785
  setLink( elem, origin, '_origin' );
786
+ setLink( elem.type, type, '_artifact' );
787
+ setLink( origin, elem, '_outer' );
704
788
  setLink( origin, elem._parent, '_parent' );
705
789
  if (elem._main) // remark: the param `elem` can also be a type
706
790
  setLink( origin, elem._main, '_main' );
@@ -708,11 +792,11 @@ function resolve( model ) {
708
792
  setLink( origin, elem._block, '_block' );
709
793
  if (elem.foreignKeys) {
710
794
  origin.foreignKeys = elem.foreignKeys;
711
- delete elem.foreignKeys;
795
+ delete elem.foreignKeys; // will be rewritten
712
796
  }
713
797
  if (elem.on) {
714
798
  origin.on = elem.on;
715
- delete elem.on;
799
+ delete elem.on; // will be rewritten
716
800
  }
717
801
  }
718
802
  elem.target = {
@@ -771,10 +855,7 @@ function resolve( model ) {
771
855
  target._descendants[service.name.absolute] ||
772
856
  [],
773
857
  elemScope, target );
774
- const preferred = descendants.filter( ( d ) => {
775
- const anno = d['@cds.redirection.target'];
776
- return anno && (anno.val === undefined || anno.val );
777
- } );
858
+ const preferred = descendants.filter( d => annotationVal( d['@cds.redirection.target'] ) );
778
859
  const exposed = preferred.length ? preferred : descendants;
779
860
  if (exposed.length < 2)
780
861
  return exposed || [];
@@ -813,7 +894,7 @@ function resolve( model ) {
813
894
  // Need to filter out auto-exposed, otherwise the behavior is
814
895
  // processing-order dependent (not storing the autoexposed in
815
896
  // _descendents would only be an alternative w/o recompilation)
816
- return descendants.filter( d => !d['@cds.autoexposed'] );
897
+ return descendants.filter( d => !annotationVal( d['@cds.autoexposed'] ) );
817
898
  }
818
899
  // try scope as target first, even if it has @cds.redirection.target: false
819
900
  if (isDirectProjection( elemScope, target ))
@@ -944,6 +1025,7 @@ function resolve( model ) {
944
1025
  $inferred: 'autoexposed',
945
1026
  '@cds.autoexposed': {
946
1027
  name: { path: [ { id: 'cds.autoexposed', location } ], location },
1028
+ $inferred: 'autoexposed',
947
1029
  },
948
1030
  };
949
1031
  // TODO: do we need to tag the generated entity with elemScope = 'auto'?
@@ -1027,7 +1109,6 @@ function resolve( model ) {
1027
1109
  // or use userQuery( query ) in the following, too?
1028
1110
  setMemberParent( col, `.${ q.$inlines.length }`, query );
1029
1111
  initFromColumns( query, col.inline, col );
1030
- continue;
1031
1112
  }
1032
1113
  else if (!col.$replacement) {
1033
1114
  const id = ensureColumnName( col, query );
@@ -1441,8 +1522,9 @@ function resolve( model ) {
1441
1522
  }
1442
1523
  // Resolve projections/views
1443
1524
  // if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );
1444
- // TODO: here, any order should be ok, i.e. just loop over $queries
1445
- traverseQueryPost( art.query, false, resolveQuery );
1525
+
1526
+ if (art.$queries)
1527
+ art.$queries.forEach( resolveQuery );
1446
1528
 
1447
1529
  if (obj.type || obj._origin || obj.value && obj.value.path || obj.elements) // typed artifacts
1448
1530
  effectiveType(obj); // set _effectiveType if appropriate, (future?): copy elems if extended
@@ -1520,9 +1602,17 @@ function resolve( model ) {
1520
1602
  }
1521
1603
  }
1522
1604
 
1523
- function annotateMembers( art, extensions = [], prop, name, parent, kind ) {
1605
+ /**
1606
+ * @param {XSN.Artifact} art
1607
+ * @param {XSN.Extension[]} [extensions]
1608
+ * @param {string} [prop]
1609
+ * @param {string} [name]
1610
+ * @param {object} [parent]
1611
+ * @param {string} [kind]
1612
+ */
1613
+ function annotateMembers( art, extensions, prop, name, parent, kind ) {
1524
1614
  const showMsg = !art && parent && parent.kind !== 'annotate';
1525
- if (!art && extensions.length) {
1615
+ if (!art && extensions && extensions.length) {
1526
1616
  if (Array.isArray( parent ))
1527
1617
  return;
1528
1618
  const parentExt = extensionFor(parent);
@@ -1538,7 +1628,7 @@ function resolve( model ) {
1538
1628
  }
1539
1629
  }
1540
1630
 
1541
- for (const ext of extensions) {
1631
+ for (const ext of extensions || []) {
1542
1632
  if ('_artifact' in ext.name) // already applied
1543
1633
  continue;
1544
1634
  setProp( ext.name, '_artifact', art );
@@ -1658,6 +1748,8 @@ function resolve( model ) {
1658
1748
  location,
1659
1749
  $inferred: 'expand-param',
1660
1750
  };
1751
+ setProp( art.returns, '_parent', art );
1752
+ setProp( art.returns, '_main', art._main || art );
1661
1753
  setProp( art.returns, '_origin', origin.returns );
1662
1754
  }
1663
1755
  }
@@ -1765,7 +1857,7 @@ function resolve( model ) {
1765
1857
  [ mergeSource.name.location, art ], { code: '...' } );
1766
1858
  return;
1767
1859
  }
1768
- mergeTarget.val.splice(pos, 1, ...mergeSource.val);
1860
+ mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
1769
1861
  }
1770
1862
  }
1771
1863
  });
@@ -1785,7 +1877,7 @@ function resolve( model ) {
1785
1877
  [ mergeSource.name.location, art ], { code: '...' } );
1786
1878
  return mergeTarget;
1787
1879
  }
1788
- mergeTarget.val.splice(pos, 1, ...mergeSource.val);
1880
+ mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
1789
1881
  layer = layers.layer( mergeSource._block );
1790
1882
  delete layerAnnos[(layer) ? layer.realname : ''];
1791
1883
  pos = findEllipsis( mergeTarget );
@@ -1796,6 +1888,89 @@ function resolve( model ) {
1796
1888
  return mergeTarget;
1797
1889
  }
1798
1890
 
1891
+ function mergeArrayValues( previousValue, arraySpec ) {
1892
+ let prevPos = 0;
1893
+ const result = [];
1894
+ for (const item of arraySpec) {
1895
+ const ell = item && item.literal === 'token' && item.val === '...';
1896
+ if (!ell) {
1897
+ result.push( item );
1898
+ }
1899
+ else {
1900
+ let upToSpec = item.upTo && checkUpToSpec( item.upTo, true );
1901
+ while (prevPos < previousValue.length) {
1902
+ const prevItem = previousValue[prevPos++];
1903
+ result.push( prevItem );
1904
+ if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
1905
+ upToSpec = false;
1906
+ break;
1907
+ }
1908
+ }
1909
+ if (upToSpec) { // non-matched UP TO
1910
+ warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
1911
+ 'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
1912
+ }
1913
+ }
1914
+ }
1915
+ return result;
1916
+ }
1917
+
1918
+ function checkUpToSpec( upToSpec, trueIfFullUpTo ) {
1919
+ const { literal } = upToSpec;
1920
+ if (trueIfFullUpTo !== true) { // inside struct of UP TO
1921
+ if (![ 'struct', 'array' ].includes( literal ))
1922
+ return true;
1923
+ }
1924
+ else if (literal === 'struct') {
1925
+ return Object.values( upToSpec.struct ).every( checkUpToSpec );
1926
+ }
1927
+ else if (![ 'array', 'boolean', 'null' ].includes( literal )) {
1928
+ return true;
1929
+ }
1930
+ error( null, [ upToSpec.location, art ],
1931
+ { anno: annoName, code: '... up to', '#': literal },
1932
+ {
1933
+ std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
1934
+ array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
1935
+ // eslint-disable-next-line max-len
1936
+ struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
1937
+ boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
1938
+ null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
1939
+ } );
1940
+ return false;
1941
+ }
1942
+
1943
+ function equalUpTo( previousItem, upToSpec ) {
1944
+ if (!previousItem)
1945
+ return false;
1946
+ if ('val' in upToSpec) {
1947
+ if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
1948
+ return true;
1949
+ const typeUpTo = typeof upToSpec.val;
1950
+ const typePrev = typeof previousItem.val;
1951
+ if (typeUpTo === 'number')
1952
+ return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
1953
+ if (typePrev === 'number')
1954
+ return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
1955
+ }
1956
+ else if (upToSpec.path) {
1957
+ return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
1958
+ }
1959
+ else if (upToSpec.sym) {
1960
+ return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
1961
+ }
1962
+ else if (upToSpec.struct && previousItem.struct) {
1963
+ return Object.entries( upToSpec.struct )
1964
+ .every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
1965
+ }
1966
+ return false;
1967
+ }
1968
+
1969
+ function normalizeRef( node ) { // see to-csn.js
1970
+ const ref = pathName( node.path );
1971
+ return node.variant ? `${ ref }#${ node.variant.id }` : ref;
1972
+ }
1973
+
1799
1974
  function removeEllipsis(a, pos = findEllipsis( a )) {
1800
1975
  let count = 0;
1801
1976
  while (a.literal === 'array' && pos > -1) {
@@ -1868,7 +2043,7 @@ function resolve( model ) {
1868
2043
  function resolveQuery( query ) {
1869
2044
  if (!query._main) // parse error
1870
2045
  return;
1871
- populateQuery( query );
2046
+ traverseQueryPost( query, null, populateQuery );
1872
2047
  forEachGeneric( query, '$tableAliases', ( alias ) => {
1873
2048
  // console.log( info( null, [alias.location,alias], 'SQA:' ).toString() );
1874
2049
  if (alias.kind === 'mixin')
@@ -2009,6 +2184,7 @@ function resolve( model ) {
2009
2184
  dependsOnSilent(art, key);
2010
2185
  }
2011
2186
  });
2187
+ obj.foreignKeys[$inferred] = 'keys';
2012
2188
  }
2013
2189
 
2014
2190
  function addForeignKeyNavigations( art ) {
@@ -2177,8 +2353,6 @@ function resolve( model ) {
2177
2353
  // Only top-level queries and sub queries in FROM
2178
2354
 
2179
2355
  function rewriteSimple( art ) {
2180
- // If we have a proper seperation of view elements and elements of the
2181
- // primary query, we can delete this function.
2182
2356
  // return;
2183
2357
  if (!art.includes && !art.query) {
2184
2358
  // console.log(message( null, art.location, art, {target:art._target},
@@ -2191,7 +2365,7 @@ function resolve( model ) {
2191
2365
  }
2192
2366
 
2193
2367
  function rewriteView( view ) {
2194
- traverseQueryPost( view.query, false, ( query ) => {
2368
+ traverseQueryExtra( view, ( query ) => {
2195
2369
  forEachGeneric( query, 'elements', rewriteAssociation );
2196
2370
  } );
2197
2371
  if (view.includes) // entities with structure includes:
@@ -2199,6 +2373,7 @@ function resolve( model ) {
2199
2373
  }
2200
2374
 
2201
2375
  // Check explicit ON / keys with REDIRECTED TO
2376
+ // TODO: run on all queries, but this is potentially incompatible
2202
2377
  function rewriteViewCheck( view ) {
2203
2378
  traverseQueryPost( view.query, false, ( query ) => {
2204
2379
  forEachGeneric( query, 'elements', rewriteAssociationCheck );
@@ -2424,7 +2599,7 @@ function resolve( model ) {
2424
2599
  resolveExpr( cond, rewriteExpr, elem, nav.tableAlias );
2425
2600
  }
2426
2601
  else {
2427
- // TODO: support that
2602
+ // TODO: support that, now that the ON condition is rewritten in the right order
2428
2603
  error( null, [ elem.value.location, elem ],
2429
2604
  'Selecting unmanaged associations from a sub query is not supported' );
2430
2605
  }
@@ -2593,6 +2768,8 @@ function resolve( model ) {
2593
2768
  }
2594
2769
 
2595
2770
  function resolveExpr( expr, expected, user, extDict, expandOrInline) {
2771
+ // TODO: when we have rewritten the resolvePath functions,
2772
+ // define a traverseExpr() in ./utils.js
2596
2773
  // TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`
2597
2774
  if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
2598
2775
  return;
@@ -2629,7 +2806,7 @@ function resolve( model ) {
2629
2806
  else if (expr.query) {
2630
2807
  const { query } = expr;
2631
2808
  if (query.kind || query._leadingQuery) { // UNION has _leadingQuery
2632
- traverseQueryPost( query, false, resolveQuery );
2809
+ // traverseQueryPost( query, false, resolveQuery );
2633
2810
  }
2634
2811
  else {
2635
2812
  error( 'expr-no-subquery', [ expr.location, user ], {},
@@ -2640,9 +2817,10 @@ function resolve( model ) {
2640
2817
  const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
2641
2818
  args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
2642
2819
  }
2643
- if (expr.suffix && !isBetaEnabled( options, 'windowFunctions' )) {
2820
+ if (expr.suffix && isDeprecatedEnabled( options )) {
2644
2821
  const { location } = expr.suffix[0] || expr;
2645
- error( null, [ location, user ], 'Window functions are not supported' );
2822
+ error( null, [ location, user ], { prop: 'deprecated' },
2823
+ 'Window functions are not supported if $(PROP) options are set' );
2646
2824
  }
2647
2825
  if (expr.suffix)
2648
2826
  expr.suffix.forEach( s => s && resolveExpr( s, expected, user, extDict ) );
@@ -2827,11 +3005,11 @@ function navProjection( navigation, preferred ) {
2827
3005
  : navigation._projections[0] || null;
2828
3006
  }
2829
3007
 
2830
- // Query tree post-order traversal - called for everything which makes a query
3008
+ // Query tree post-order traversal - called for everything which contributes to the query
3009
+ // i.e. is necessary to calculate the elements of the query
2831
3010
  // except "real ones": operands of UNION etc, JOIN with ON, and sub queries in FROM
3011
+ // NOTE: does not run on non-referred sub queries! Consider using ‹main›.$queries instead!
2832
3012
  function traverseQueryPost( query, simpleOnly, callback ) {
2833
- while (Array.isArray(query)) // query in parentheses, TODO: remove
2834
- query = query[0];
2835
3013
  if (!query) // parser error
2836
3014
  return;
2837
3015
  if (!query.op) { // in FROM (not JOIN)
@@ -2851,13 +3029,51 @@ function traverseQueryPost( query, simpleOnly, callback ) {
2851
3029
  // console.log('FE:')
2852
3030
  }
2853
3031
  else if (query.args) { // JOIN, UNION, INTERSECT
2854
- for (const q of query.args)
2855
- traverseQueryPost( q, simpleOnly, callback );
2856
- // The ON condition has to be traversed extra, because it must be evaluated
2857
- // after the complete FROM has been traversed. It is also not necessary to
2858
- // evaluate it in populateQuery().
3032
+ if (!query.join && simpleOnly == null) {
3033
+ // enough for elements: traverse only first args for UNION/INTERSECT
3034
+ // TODO: we might use this also when we do not rewrite associations
3035
+ // in non-referred sub queries
3036
+ traverseQueryPost( query.args[0], simpleOnly, callback );
3037
+ }
3038
+ else {
3039
+ for (const q of query.args)
3040
+ traverseQueryPost( q, simpleOnly, callback );
3041
+ // The ON condition has to be traversed extra, because it must be evaluated
3042
+ // after the complete FROM has been traversed. It is also not necessary to
3043
+ // evaluate it in populateQuery().
3044
+ }
2859
3045
  }
2860
3046
  // else: with parse error (`select from <EOF>`, `select distinct from;`)
2861
3047
  }
2862
3048
 
3049
+ // Call callback on all queries in dependency order, i.e. starting with query Q
3050
+ // 1. sub queries in FROM sources of Q
3051
+ // 2. Q itself, except if non-referred query, but with right UNION parts
3052
+ // 3. sub queries in ON in FROM of Q
3053
+ // 4. sub queries in columns, WHERE, HAVING
3054
+ function traverseQueryExtra( main, callback ) {
3055
+ if (!main.$queries)
3056
+ return;
3057
+ // with a top-level UNION, $queries[0] is just the left
3058
+ traverseQueryPost( main.query, false, (q) => { // also with right of UNION (to be compatible)
3059
+ setProp( q, '_status', 'extra' );
3060
+ callback( q );
3061
+ } );
3062
+ for (const query of main.$queries.slice(1)) {
3063
+ if (query._status === 'extra' || query._parent.kind === '$tableAlias')
3064
+ continue; // if parent is alias, query is FROM source -> run by traverseQueryPost
3065
+ // we are now in the top-level (parent is entity) or a non-referred query (parent is query)
3066
+ setProp( query, '_status', 'extra' ); // do not call callback() in non-referred query
3067
+ // console.log( 'A:', query.name,query._status)
3068
+ traverseQueryPost( query, null, (q) => {
3069
+ if (q._status !== 'extra') {
3070
+ // console.log( 'T:', q.name)
3071
+ setProp( q, '_status', 'extra' );
3072
+ callback( q );
3073
+ }
3074
+ // else console.log( 'E:', q.name)
3075
+ } );
3076
+ }
3077
+ }
3078
+
2863
3079
  module.exports = resolve;