@sap/cds-compiler 6.3.6 → 6.4.6

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 (62) hide show
  1. package/CHANGELOG.md +101 -3
  2. package/LICENSE +32 -0
  3. package/README.md +14 -2
  4. package/bin/cdsse.js +0 -3
  5. package/doc/CHANGELOG_BETA.md +1 -1
  6. package/doc/CHANGELOG_DEPRECATED.md +1 -1
  7. package/lib/base/message-registry.js +9 -2
  8. package/lib/base/messages.js +1 -1
  9. package/lib/base/model.js +2 -0
  10. package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
  11. package/lib/checks/existsMustEndInAssoc.js +1 -1
  12. package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
  13. package/lib/checks/validator.js +4 -2
  14. package/lib/compiler/assert-consistency.js +3 -2
  15. package/lib/compiler/builtins.js +5 -6
  16. package/lib/compiler/checks.js +37 -26
  17. package/lib/compiler/define.js +1 -1
  18. package/lib/compiler/extend.js +39 -50
  19. package/lib/compiler/finalize-parse-cdl.js +1 -1
  20. package/lib/compiler/lsp-api.js +1 -1
  21. package/lib/compiler/populate.js +2 -2
  22. package/lib/compiler/propagator.js +29 -6
  23. package/lib/compiler/resolve.js +13 -3
  24. package/lib/compiler/shared.js +157 -133
  25. package/lib/compiler/tweak-assocs.js +87 -29
  26. package/lib/compiler/xpr-rewrite.js +164 -160
  27. package/lib/edm/annotations/edmJson.js +206 -37
  28. package/lib/edm/csn2edm.js +13 -0
  29. package/lib/edm/edmUtils.js +2 -2
  30. package/lib/gen/BaseParser.js +106 -72
  31. package/lib/gen/CdlGrammar.checksum +1 -1
  32. package/lib/gen/CdlParser.js +1501 -1509
  33. package/lib/json/to-csn.js +8 -5
  34. package/lib/language/genericAntlrParser.js +0 -0
  35. package/lib/main.js +19 -16
  36. package/lib/model/csnRefs.js +589 -521
  37. package/lib/model/csnUtils.js +8 -5
  38. package/lib/model/enrichCsn.js +1 -0
  39. package/lib/parsers/AstBuildingParser.js +73 -28
  40. package/lib/render/toCdl.js +2 -1
  41. package/lib/render/toHdbcds.js +6 -3
  42. package/lib/render/toSql.js +5 -0
  43. package/lib/transform/db/applyTransformations.js +1 -1
  44. package/lib/transform/db/assertUnique.js +4 -1
  45. package/lib/transform/db/assocsToQueries/transformExists.js +3 -10
  46. package/lib/transform/db/assocsToQueries/utils.js +0 -5
  47. package/lib/transform/db/cdsPersistence.js +17 -18
  48. package/lib/transform/db/expansion.js +179 -3
  49. package/lib/transform/db/flattening.js +16 -5
  50. package/lib/transform/db/rewriteCalculatedElements.js +79 -283
  51. package/lib/transform/effective/main.js +8 -1
  52. package/lib/transform/forOdata.js +1 -1
  53. package/lib/transform/forRelationalDB.js +21 -80
  54. package/lib/transform/localized.js +75 -127
  55. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
  56. package/lib/transform/transformUtils.js +23 -21
  57. package/lib/transform/translateAssocsToJoins.js +7 -5
  58. package/lib/transform/tupleExpansion.js +16 -3
  59. package/package.json +3 -3
  60. package/doc/DeprecatedOptions_v2.md +0 -150
  61. package/doc/NameResolution.md +0 -837
  62. package/lib/transform/parseExpr.js +0 -415
@@ -5,6 +5,7 @@
5
5
  const {
6
6
  forEachGeneric,
7
7
  forEachInOrder,
8
+ isBetaEnabled,
8
9
  } = require('../base/model');
9
10
  const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
10
11
 
@@ -90,7 +91,7 @@ function tweakAssocs( model ) {
90
91
 
91
92
  if (art.query) {
92
93
  traverseQueryPost(art.query, false, (query) => {
93
- forEachGeneric( query, 'elements', rewriteAssociationCheck );
94
+ forEachGeneric( query, 'elements', handleQueryElements );
94
95
  });
95
96
  }
96
97
  }
@@ -116,10 +117,10 @@ function tweakAssocs( model ) {
116
117
  return;
117
118
  const loc = [ elem.target.location, elem ];
118
119
  const main = elem._main || elem;
119
- if (!elem.$inferred && !main.$inferred) {
120
+ if (!elem.$inferred && !main.$inferred && !model.options.$recompile) {
120
121
  info( 'assoc-target-not-in-service', loc,
121
122
  { target, '#': (elem._main.query ? 'select' : 'define') }, {
122
- std: 'Target $(TARGET) of association is outside any service', // not used
123
+ std: 'Association target $(TARGET) is outside any service', // not used
123
124
  define: 'Target $(TARGET) of explicitly defined association is outside any service',
124
125
  select: 'Target $(TARGET) of explicitly selected association is outside any service',
125
126
  } );
@@ -135,6 +136,26 @@ function tweakAssocs( model ) {
135
136
  }
136
137
  }
137
138
 
139
+ function handleQueryElements( column ) {
140
+ rewriteAssociationCheck( column );
141
+ if (!isBetaEnabled( model.options, '$calcForDraft' ))
142
+ return;
143
+ const { value } = column; // `value` = column expression
144
+ if (!value || !value.args && !value.suffix)
145
+ return;
146
+ // TODO: what about non-simple refs (assocs, even with filter/args)?
147
+
148
+ // with “real” expressions, set $calc according to these
149
+ // (with references, $calc might be inherited from the source element)
150
+ column.$calc = copyExpr( column.value, null ); // copy while keeping location
151
+
152
+ if (traverseExpr.STOP ===
153
+ traverseExpr( column.$calc, 'rewrite-on', column,
154
+ ref => rewriteColumnPath( ref, column ) ))
155
+ column.$calc = true;
156
+ }
157
+
158
+ // Check explicit ON / keys with REDIRECTED TO
138
159
  function rewriteAssociationCheck( element ) {
139
160
  const elem = element.items || element; // TODO v6: nested items
140
161
  if (elem.elements)
@@ -436,7 +457,7 @@ function tweakAssocs( model ) {
436
457
  return;
437
458
 
438
459
  if (elem._parent?.kind === 'element') {
439
- // managed association as sub element not supported yet
460
+ // unmanaged association as sub element not supported yet
440
461
  // TODO: Only report once for multi-include chains, see
441
462
  // Associations/SubElements/UnmanagedInSubElement.err.cds
442
463
  error( 'type-unsupported-rewrite', [ elem.location, elem ], { '#': 'sub-element' } );
@@ -732,6 +753,7 @@ function tweakAssocs( model ) {
732
753
  rewritePathForEnv( expr, navEnv, assoc );
733
754
  }
734
755
  else if (assoc._main.query) { // from ON cond of mixin element in query
756
+ // here also $calc
735
757
  const root = expr.path[0]._navigation || expr.path[0]._artifact;
736
758
  if (expr.scope === 'param' || root?.kind === '$parameters') {
737
759
  if (assoc.$errorReported !== 'assoc-unexpected-scope') {
@@ -743,7 +765,8 @@ function tweakAssocs( model ) {
743
765
  }
744
766
  return;
745
767
  }
746
- if (expr.path[0]._navigation) { // rewrite src elem, mixin, $self[.elem]
768
+
769
+ if (expr.path[0]._navigation) { // rewrite: src elem, mixin, $self[.elem]
747
770
  const nav = pathNavigation( expr );
748
771
  const elem = (assoc._origin === root) ? assoc : navProjection( nav.navigation, assoc );
749
772
  // TODO: Use rewritePathForEnv(); make it handle mixins
@@ -823,8 +846,11 @@ function tweakAssocs( model ) {
823
846
 
824
847
  for (let i = startIndex; i < ref.path.length; ++i) {
825
848
  if (i > startIndex && art.target) {
826
- // if the current artifact is an association, we need to respect the redirection
827
- // chain from original target to new one.
849
+ // TODO: Can we combine this with the code from xpr-rewrite.js
850
+ // If the current artifact is an association, we need to respect the redirection
851
+ // chain from original target to new one. We need to use '_originalArtifact' due
852
+ // to secondary associations and their redirection chains. See comment in
853
+ // test3/Redirections/SecondaryAssocs/RedirectedPathRewriteOne.cds
828
854
  // FIXME: Won't work with associations in projected structures.
829
855
  const origTarget = ref.path[i - 1]?._originalArtifact?.target?._artifact;
830
856
  const chain = cachedRedirectionChain( art, origTarget );
@@ -914,6 +940,33 @@ function tweakAssocs( model ) {
914
940
  }
915
941
  }
916
942
 
943
+ /**
944
+ * Rewrite the reference `ref` with first elem/mixin ref item `item` for user
945
+ * `assoc` (the query element), `elem` is the first (or preferred) query element
946
+ * for item.
947
+ */
948
+ function rewriteColumnPath( ref, column ) {
949
+ if (!ref._artifact)
950
+ return null;
951
+ const root = ref.path?.[0];
952
+ const nav = pathNavigation( ref );
953
+ if (nav.navigation) { // TabAlias.elem, elem, mixin
954
+ const elem = navProjection( nav.navigation, null );
955
+ // TODO?: Use rewritePathForEnv(); make it handle mixins?
956
+ if (rewritePath( ref, nav.item, column, elem, null ))
957
+ return traverseExpr.STOP;
958
+ }
959
+ else if (ref.scope === 'param' || root?.kind === '$parameters') {
960
+ return traverseExpr.STOP;
961
+ }
962
+ return null;
963
+ }
964
+
965
+ /**
966
+ * Rewrite the reference `ref` with first elem/mixin ref item `item` for user
967
+ * `assoc` (the query element), `elem` is the first (or preferred) query element
968
+ * for item.
969
+ */
917
970
  function rewritePath( ref, item, assoc, elem, location ) {
918
971
  const { path } = ref;
919
972
  const root = path[0];
@@ -928,9 +981,9 @@ function tweakAssocs( model ) {
928
981
  delete root._navigation;
929
982
  setArtifactLink( root, elem );
930
983
  setArtifactLink( ref, elem );
931
- return;
984
+ return true; // ERROR
932
985
  }
933
- if (item !== root) {
986
+ if (item !== root) { // TableAlias.item, $self.item
934
987
  // e.g. mixin ON-condition: Base.foo -> $self.foo or multi-path projection,
935
988
  // $projection -> $self
936
989
  root.id = '$self';
@@ -948,7 +1001,7 @@ function tweakAssocs( model ) {
948
1001
  setLink( root, '_navigation', elem );
949
1002
  }
950
1003
  if (!elem.name) // nothing to do for own $projection, $projection.elem
951
- return; // (except having it renamed to $self)
1004
+ return false; // (except having it renamed to $self)
952
1005
  item.id = elem.name.id;
953
1006
  let state = null;
954
1007
  for (const i of path) {
@@ -964,6 +1017,7 @@ function tweakAssocs( model ) {
964
1017
  }
965
1018
  if (state !== true)
966
1019
  setArtifactLink( ref, state );
1020
+ return false;
967
1021
  }
968
1022
 
969
1023
  function prependSelfToPath( path, elem ) {
@@ -1049,24 +1103,6 @@ function tweakAssocs( model ) {
1049
1103
  }
1050
1104
  }
1051
1105
 
1052
- function navProjection( navigation, preferred ) {
1053
- // TODO: Info if more than one possibility?
1054
- if (!navigation)
1055
- return {};
1056
-
1057
- if (!navigation._projections && !navigation._complexProjections)
1058
- return null;
1059
-
1060
- // _complexProjections contains projections that are not "simple",
1061
- // i.e. contain a filter or arguments. Only used if it contains our
1062
- // preferred association.
1063
- if (preferred && ( navigation._complexProjections?.includes( preferred ) ||
1064
- navigation._projections?.includes( preferred )))
1065
- return preferred;
1066
-
1067
- return navigation._projections?.[0] || null;
1068
- }
1069
-
1070
1106
  function findRewriteTarget( expr, index, env, user ) {
1071
1107
  if (env.kind === '$navElement' || env.kind === '$tableAlias') {
1072
1108
  const r = firstProjectionForPath( expr.path, index, env, user );
@@ -1216,7 +1252,7 @@ function followNavigationPath( path, nav ) {
1216
1252
  * - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
1217
1253
  * - $self -> { item: undefined, tableAlias: $self }
1218
1254
  * - $parameters.P, :P -> {}
1219
- * - $now, current_date -> {}
1255
+ * - $now -> {}
1220
1256
  * - undef, redef -> {}
1221
1257
  * With 'navigation': store that navigation._artifact is projected
1222
1258
  * With 'navigation': rewrite its ON condition
@@ -1245,4 +1281,26 @@ function pathNavigation( ref ) {
1245
1281
  return { navigation: root.elements[item.id], item, tableAlias: root };
1246
1282
  }
1247
1283
 
1284
+ /**
1285
+ * Return the first (or preferred) query elements which projections the navigation
1286
+ * element `navigation` (i.e. source element belonging to a specific table alias).
1287
+ */
1288
+ function navProjection( navigation, preferred ) {
1289
+ // TODO: Info if more than one possibility?
1290
+ if (!navigation)
1291
+ return {};
1292
+
1293
+ if (!navigation._projections && !navigation._complexProjections)
1294
+ return null;
1295
+
1296
+ // _complexProjections contains projections that are not "simple",
1297
+ // i.e. contain a filter or arguments. Only used if it contains our
1298
+ // preferred association.
1299
+ if (preferred && ( navigation._complexProjections?.includes( preferred ) ||
1300
+ navigation._projections?.includes( preferred )))
1301
+ return preferred;
1302
+
1303
+ return navigation._projections?.[0] || null;
1304
+ }
1305
+
1248
1306
  module.exports = tweakAssocs;