@sap/cds-compiler 3.6.2 → 3.8.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 (89) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +12 -5
  4. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  5. package/doc/CHANGELOG_BETA.md +35 -2
  6. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  7. package/doc/DeprecatedOptions_v2.md +1 -1
  8. package/doc/NameResolution.md +1 -1
  9. package/lib/api/main.js +63 -23
  10. package/lib/api/options.js +1 -0
  11. package/lib/api/validate.js +5 -0
  12. package/lib/base/dictionaries.js +15 -3
  13. package/lib/base/keywords.js +2 -0
  14. package/lib/base/message-registry.js +120 -34
  15. package/lib/base/messages.js +51 -27
  16. package/lib/base/model.js +4 -2
  17. package/lib/base/shuffle.js +2 -1
  18. package/lib/checks/arrayOfs.js +1 -1
  19. package/lib/checks/defaultValues.js +1 -1
  20. package/lib/checks/elements.js +29 -1
  21. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/onConditions.js +15 -9
  25. package/lib/checks/sql-snippets.js +2 -2
  26. package/lib/checks/types.js +5 -1
  27. package/lib/checks/validator.js +7 -3
  28. package/lib/compiler/assert-consistency.js +42 -26
  29. package/lib/compiler/base.js +50 -4
  30. package/lib/compiler/builtins.js +17 -8
  31. package/lib/compiler/checks.js +241 -246
  32. package/lib/compiler/define.js +113 -146
  33. package/lib/compiler/extend.js +889 -383
  34. package/lib/compiler/finalize-parse-cdl.js +5 -58
  35. package/lib/compiler/index.js +1 -1
  36. package/lib/compiler/kick-start.js +7 -8
  37. package/lib/compiler/populate.js +297 -293
  38. package/lib/compiler/propagator.js +27 -18
  39. package/lib/compiler/resolve.js +146 -463
  40. package/lib/compiler/shared.js +36 -79
  41. package/lib/compiler/tweak-assocs.js +30 -28
  42. package/lib/compiler/utils.js +31 -5
  43. package/lib/edm/annotations/genericTranslation.js +131 -59
  44. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  45. package/lib/edm/csn2edm.js +22 -5
  46. package/lib/edm/edm.js +6 -4
  47. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  48. package/lib/edm/edmPreprocessor.js +42 -26
  49. package/lib/gen/Dictionary.json +38 -2
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -1
  52. package/lib/gen/languageLexer.js +1 -1
  53. package/lib/gen/languageParser.js +4828 -4472
  54. package/lib/inspect/inspectPropagation.js +20 -34
  55. package/lib/json/from-csn.js +140 -44
  56. package/lib/json/to-csn.js +114 -122
  57. package/lib/language/errorStrategy.js +2 -0
  58. package/lib/language/genericAntlrParser.js +156 -36
  59. package/lib/language/language.g4 +100 -58
  60. package/lib/language/textUtils.js +13 -0
  61. package/lib/main.d.ts +43 -3
  62. package/lib/main.js +4 -2
  63. package/lib/model/csnRefs.js +15 -3
  64. package/lib/model/csnUtils.js +12 -74
  65. package/lib/model/revealInternalProperties.js +4 -2
  66. package/lib/modelCompare/compare.js +2 -1
  67. package/lib/optionProcessor.js +3 -0
  68. package/lib/render/manageConstraints.js +5 -2
  69. package/lib/render/toCdl.js +216 -104
  70. package/lib/render/toHdbcds.js +2 -9
  71. package/lib/render/toRename.js +14 -51
  72. package/lib/render/toSql.js +4 -3
  73. package/lib/render/utils/common.js +9 -5
  74. package/lib/transform/braceExpression.js +6 -0
  75. package/lib/transform/db/assertUnique.js +2 -1
  76. package/lib/transform/db/expansion.js +2 -0
  77. package/lib/transform/db/flattening.js +37 -36
  78. package/lib/transform/db/rewriteCalculatedElements.js +600 -0
  79. package/lib/transform/db/transformExists.js +4 -0
  80. package/lib/transform/db/views.js +40 -37
  81. package/lib/transform/forOdataNew.js +20 -15
  82. package/lib/transform/forRelationalDB.js +58 -41
  83. package/lib/transform/odata/typesExposure.js +50 -15
  84. package/lib/transform/parseExpr.js +16 -8
  85. package/lib/transform/transformUtilsNew.js +42 -14
  86. package/lib/transform/translateAssocsToJoins.js +60 -37
  87. package/lib/transform/universalCsn/coreComputed.js +15 -7
  88. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  89. package/package.json +2 -1
@@ -17,6 +17,7 @@ function translateAssocsToJoinsCSN(csn, options){
17
17
  // Do not re-complain about localized
18
18
  const compileOptions = { ...options, $skipNameCheck: true };
19
19
  delete compileOptions.csnFlavor;
20
+ // console.log('CSN passed by A2J to compiler:',JSON.stringify(csn,null,2))
20
21
  const model = recompileX(csn, compileOptions);
21
22
  timetrace.stop('A2J: Recompiling model');
22
23
  timetrace.start('A2J: Translating associations to joins');
@@ -195,7 +196,7 @@ function translateAssocsToJoins(model, inputOptions = {})
195
196
  'leaf' QAT and to the respective $tableAlias which is used to link paths to the correct
196
197
  table alias. Subqueries are not considered in the mergePathIntoQat(), so a subquery QA
197
198
  must be created and added separately to the lead query $tableAlias'es.
198
- Also the name of the subquery (the alias) needs to be set to the final QA alias name.
199
+ Also, the name of the subquery (the alias) needs to be set to the final QA alias name.
199
200
  */
200
201
  function createQAForFromClauseSubQuery(query, env)
201
202
  {
@@ -203,7 +204,14 @@ function translateAssocsToJoins(model, inputOptions = {})
203
204
  if (query.$tableAliases[taName].kind !== '$self') {
204
205
  let ta = query.$tableAliases[taName];
205
206
  if(!ta.$QA) {
206
- ta.$QA = createQA(env, ta._origin, taName, undefined);
207
+ let alias = taName;
208
+ if (ta.name.$inferred === '$internal') {
209
+ // query has no explicit table alias, i.e. is internal: make it visible and remove `$`
210
+ alias = ta.name.alias.replace(/^[$]/, '_');
211
+ ta.$inferred = undefined;
212
+ ta.name.$inferred = undefined;
213
+ }
214
+ ta.$QA = createQA(env, ta._origin, alias, undefined);
207
215
  incAliasCount(env, ta.$QA);
208
216
  if(ta.name && ta.name.id) {
209
217
  ta.name.id = ta.$QA.name.id;
@@ -251,22 +259,24 @@ function translateAssocsToJoins(model, inputOptions = {})
251
259
  */
252
260
  function substituteDollarSelf(pathNode)
253
261
  {
254
- let [head, ...tail] = pathNode.path;
255
- if(['$projection', '$self'].includes(head.id) && tail.length) {
262
+ let pathValue = pathNode;
263
+ let [head, ...tail] = pathValue.path;
264
+ while(tail.length && head._navigation?.kind === '$self') {
256
265
  const self = head;
257
- if(self._navigation && self._navigation.kind === '$self') {
258
- [head, ...tail] = tail;
259
- if(head) {
260
- let pathValue = self._navigation._origin.elements[head.id].value;
261
- // core compiler has already caught $self.<assoc>.<postfix> and
262
- // non-path $self expressions with postfix path
263
- if(pathValue.path && tail.length) {
266
+ [head, ...tail] = tail;
267
+ if(head) {
268
+ pathValue = self._navigation._origin.elements[head.id].value;
269
+ // core compiler has already caught $self.<assoc>.<postfix> and
270
+ // non-path $self expressions with postfix path
271
+ if(pathValue.path) {
272
+ if(tail.length)
264
273
  pathValue = constructPathNode([...pathValue.path, ...tail], pathValue.alias, false);
265
- }
266
- replaceNodeContent(pathNode, pathValue);
274
+ [head, ...tail] = pathValue.path;
267
275
  }
268
276
  }
269
277
  }
278
+ if(head)
279
+ replaceNodeContent(pathNode, pathValue);
270
280
  }
271
281
 
272
282
  /*
@@ -304,7 +314,7 @@ function translateAssocsToJoins(model, inputOptions = {})
304
314
  // pop ta ps
305
315
  if(head._navigation.kind !== '$tableAlias')
306
316
  tail = pathNode.path;
307
- // if tail.lenth > 1, search bottom up for QA
317
+ // if tail.length > 1, search bottom up for QA
308
318
  // default to rootQA, _parent.$QA has precedence
309
319
  let [QA, ps] = rightMostQA(tail, head._navigation._parent.$QA || head._navigation.$QA);
310
320
  if(!QA) {
@@ -636,7 +646,7 @@ function translateAssocsToJoins(model, inputOptions = {})
636
646
  }
637
647
 
638
648
  env.assocStack.push(assoc);
639
- let onCond = cloneOnCondition(assoc.on);
649
+ const onCond = cloneOnCondition(assoc.on);
640
650
  env.assocStack.pop();
641
651
  return onCond;
642
652
  }
@@ -655,8 +665,8 @@ function translateAssocsToJoins(model, inputOptions = {})
655
665
  }
656
666
 
657
667
  function cloneOnCondExprStream(expr) {
658
- let args = expr.args;
659
- let result = { op: { val: expr.op.val }, args: [ ] };
668
+ const args = expr.args;
669
+ const result = { op: { val: expr.op.val }, args: [ ] };
660
670
  for(let i = 0; i < args.length; i++)
661
671
  {
662
672
  if(args[i].op && args[i].op.val === 'xpr')
@@ -668,7 +678,7 @@ function translateAssocsToJoins(model, inputOptions = {})
668
678
  else if(i < args.length-2 && args[i].path &&
669
679
  args[i+1]?.literal === 'token' && args[i+1]?.val === '=' && args[i+2].path)
670
680
  {
671
- let fwdAssoc = getForwardAssociation(args[i].path, args[i+2].path);
681
+ const fwdAssoc = getForwardAssociation(args[i].path, args[i+2].path);
672
682
  if(fwdAssoc)
673
683
  {
674
684
  //env.assocStack.includes(fwdAssoc) => recursion
@@ -702,7 +712,7 @@ function translateAssocsToJoins(model, inputOptions = {})
702
712
 
703
713
  // If this is a backlink condition, produce the
704
714
  // ON cond of the forward assoc with swapped src/tgt aliases
705
- let fwdAssoc = getForwardAssociationExpr(expr);
715
+ const fwdAssoc = getForwardAssociationExpr(expr);
706
716
  if(fwdAssoc) {
707
717
  if(env.assocStack.length === 2) {
708
718
  // reuse (ugly) error message from forHana
@@ -729,25 +739,38 @@ function translateAssocsToJoins(model, inputOptions = {})
729
739
  }
730
740
 
731
741
  // The src/tgtAliases need to be swapped for ON Condition of the forward assoc.
732
- // The correct table alias is the QA of the original target. If this target
733
- // has been redirected, use the QA of the redirected target.
734
- // As last resort use the source alias information.
735
- // TODO Discuss: Huh, why do you need to care about redirections?
736
- // Probably only with to-be-rewritten ON conditions (should be error in v2).
742
+ // If the QAT assoc is a mixin and forward assoc was propagated, the original
743
+ // forward definition must have a target in the query source otherwise the ON cond
744
+ // is not resolvable (exception propagated mixins, as these are defined against the
745
+ // view signature and not a query source). If the target is not part of the query source,
746
+ // raise an error. Swap source and target otherwise.
737
747
  function swapTableAliasesForFwdAssoc(fwdAssoc, srcAlias, tgtAlias) {
738
- let newSrcAlias = tgtAlias;
748
+ const newSrcAlias = tgtAlias;
739
749
  let newTgtAlias = {};
740
- // first try to identify table alias for complex views or redirected associations
741
- if(fwdAssoc._redirected && fwdAssoc._redirected.length &&
742
- // redirected target must have a $QA
743
- fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA &&
744
- // $QA's artifact must either be same srcAlias artifact
745
- (fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA._artifact === srcAlias._artifact ||
746
- // OR original assoc is a mixin (then just use the $QA)
747
- assoc.kind === 'mixin')) {
748
- newTgtAlias.id = fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA.name.id;
749
- newTgtAlias._artifact = fwdAssoc._redirected[fwdAssoc._redirected.length-1]._effectiveType;
750
- newTgtAlias._navigation = fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA.path[0]._navigation;
750
+
751
+ let i = 0;
752
+ let fwdOrigin = fwdAssoc;
753
+ while(fwdOrigin._origin) {
754
+ fwdOrigin = fwdOrigin._origin;
755
+ i++;
756
+ }
757
+ // If fwdAssoc was propagated and the origin is not a mixin itself (which always
758
+ // points to the signature of the current view and ensures that the ON cond is
759
+ // resolvable) make sure that the original assoc target is contained in the local
760
+ // query source
761
+ if(assoc.kind === 'mixin' && i > 0 && fwdOrigin.kind !== 'mixin') {
762
+ const tas = Object.values(env.lead.$tableAliases);
763
+ const i = tas.findIndex(ta => ta._artifact === fwdOrigin.target._artifact);
764
+ if(i >= 0 && tas[i].$QA) {
765
+ newTgtAlias.id = tas[i].$QA.name.id;
766
+ newTgtAlias._artifact = tas[i]._effectiveType;
767
+ newTgtAlias._navigation = tas[i].$QA.path[0]._navigation;
768
+ }
769
+ else {
770
+ error(null, [ assocQAT._origin.location, assocQAT._origin ], { name: fwdOrigin.target._artifact.name.id, art: assoc.name.id },
771
+ 'Expected association target $(NAME) of association $(ART) to be a query source');
772
+ newTgtAlias = Object.assign(newTgtAlias, srcAlias);
773
+ }
751
774
  }
752
775
  else {
753
776
  newTgtAlias = Object.assign(newTgtAlias, srcAlias);
@@ -989,7 +1012,7 @@ function translateAssocsToJoins(model, inputOptions = {})
989
1012
  A QA (QueryArtifact) is a representative for a table/view that must appear
990
1013
  in the FROM clause either named directly or indirectly through an association.
991
1014
  */
992
- function createQA(env, artifact, alias=undefined, namedArgs=undefined)
1015
+ function createQA(env, artifact, alias, namedArgs=undefined)
993
1016
  {
994
1017
  if(alias === undefined) {
995
1018
  throw new CompilerAssertion('no alias provided');
@@ -1,18 +1,19 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- forEachDefinition, forAllQueries, getNormalizedQuery,
4
+ forEachDefinition, forAllQueries, getNormalizedQuery, forEachMemberRecursively,
5
5
  } = require('../../model/csnUtils');
6
6
  const { setAnnotationIfNotDefined } = require('./utils');
7
7
  const { CompilerAssertion } = require('../../base/error');
8
8
 
9
9
  /**
10
- * Set @Core.Computed on the elements of views (and projections).
10
+ * Set @Core.Computed on the elements of views (and projections) as well
11
+ * as on calculated elements of entities and aspects.
11
12
  *
12
13
  * @param {CSN.Model} csn
13
14
  * @param {object} csnUtils
14
15
  */
15
- function setCoreComputedOnViews( csn, csnUtils ) {
16
+ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
16
17
  const {
17
18
  artifactRef, getColumn, getElement, getOrigin,
18
19
  } = csnUtils;
@@ -24,6 +25,12 @@ function setCoreComputedOnViews( csn, csnUtils ) {
24
25
  traverseQueryAndAttachCoreComputed(query, query.SELECT.elements || artifact.elements);
25
26
  }, path);
26
27
  }
28
+ else if (artifact.kind === 'entity' || artifact.kind === 'aspect') {
29
+ forEachMemberRecursively(artifact, (element) => {
30
+ if (element.value && !element.value?.ref) // calculated elements, but simple references are ignored
31
+ setAnnotationIfNotDefined(element, '@Core.Computed', true);
32
+ }, path);
33
+ }
27
34
  });
28
35
  /**
29
36
  * Attach @Core.Computed to elements resulting from calculated fields
@@ -84,7 +91,7 @@ function setCoreComputedOnViews( csn, csnUtils ) {
84
91
  * @todo cleanup throw(s) - but leave in during dev
85
92
  */
86
93
  function getElementFromFrom( name, base ) {
87
- if (base.SELECT && base.SELECT.elements) {
94
+ if (base.SELECT?.elements?.[name]) {
88
95
  return getAncestor(base.SELECT.elements[name], name, base.SELECT);
89
96
  }
90
97
  else if (base.ref) {
@@ -100,7 +107,7 @@ function setCoreComputedOnViews( csn, csnUtils ) {
100
107
  return checkJoinSources(base.args, name);
101
108
  }
102
109
 
103
- throw new CompilerAssertion(JSON.stringify(base));
110
+ throw new CompilerAssertion(`Element “${name}” not found in: ${JSON.stringify(base)}`);
104
111
  }
105
112
 
106
113
  /**
@@ -146,7 +153,8 @@ function setCoreComputedOnViews( csn, csnUtils ) {
146
153
  function needsCoreComputed( column ) {
147
154
  return column &&
148
155
  (
149
- column.xpr || column.func || column.val !== undefined || column.param || column.SELECT || column.SET ||
156
+ column.xpr || column.list || column.func || column.val !== undefined || column.param ||
157
+ column.SELECT || column.SET ||
150
158
  column.ref && [ '$at', '$now', '$user', '$session' ].includes(column.ref[0])
151
159
  );
152
160
  }
@@ -175,5 +183,5 @@ function setCoreComputedOnViews( csn, csnUtils ) {
175
183
  }
176
184
 
177
185
  module.exports = {
178
- setCoreComputedOnViews,
186
+ setCoreComputedOnViewsAndCalculatedElements,
179
187
  };
@@ -13,7 +13,7 @@ const {
13
13
  const {
14
14
  forEachValue, forEach,
15
15
  } = require('../../utils/objectUtils');
16
- const { setCoreComputedOnViews } = require('./coreComputed');
16
+ const { setCoreComputedOnViewsAndCalculatedElements } = require('./coreComputed');
17
17
 
18
18
  /**
19
19
  * Loop through a universal CSN and enrich it with the properties/annotations
@@ -134,7 +134,7 @@ module.exports = (csn, options) => {
134
134
  * `@Core.Computed' must be calculated manually as this annotation
135
135
  * is not set in the universal csn flavor.
136
136
  */
137
- setCoreComputedOnViews( csn, csnUtils );
137
+ setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils );
138
138
  /**
139
139
  * Construct an extensions object which maps a built-in type to it's annotations
140
140
  */
@@ -273,7 +273,7 @@ module.exports = (csn, options) => {
273
273
 
274
274
  /**
275
275
  * Walk over properties on member level and propagate all relevant properties
276
- * from it's prototype.
276
+ * from its prototype.
277
277
  */
278
278
  function propagateOnMemberLevel() {
279
279
  applyTransformations(csn, {
@@ -320,7 +320,7 @@ module.exports = (csn, options) => {
320
320
  }, []);
321
321
 
322
322
  /**
323
- * Propagate properties to the `member` from it's prototype.
323
+ * Propagate properties to the `member` from its prototype.
324
324
  * For that to work we calculate the prototype chain of the member and
325
325
  * propagate properties along this prototype chain until we reach the `member`
326
326
  * passed to this function.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "3.6.2",
3
+ "version": "3.8.0",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",
@@ -19,6 +19,7 @@
19
19
  "xmakeAfterInstall": "npm run gen",
20
20
  "xmakePrepareRelease": "echo \"$(node scripts/stripReadme.js README.md)\" > README.md && node scripts/assertSnapshotVersioning.js && node scripts/assertChangelog.js && node scripts/cleanup.js --remove-dev",
21
21
  "test": "node scripts/verifyGrammarChecksum.js && mocha --reporter min --reporter-option maxDiffSize=0 scripts/testLazyLoading.js && mocha --parallel --reporter min --reporter-option maxDiffSize=0 test/ test3/",
22
+ "testci": "node scripts/verifyGrammarChecksum.js && mocha --reporter-option maxDiffSize=0 scripts/testLazyLoading.js && mocha --parallel --reporter-option maxDiffSize=0 test/ test3/",
22
23
  "testverbose": "node scripts/verifyGrammarChecksum.js && mocha --parallel test/ test3/",
23
24
  "test3": "node scripts/verifyGrammarChecksum.js && mocha --reporter-option maxDiffSize=0 test3/",
24
25
  "deployTest3SQL": "deployRefs=true mocha --reporter-option maxDiffSize=0 test3/test.deploy.hana-sql.js",