@sap/cds-compiler 3.0.0 → 3.1.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 (79) hide show
  1. package/CHANGELOG.md +104 -9
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +28 -16
  4. package/doc/API.md +11 -0
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +24 -2
  7. package/doc/CHANGELOG_DEPRECATED.md +21 -1
  8. package/lib/api/main.js +92 -40
  9. package/lib/api/options.js +2 -3
  10. package/lib/base/keywords.js +64 -1
  11. package/lib/base/message-registry.js +33 -5
  12. package/lib/base/messages.js +54 -65
  13. package/lib/base/model.js +2 -0
  14. package/lib/base/optionProcessorHelper.js +53 -21
  15. package/lib/checks/actionsFunctions.js +8 -7
  16. package/lib/checks/selectItems.js +96 -14
  17. package/lib/checks/types.js +5 -8
  18. package/lib/checks/validator.js +1 -2
  19. package/lib/compiler/assert-consistency.js +65 -13
  20. package/lib/compiler/base.js +6 -4
  21. package/lib/compiler/builtins.js +93 -4
  22. package/lib/compiler/checks.js +1 -1
  23. package/lib/compiler/define.js +28 -23
  24. package/lib/compiler/extend.js +20 -11
  25. package/lib/compiler/finalize-parse-cdl.js +5 -9
  26. package/lib/compiler/index.js +2 -0
  27. package/lib/compiler/populate.js +37 -32
  28. package/lib/compiler/propagator.js +11 -6
  29. package/lib/compiler/resolve.js +15 -19
  30. package/lib/compiler/shared.js +54 -18
  31. package/lib/compiler/tweak-assocs.js +5 -11
  32. package/lib/compiler/utils.js +15 -6
  33. package/lib/edm/annotations/genericTranslation.js +12 -2
  34. package/lib/edm/annotations/preprocessAnnotations.js +18 -15
  35. package/lib/edm/csn2edm.js +18 -17
  36. package/lib/edm/edm.js +22 -13
  37. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  38. package/lib/edm/edmInboundChecks.js +85 -0
  39. package/lib/edm/edmPreprocessor.js +336 -665
  40. package/lib/edm/edmUtils.js +86 -45
  41. package/lib/gen/Dictionary.json +29 -9
  42. package/lib/gen/language.checksum +1 -1
  43. package/lib/gen/language.interp +1 -2
  44. package/lib/gen/languageLexer.js +3 -0
  45. package/lib/gen/languageParser.js +4332 -4496
  46. package/lib/inspect/.eslintrc.json +4 -0
  47. package/lib/inspect/index.js +14 -0
  48. package/lib/inspect/inspectModelStatistics.js +81 -0
  49. package/lib/inspect/inspectPropagation.js +189 -0
  50. package/lib/inspect/inspectUtils.js +44 -0
  51. package/lib/json/from-csn.js +19 -20
  52. package/lib/json/to-csn.js +11 -8
  53. package/lib/language/genericAntlrParser.js +150 -92
  54. package/lib/language/language.g4 +47 -74
  55. package/lib/main.d.ts +1 -0
  56. package/lib/model/api.js +1 -1
  57. package/lib/model/csnRefs.js +56 -29
  58. package/lib/model/csnUtils.js +29 -14
  59. package/lib/model/revealInternalProperties.js +6 -4
  60. package/lib/modelCompare/compare.js +3 -0
  61. package/lib/optionProcessor.js +81 -38
  62. package/lib/render/toCdl.js +57 -32
  63. package/lib/render/toHdbcds.js +1 -1
  64. package/lib/render/toSql.js +31 -11
  65. package/lib/render/utils/common.js +3 -4
  66. package/lib/transform/db/associations.js +43 -35
  67. package/lib/transform/db/cdsPersistence.js +0 -1
  68. package/lib/transform/db/flattening.js +3 -4
  69. package/lib/transform/db/transformExists.js +7 -5
  70. package/lib/transform/draft/db.js +1 -1
  71. package/lib/transform/forHanaNew.js +11 -2
  72. package/lib/transform/forOdataNew.js +4 -4
  73. package/lib/transform/localized.js +15 -11
  74. package/lib/transform/odata/typesExposure.js +14 -5
  75. package/lib/utils/file.js +28 -18
  76. package/lib/utils/moduleResolve.js +0 -1
  77. package/package.json +3 -4
  78. package/share/messages/syntax-expected-integer.md +9 -8
  79. package/lib/checks/unknownMagic.js +0 -41
@@ -385,10 +385,9 @@ artifactDef[ outer, defOnly = false ] locals[ art = {} ] // cannot use `parent`
385
385
  }
386
386
  // #ATN: EXTEND elem, while CONTEXT, ENTITY etc are not reserved
387
387
  ( extendContext[ $art, $outer ]
388
- | extendEntity[ $art, $outer ]
388
+ | extendEntity[ $art, $outer ] // or aspect
389
389
  | extendProjection[ $art, $outer ]
390
390
  | extendType[ $art, $outer ]
391
- | extendAspect[ $art, $outer ]
392
391
  // Streamlined Syntax
393
392
  | extendArtifact[ $art, $outer ]
394
393
  )
@@ -398,6 +397,7 @@ artifactDef[ outer, defOnly = false ] locals[ art = {} ] // cannot use `parent`
398
397
  this.error( 'syntax-extend-context', $annotate,
399
398
  { code: 'ANNOTATE artifact', kind: defOnly },
400
399
  'No $(CODE) within $(KIND) extensions' );
400
+ if (!$outer.extensions) $outer.extensions = [];
401
401
  this.meltKeywordToIdentifier();
402
402
  }
403
403
  annotateArtifact[ $art, $outer ] // not kind-specific
@@ -543,11 +543,12 @@ projectionExclusion[ outer ] locals[ art = {} ]
543
543
  { this.addDef( $art, $outer, 'excludingDict', '', $name.id ); }
544
544
  ;
545
545
 
546
+ // also used for aspect
546
547
  extendEntity[ art, outer ] locals[ name = {} ]
547
548
  @after { /* #ATN 1 */ this.attachLocation( $art ); }
548
549
  :
549
- ENTITY simplePath[ $name, 'Extend' ]
550
- { $art.expectedKind = 'entity'; $art.name = $name;
550
+ kind=(ASPECT | ENTITY) simplePath[ $name, 'Extend' ]
551
+ { $art.expectedKind = $kind.text.toLowerCase(); $art.name = $name;
551
552
  this.addItem( $art, $outer, 'extensions', 'extend' );
552
553
  }
553
554
  (
@@ -555,7 +556,7 @@ extendEntity[ art, outer ] locals[ name = {} ]
555
556
  annotationAssignment_ll1[ $art ]*
556
557
  // ATN: the ref can start with ACTIONS
557
558
  (
558
- includeRef[ $art ]
559
+ includeRef[ $art ] ( ',' includeRef[ $art ] )*
559
560
  requiredSemi
560
561
  |
561
562
  extendForEntity[ $art ]
@@ -753,17 +754,6 @@ extendType[ art, outer ] locals[ name = {} ]
753
754
  extendWithOptElements[ $art, $art ]
754
755
  ;
755
756
 
756
- extendAspect[ art, outer ] locals[ name = {} ]
757
- @after { this.attachLocation( $art ); }
758
- :
759
- // aspects are types, i.e. kind is 'type' for aspects
760
- ASPECT simplePath[ $name, 'Extend' ]
761
- { $art.expectedKind = 'aspect'; $art.name = $name;
762
- this.addItem( $art, $outer, 'extensions', 'extend' );
763
- }
764
- extendWithOptElements[ $art, $art ]
765
- ;
766
-
767
757
  annotationDef[ art, outer ] locals[ name = {} ]
768
758
  @after { this.attachLocation( $art ); }
769
759
  :
@@ -793,6 +783,7 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
793
783
  '{' { $art.elements = this.createDict(); }
794
784
  elementDefOrExtend[ $art ]*
795
785
  '}' { this.finalizeDictOrArray( $art.elements ); }
786
+ { this.checkExtensionDict( $art.elements ); }
796
787
  optionalSemi
797
788
  |
798
789
  requiredSemi
@@ -803,12 +794,13 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
803
794
  // #ATN: DEFINITIONS, COLUMNS, ACTIONS etc are not reserved and could be identifiers (ref).
804
795
  // TODO: exclude "expected" according to disallowElementExtension()
805
796
  (
806
- includeRef[ $art ]
797
+ includeRef[ $art ] ( ',' includeRef[ $art ] )*
807
798
  requiredSemi
808
799
  |
809
800
  '{' { $art.elements = this.createDict(); }
810
801
  elementDefOrExtend[ $art ]*
811
802
  '}' { this.finalizeDictOrArray( $art.elements ); }
803
+ { this.checkExtensionDict( $art.elements ); }
812
804
  optionalSemi
813
805
  |
814
806
  requiredSemi
@@ -834,17 +826,18 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
834
826
  |
835
827
  { this.disallowElementExtension( $elemName, $outer, 'actions' ); }
836
828
  ACTIONS '{' { $art.actions = this.createDict(); }
837
- actionFunctionDef[ $art ]*
829
+ actionFunctionDef[ $art ]* // TODO: no EXTEND in actions? (ok, would just allow annos)
838
830
  '}' { this.finalizeDictOrArray( $art.actions ); }
839
831
  optionalSemi
840
832
  |
841
833
  ELEMENTS '{' { $art.elements = this.createDict(); }
842
834
  elementDefOrExtend[ $art ]*
843
835
  '}' { this.finalizeDictOrArray( $art.elements ); }
836
+ { this.checkExtensionDict( $art.elements ); }
844
837
  optionalSemi
845
838
  |
846
839
  ENUM '{' { $art.enum = this.createDict(); }
847
- enumSymbolDef[ $art ]*
840
+ enumSymbolDef[ $art ]* // TODO: no EXTEND in enum? (ok, would just allow annos)
848
841
  '}' { this.finalizeDictOrArray( $art.enum ); }
849
842
  optionalSemi
850
843
  )
@@ -856,12 +849,13 @@ extendWithOptElements[ art ]
856
849
  WITH { this.noSemicolonHere(); this.docComment( $art ); }
857
850
  annotationAssignment_ll1[ $art ]*
858
851
  (
859
- includeRef[ $art ]
852
+ includeRef[ $art ] ( ',' includeRef[ $art ] )*
860
853
  requiredSemi
861
854
  |
862
855
  '{' { $art.elements = this.createDict(); }
863
856
  elementDefOrExtend[ $art ]*
864
857
  '}' { this.finalizeDictOrArray( $art.elements ); }
858
+ { this.checkExtensionDict( $art.elements ); }
865
859
  optionalSemi
866
860
  |
867
861
  requiredSemi
@@ -873,6 +867,7 @@ extendWithOptElements[ art ]
873
867
  '{' { $art.elements = this.createDict(); }
874
868
  elementDefOrExtend[ $art ]*
875
869
  '}' { this.finalizeDictOrArray( $art.elements ); }
870
+ { this.checkExtensionDict( $art.elements ); }
876
871
  optionalSemi
877
872
  |
878
873
  requiredSemi
@@ -892,11 +887,13 @@ annotateArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
892
887
  '{' { $art.elements = this.createDict(); }
893
888
  annotateElement[ $art ]*
894
889
  '}' { this.finalizeDictOrArray( $art.elements ); }
890
+ { this.checkExtensionDict( $art.elements ); }
895
891
  (
896
892
  ACTIONS
897
893
  '{' { $art.actions = this.createDict(); }
898
894
  annotateAction[ $art ]*
899
895
  '}' { this.finalizeDictOrArray( $art.actions ); }
896
+ { this.checkExtensionDict( $art.actions ); }
900
897
  )?
901
898
  optionalSemi
902
899
  |
@@ -904,6 +901,7 @@ annotateArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
904
901
  '{' { $art.actions = this.createDict(); }
905
902
  annotateAction[ $art ]*
906
903
  '}' { this.finalizeDictOrArray( $art.actions ); }
904
+ { this.checkExtensionDict( $art.actions ); }
907
905
  optionalSemi
908
906
  |
909
907
  '(' { $art.params = this.createDict(); }
@@ -912,11 +910,13 @@ annotateArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
912
910
  annotateParam[ $art ]
913
911
  )*
914
912
  ')' { this.finalizeDictOrArray( $art.params ); }
913
+ { this.checkExtensionDict( $art.params ); }
915
914
  (
916
915
  RETURNS { $art['$'+'syntax'] = 'returns'; }
917
916
  '{' { $art.elements = this.createDict(); }
918
917
  annotateElement[ $art ]*
919
918
  '}' { this.finalizeDictOrArray( $art.elements ); }
919
+ { this.checkExtensionDict( $art.elements ); }
920
920
  optionalSemi
921
921
  |
922
922
  requiredSemi
@@ -926,6 +926,7 @@ annotateArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
926
926
  '{' { $art.elements = this.createDict(); }
927
927
  annotateElement[ $art ]*
928
928
  '}' { this.finalizeDictOrArray( $art.elements ); }
929
+ { this.checkExtensionDict( $art.elements ); }
929
930
  optionalSemi
930
931
 
931
932
  |
@@ -946,6 +947,7 @@ annotateElement[ outer ] locals[ art = {} ]
946
947
  '{' { $art.elements = this.createDict(); }
947
948
  annotateElement[ $art ]*
948
949
  '}' { this.finalizeDictOrArray( $art.elements ); }
950
+ { this.checkExtensionDict( $art.elements ); }
949
951
  optionalSemi
950
952
  |
951
953
  requiredSemi
@@ -968,11 +970,13 @@ annotateAction [ outer ] locals [ art = {} ]
968
970
  annotateParam[ $art ]
969
971
  )*
970
972
  ')' { this.finalizeDictOrArray( $art.params ); }
973
+ { this.checkExtensionDict( $art.params ); }
971
974
  )?
972
975
  (
973
976
  RETURNS '{' { $art.elements = this.createDict(); }
974
977
  annotateElement[ $art ]*
975
978
  '}' { this.finalizeDictOrArray( $art.elements ); }
979
+ { this.checkExtensionDict( $art.elements ); }
976
980
  optionalSemi
977
981
  |
978
982
  requiredSemi
@@ -1543,46 +1547,14 @@ typeSpecSemi[ art ] // with 'includes', for type and annotation defs
1543
1547
  |
1544
1548
  // alt lookahead includes MANY '{'
1545
1549
  { $art.type = {}; }
1550
+ // Can't use typeRefOptArgs because of clash with include rule below (ATN would change)
1546
1551
  simplePath[ $art.type, 'artref' ]
1547
1552
  (
1548
- typeRefArgs[ $art ]
1549
- { this.docComment( $art ); }
1550
- annotationAssignment_ll1[ $art ]*
1551
- (
1552
- ENUM '{' { $art.enum = this.createDict(); }
1553
- enumSymbolDef[ $art ]*
1554
- '}' { this.finalizeDictOrArray( $art.enum ); }
1555
- (
1556
- optionalSemi
1557
- |
1558
- defaultValue[ $art ]
1559
- requiredSemi
1560
- )
1561
- |
1562
- defaultValue[ $art ]?
1563
- requiredSemi
1564
- )
1565
- |
1566
- ':' // with element, e.g. `type T : E:elem enum { ... }`
1567
- { $art.type.scope = $art.type.path.length; }
1568
- simplePath[ $art.type, 'ref']
1569
- { this.docComment( $art ); }
1570
- annotationAssignment_ll1[ $art ]*
1571
- (
1572
- ENUM '{' { $art.enum = this.createDict(); }
1573
- enumSymbolDef[ $art ]*
1574
- '}' { this.finalizeDictOrArray( $art.enum ); }
1575
- (
1576
- optionalSemi
1577
- |
1578
- defaultValue[ $art ]
1579
- requiredSemi
1580
- )
1581
- |
1582
- defaultValue[ $art ]?
1583
- requiredSemi
1584
- )
1585
- |
1553
+ ( typeRefArgs[ $art ]
1554
+ | ':' // with element, e.g. `type T : E:elem enum { ... }`
1555
+ { $art.type.scope = $art.type.path.length; }
1556
+ simplePath[ $art.type, 'ref']
1557
+ )?
1586
1558
  { this.docComment( $art ); }
1587
1559
  annotationAssignment_ll1[ $art ]*
1588
1560
  (
@@ -1831,7 +1803,7 @@ typeNamedArg[ art ] locals[ arg = '' ]
1831
1803
  :
1832
1804
  name=ident['paramname']
1833
1805
  ':'
1834
- { if (this.checkTypeFacet( $art, $name.id ))
1806
+ { if ($name.id && this.checkTypeFacet( $art, $name.id ))
1835
1807
  $arg = $name.id.id;
1836
1808
  }
1837
1809
  (
@@ -1868,10 +1840,10 @@ queryExpression returns[ query ] // QLSubqueryComplex, SubqueryComplex
1868
1840
  | op=MINUS q=DISTINCT?
1869
1841
  )
1870
1842
  qt=queryTerm
1871
- { $query = this.leftAssocBinaryOp( $query, $op, $q, $qt.query );; $ctx.q = null; }
1843
+ { if ($qt.query) $query = this.leftAssocBinaryOp( $query, $op, $q, $qt.query );; $ctx.q = null; }
1872
1844
  )*
1873
- ( ob=orderByClause[ $query ] { $query = $ob.query; } ) ?
1874
- ( lc=limitClause[ $query ] { $query = $lc.query; } ) ?
1845
+ ( ob=orderByClause[ $query ] { if ($ob.query) $query = $ob.query; } ) ?
1846
+ ( lc=limitClause[ $query ] { if ($lc.query) $query = $lc.query; } ) ?
1875
1847
  ;
1876
1848
 
1877
1849
  orderByClause[ inQuery ] returns [ query ]
@@ -2075,7 +2047,7 @@ tableExpression returns[ table ] // TableOrJoin
2075
2047
  ON cond=condition { $table.on = $cond.cond; }
2076
2048
  |
2077
2049
  crj=CROSS jn=JOIN tt=tableTerm
2078
- { $table = this.leftAssocBinaryOp( $table, $jn, $crj, $tt.table, 'join' ); }
2050
+ { if (!$table) { $table = {}; } $table = this.leftAssocBinaryOp( $table, $jn, $crj, $tt.table, 'join' ); }
2079
2051
  )*
2080
2052
  ;
2081
2053
 
@@ -2257,9 +2229,11 @@ conditionTerm returns [ cond ]
2257
2229
  |
2258
2230
  { $cond = { args: [ $expr.expr ] }; }
2259
2231
  NOT predicate[ $cond, true ]
2232
+ { if (!$cond.op) $cond = null; } // predicate failed to parse, avoid subseqential errors
2260
2233
  |
2261
2234
  { $cond = { args: [ $expr.expr ] }; }
2262
2235
  predicate[ $cond, false ]
2236
+ { if (!$cond.op) $cond = null; } // predicate failed to parse, avoid subseqential errors
2263
2237
  )? // optional: for conditions in parentheses
2264
2238
  ;
2265
2239
 
@@ -2464,7 +2438,12 @@ pathArguments[ pathStep, considerSpecial ]
2464
2438
  '(' // dict or array, see below
2465
2439
  // Make sure that we do not introduce A:B paths in expressions!
2466
2440
  // Need to avoid adaptPredict(), otherwise Generic keywords won't work in funcExpression
2467
- { this.setLocalTokenForId( { ':': 'HelperToken1', '=>': 'HelperToken2' } ); }
2441
+ //
2442
+ // For code completion, we need to handle generic tokens directly after the
2443
+ // '('. To avoid invalidating an assoc `trim` to an entity with parameter
2444
+ // `leading` (ok, a bit constructed), we do not do it with named parameters.
2445
+ { if (!this.setLocalTokenForId( { ':': 'HelperToken1', '=>': 'HelperToken2' } ))
2446
+ this.prepareGenericKeywords( $considerSpecial ); }
2468
2447
  (
2469
2448
  { $pathStep.args = this.createDict(); $pathStep['$'+'syntax'] = ':'; }
2470
2449
  id=HelperToken1 ':'
@@ -2687,10 +2666,7 @@ annoValueBase[ assignment ] locals [ seenEllipsis = false ]
2687
2666
  flattenedValue[ assignment ] locals[ val = { name: {} } ]
2688
2667
  :
2689
2668
  at='@'? annotationPath[ $val.name, 'name', $at ]
2690
- (
2691
- '#' { this.meltKeywordToIdentifier(); }
2692
- variant=ident['variant'] { $val.name.variant = $variant.id; }
2693
- )?
2669
+ ( annotationPathVariant[ $val.name ] )?
2694
2670
  (
2695
2671
  ':' { this.meltKeywordToIdentifier(true); } // allow path as anno value start with reserved
2696
2672
  annoValue[ $val ]
@@ -2739,10 +2715,7 @@ annoSubValue returns[ val = {} ]
2739
2715
  { Object.assign( $val, this.numberLiteral( $num, $plus||$min ) ); }
2740
2716
  |
2741
2717
  at='@'? annotationPath[ $val, 'ref', $at ]
2742
- (
2743
- '#' { this.meltKeywordToIdentifier(); }
2744
- variant=ident['variant'] { $val.variant = $variant.id; }
2745
- )?
2718
+ ( annotationPathVariant[ $val ] )?
2746
2719
  ;
2747
2720
 
2748
2721
  literalValue returns[ val ] locals[ tok ]
@@ -2800,12 +2773,12 @@ annotationPath[ art, category, headat = null ] locals[ _sync = 'nop' ]
2800
2773
  )*
2801
2774
  ;
2802
2775
 
2803
- annotationPathVariant[ art ]
2776
+ annotationPathVariant[ art ] locals[ variant = {} ]
2804
2777
  @after { this.attachLocation($art); }
2805
2778
  :
2806
2779
  // TODO: warning for space after '#'
2807
2780
  '#' { this.meltKeywordToIdentifier(); }
2808
- variant=ident['variant'] { $art.variant = $variant.id; }
2781
+ simplePath[ $variant, 'variant' ] { $art.variant = $variant; }
2809
2782
  ;
2810
2783
 
2811
2784
  // Identifier and non-reserved keywords --------------------------------------
package/lib/main.d.ts CHANGED
@@ -896,6 +896,7 @@ declare namespace compiler {
896
896
  * but not `cds.foundation.*`.
897
897
  *
898
898
  * @param definitionName Top-level definition name of the artifact.
899
+ * @since v2.14.0
899
900
  */
900
901
  function isInReservedNamespace(definitionName: string): boolean;
901
902
  }
package/lib/model/api.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * Each function in `userFunctions` and `defaultFunctions` is called with:
12
12
  * - `userFunctions`
13
13
  * - the current CSN node, i.e. ‹parent node›.‹property name›
14
- * - the the ‹parent node›
14
+ * - the ‹parent node›
15
15
  * - the ‹property name› (might be useful if the same function is used for several props)
16
16
  */
17
17
  const defaultFunctions = {
@@ -210,8 +210,11 @@ const referenceSemantics = {
210
210
  ref_where: { lexical: justDollar , dynamic: 'ref-target'}, // ...using baseEnv
211
211
  on: { lexical: justDollar, dynamic: 'query' }, // assoc defs, redirected to
212
212
  // there are also 'on_join' and 'on_mixin' with default semantics
213
- orderBy: { lexical: query => query, dynamic: 'query' },
214
- orderBy_set: { lexical: query => query.$next, dynamic: 'query' }, // to outer SELECT (from UNION)
213
+ orderBy_ref: { lexical: query => query, dynamic: 'query' },
214
+ orderBy_expr: { lexical: query => query, dynamic: 'source' }, // ref in ORDER BY expression
215
+ orderBy_set_ref: { lexical: query => query.$next, dynamic: 'query' }, // to outer SELECT (from UNION)
216
+ // refs in ORDER BY expr in UNION not really allowed - only with table alias (of outer queries) or $self
217
+ orderBy_set_expr: { lexical: query => query.$next, dynamic: false },
215
218
  // default: { lexical: query => query, dynamic: 'source' }
216
219
  }
217
220
 
@@ -551,37 +554,39 @@ function csnRefs( csn, universalReady ) {
551
554
  }
552
555
  }
553
556
  // now the dynamic environment: ------------------------------------------
554
- if (semantics.dynamic === 'target') { // ref in keys
555
- const target = assocTarget( parent, refCtx );
556
- return resolvePath( path, target.elements[head], target, 'target' );
557
- }
558
- if (baseEnv) // ref-target (filter condition), expand, inline
559
- return resolvePath( path, baseEnv.elements[head], baseEnv, semantics.dynamic );
560
- if (!query) { // outside queries - TODO: items?
561
- let art = parent.elements[head];
562
- // Ref to up_ in anonymous aspect
563
- if (!art && head === 'up_') {
564
- const up = getCache( parent, '_parent' );
565
- const target = up && typeof up.target === 'string' && csn.definitions[up.target];
566
- if (target && target.elements) {
567
- initDefinition( target );
568
- art = target.elements.up_;
557
+ if (semantics.dynamic !== false) {
558
+ if (semantics.dynamic === 'target') { // ref in keys
559
+ const target = assocTarget( parent, refCtx );
560
+ return resolvePath( path, target.elements[head], target, 'target' );
561
+ }
562
+ if (baseEnv) // ref-target (filter condition), expand, inline
563
+ return resolvePath( path, baseEnv.elements[head], baseEnv, semantics.dynamic );
564
+ if (!query) { // outside queries - TODO: items?
565
+ let art = parent.elements[head];
566
+ // Ref to up_ in anonymous aspect
567
+ if (!art && head === 'up_') {
568
+ const up = getCache( parent, '_parent' );
569
+ const target = up && typeof up.target === 'string' && csn.definitions[up.target];
570
+ if (target && target.elements) {
571
+ initDefinition( target );
572
+ art = target.elements.up_;
573
+ }
569
574
  }
575
+ return resolvePath( path, art, parent, 'parent' );
570
576
  }
571
- return resolvePath( path, art, parent, 'parent' );
572
- }
573
577
 
574
- if (semantics.dynamic === 'query')
575
- // TODO: for ON condition in expand, would need to use cached _element
576
- return resolvePath( path, qcache.elements[head], null, 'query' );
577
- for (const name in qcache.$aliases) {
578
- const alias = qcache.$aliases[name];
579
- const found = alias.elements[head];
580
- if (found)
581
- return resolvePath( path, found, alias._ref, 'source', name )
578
+ if (semantics.dynamic === 'query')
579
+ // TODO: for ON condition in expand, would need to use cached _element
580
+ return resolvePath( path, qcache.elements[head], null, 'query' );
581
+ for (const name in qcache.$aliases) {
582
+ const alias = qcache.$aliases[name];
583
+ const found = alias.elements[head];
584
+ if (found)
585
+ return resolvePath( path, found, alias._ref, 'source', name )
586
+ }
582
587
  }
583
588
  // console.log(query.SELECT,qcache,qcache.$next,main)
584
- throw new ModelError ( `Path item ${ 0 }=${ head } refers to nothing, refCtx: ${ refCtx }` );
589
+ throw new ModelError ( `Path item 0=${ head } refers to nothing, refCtx: ${ refCtx }` );
585
590
  }
586
591
 
587
592
  /**
@@ -907,6 +912,7 @@ function startCsnPath( csnPath, csn ) {
907
912
  /**
908
913
  * @param {CSN.Path} csnPath
909
914
  * @param {CSN.Model} csn
915
+ * @param {any} resolve
910
916
  */
911
917
  function analyseCsnPath( csnPath, csn, resolve ) {
912
918
  /** @type {object} */
@@ -932,6 +938,15 @@ function analyseCsnPath( csnPath, csn, resolve ) {
932
938
  parent = art;
933
939
  art = obj[prop];
934
940
  }
941
+ else if (refCtx === 'orderBy') {
942
+ const isSelect = isSelectQuery( query );
943
+ // use _query_ elements with direct refs (consider sub-optimal CSN,
944
+ // representation of the CAST function), otherwise source elements:
945
+ if (obj[prop].ref && !obj[prop].cast)
946
+ refCtx = (isSelect ? 'orderBy_ref' : 'orderBy_set_ref');
947
+ else
948
+ refCtx = (isSelect ? 'orderBy_expr' : 'orderBy_set_expr');
949
+ }
935
950
  isName = false;
936
951
  }
937
952
  else if (artifactProperties.includes( String(prop) )) {
@@ -982,7 +997,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
982
997
  refCtx = prop;
983
998
  }
984
999
  else if (prop === 'orderBy') {
985
- refCtx = (query.SET ? 'orderBy_set' : 'orderBy');
1000
+ refCtx = 'orderBy';
986
1001
  }
987
1002
  else if (prop !== 'xpr') {
988
1003
  refCtx = prop;
@@ -995,6 +1010,18 @@ function analyseCsnPath( csnPath, csn, resolve ) {
995
1010
  return resolve( obj, refCtx, main, query, parent, baseEnv );
996
1011
  }
997
1012
 
1013
+ // A SELECT which is (unnecessarily) put into parentheses, the CSN
1014
+ // representation uses SET without `op` and args of length 1:
1015
+ function isSelectQuery( query ) {
1016
+ while (query.SET) {
1017
+ const { args } = query.SET;
1018
+ if (args.length !== 1)
1019
+ return false;
1020
+ query = args[0];
1021
+ }
1022
+ return true;
1023
+ }
1024
+
998
1025
  module.exports = {
999
1026
  csnRefs,
1000
1027
  traverseQuery,
@@ -492,15 +492,18 @@ function getUtils(model, universalReady) {
492
492
 
493
493
  /**
494
494
  * Resolve to the final type of a type, that means follow type chains, references, etc.
495
- * Input is a type name, i.e. string, or type ref, i.e. `{ ref: [...] }`.
495
+ * Input is a fully qualified type name, i.e. string, or type ref, i.e. `{ ref: [...] }`.
496
496
  *
497
497
  * Returns `null` if the type can't be resolved or if the referenced element has no type,
498
498
  * e.g. `typeof V:calculated`.
499
499
  * Otherwise, if scalar, returns an object that has a `type` property and all collected type
500
500
  * properties, or the type object with `elements` or `items` property if structured/arrayed.
501
501
  *
502
- * Caches type lookups. If the CSN changes drastically, you will need to re-call `csnUtils()`
503
- * and use the newly returned `getFinalBaseTypeWithProps()`.
502
+ * Notes:
503
+ * - Caches type lookups. If the CSN changes drastically, you will need to re-call
504
+ * `csnUtils()` and use the newly returned `getFinalBaseTypeWithProps()`.
505
+ * - Does _not_ return the underlying type definition! It is an object with all relevant
506
+ * type properties collected while traversing the type chain!
504
507
  *
505
508
  * @param {string|object} type Type as string or type ref, i.e. `{ ref: [...] }`
506
509
  * @returns {object|null}
@@ -517,7 +520,7 @@ function getUtils(model, universalReady) {
517
520
 
518
521
  // We differentiate between ref and type to avoid collisions due to dict key.
519
522
  // Delimiter chosen arbitrarily; just one that is rarely used.
520
- const resolvedKey = (typeof type === 'object') ? `ref:${type.ref.join('\\')}` : `type:${type}`;
523
+ const resolvedKey = (typeof type === 'object') ? `ref[${type.ref.length}]:${type.ref.join('\\')}` : `type:${type}`;
521
524
 
522
525
  if (finalBaseTypeCache[resolvedKey]) {
523
526
  if (finalBaseTypeCache[resolvedKey] === true)
@@ -786,10 +789,12 @@ function forEachGeneric( construct, prop, callback, path = [], iterateOptions =
786
789
  executeCallbacks( dictObj, name );
787
790
  }
788
791
  function executeCallbacks(o, name ) {
792
+ const p = iterateOptions.pathWithoutProp ? [ name ] : [ prop, name ];
793
+
789
794
  if (Array.isArray(callback))
790
- callback.forEach(cb => cb( o, name, prop, path.concat([prop, name]), construct ));
795
+ callback.forEach(cb => cb( o, name, prop, path.concat(p), construct ));
791
796
  else
792
- callback( o, name, prop, path.concat([prop, name]), construct )
797
+ callback( o, name, prop, path.concat(p), construct )
793
798
  }
794
799
  }
795
800
 
@@ -1056,7 +1061,7 @@ function isValidMappingDialectCombi(sqlDialect, sqlMapping) {
1056
1061
  */
1057
1062
  // eslint-disable-next-line no-unused-vars
1058
1063
  function getElementDatabaseNameOf(elemName, sqlMapping, sqlDialect='plain') {
1059
- isValidMappingDialectCombi(sqlMapping, sqlDialect)
1064
+ isValidMappingDialectCombi(sqlDialect, sqlMapping)
1060
1065
  if (sqlMapping === 'hdbcds') {
1061
1066
  return elemName;
1062
1067
  }
@@ -1280,19 +1285,29 @@ function copyAnnotationsAndDoc(fromNode, toNode, overwrite = false) {
1280
1285
  * @todo Does _not_ apply param/action/... annotations.
1281
1286
  *
1282
1287
  * @param {CSN.Model} csn
1283
- * @param {{overwrite?: boolean, filter?: (name: string) => boolean}} config
1288
+ * @param {{notFound?: (name: string, index: number) => void, override?: boolean, filter?: (name: string) => boolean}} config
1289
+ * notFound: Function that is called if the referenced definition can't be found.
1290
+ * Second argument is index in `csn.extensions` array.
1291
+ * override: Whether to ignore existing annotations.
1292
+ * filter: Positive filter. If it returns true, annotations for the referenced artifact
1293
+ * will be applied.
1284
1294
  */
1285
1295
  function applyAnnotationsFromExtensions(csn, config) {
1286
1296
  if (!csn.extensions)
1287
1297
  return;
1288
1298
 
1289
1299
  const filter = config.filter || ((_name) => true);
1290
- for (const ext of csn.extensions) {
1300
+ for (let i = 0; i < csn.extensions.length; ++i) {
1301
+ const ext = csn.extensions[i];
1291
1302
  const name = ext.annotate || ext.extend;
1292
- const def = csn.definitions[name];
1293
- if (name && def && filter(name)) {
1294
- copyAnnotationsAndDoc(ext, def, config.overwrite);
1295
- applyAnnotationsToElements(ext, def);
1303
+ if (name && filter(name)) {
1304
+ const def = csn.definitions[name];
1305
+ if (def) {
1306
+ copyAnnotationsAndDoc(ext, def, config.override);
1307
+ applyAnnotationsToElements(ext, def);
1308
+ } else if (config.notFound) {
1309
+ config.notFound(name, i);
1310
+ }
1296
1311
  }
1297
1312
  }
1298
1313
 
@@ -1309,7 +1324,7 @@ function applyAnnotationsFromExtensions(csn, config) {
1309
1324
  forEach(ext.elements, (key, sourceElem) => {
1310
1325
  const targetElem = def.elements[key];
1311
1326
  if (targetElem) {
1312
- copyAnnotationsAndDoc(sourceElem, targetElem, config.overwrite);
1327
+ copyAnnotationsAndDoc(sourceElem, targetElem, config.override);
1313
1328
  applyAnnotationsToElements(sourceElem, targetElem);
1314
1329
  }
1315
1330
  });
@@ -58,7 +58,6 @@ function tableAliasAsLink( art, parent, name ) {
58
58
  *
59
59
  * @param {XSN.Model} model
60
60
  * @param {string} [nameOrPath]
61
- * @returns {string}
62
61
  */
63
62
  function revealInternalProperties( model, nameOrPath ) {
64
63
  const transformers = {
@@ -131,7 +130,10 @@ function revealInternalProperties( model, nameOrPath ) {
131
130
 
132
131
  path = path.split('/');
133
132
  if (path.length === 1) {
134
- return reveal( xsn.definitions[path] || xsn.vocabularies && xsn.vocabularies[path] );
133
+ const def = xsn.definitions?.[path[0]] || xsn.vocabularies?.[path[0]];
134
+ if (!def)
135
+ throw new Error(`reveal xsn: Unknown definition: “${path[0]}”`)
136
+ return reveal( def );
135
137
  }
136
138
 
137
139
  // with the code below, we might miss the right transformer function
@@ -296,8 +298,8 @@ function revealInternalProperties( model, nameOrPath ) {
296
298
  function array( node, fn ) {
297
299
  const r = node.map( n => fn( n, node ) );
298
300
  if (node[$location])
299
- r.push( { $location: locationString( node[$location] ) } );
300
- return r;
301
+ r.push( { '[$location]': locationString( node[$location] ) } );
302
+ return (node.$prefix) ? [ { $prefix: node.$prefix }, ...node ] : r;
301
303
  }
302
304
 
303
305
  function artifactIdentifier( node, parent ) {
@@ -169,6 +169,9 @@ function getElementComparator(otherArtifact, addedElementsDict = null, changedEl
169
169
  }
170
170
  if (relevantTypeChange(element.type, otherElement.type) || typeParametersChanged(element, otherElement)) {
171
171
  // Type or parameters, e.g. association target, changed.
172
+ if(otherElement.notNull && element.notNull === undefined) {
173
+ element.$notNull = false; // Explictily set notNull to the implicit default so we render the correct ALTER
174
+ }
172
175
  changedElementsDict[name] = changedElement(element, otherElement);
173
176
  }
174
177