@sap/cds-compiler 4.2.4 → 4.3.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 (66) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/bin/cdsc.js +8 -0
  3. package/bin/cdshi.js +3 -3
  4. package/doc/CHANGELOG_BETA.md +7 -0
  5. package/lib/api/main.js +19 -0
  6. package/lib/base/location.js +16 -0
  7. package/lib/base/message-registry.js +47 -16
  8. package/lib/base/messages.js +49 -38
  9. package/lib/base/model.js +1 -1
  10. package/lib/checks/checkPathsInStoredCalcElement.js +83 -0
  11. package/lib/checks/existsExpressionsOnlyForeignKeys.js +71 -0
  12. package/lib/checks/existsMustEndInAssoc.js +27 -0
  13. package/lib/checks/onConditions.js +47 -1
  14. package/lib/checks/validator.js +10 -1
  15. package/lib/compiler/assert-consistency.js +23 -15
  16. package/lib/compiler/base.js +31 -14
  17. package/lib/compiler/builtins.js +21 -20
  18. package/lib/compiler/checks.js +36 -49
  19. package/lib/compiler/define.js +71 -91
  20. package/lib/compiler/extend.js +27 -25
  21. package/lib/compiler/finalize-parse-cdl.js +1 -1
  22. package/lib/compiler/generate.js +67 -87
  23. package/lib/compiler/kick-start.js +7 -5
  24. package/lib/compiler/populate.js +32 -30
  25. package/lib/compiler/propagator.js +2 -0
  26. package/lib/compiler/resolve.js +29 -25
  27. package/lib/compiler/shared.js +57 -31
  28. package/lib/compiler/tweak-assocs.js +203 -22
  29. package/lib/compiler/utils.js +0 -18
  30. package/lib/gen/Dictionary.json +10 -4
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/languageParser.js +3 -3
  33. package/lib/inspect/inspectPropagation.js +2 -1
  34. package/lib/json/from-csn.js +63 -28
  35. package/lib/json/to-csn.js +23 -13
  36. package/lib/language/antlrParser.js +1 -1
  37. package/lib/language/errorStrategy.js +5 -1
  38. package/lib/language/genericAntlrParser.js +67 -61
  39. package/lib/main.d.ts +26 -1
  40. package/lib/main.js +2 -1
  41. package/lib/model/csnRefs.js +1 -0
  42. package/lib/model/csnUtils.js +28 -0
  43. package/lib/model/revealInternalProperties.js +3 -9
  44. package/lib/optionProcessor.js +17 -1
  45. package/lib/render/toCdl.js +1 -1
  46. package/lib/transform/db/associations.js +3 -4
  47. package/lib/transform/db/backlinks.js +293 -0
  48. package/lib/transform/db/expansion.js +9 -7
  49. package/lib/transform/db/flattening.js +3 -2
  50. package/lib/transform/db/rewriteCalculatedElements.js +1 -67
  51. package/lib/transform/db/transformExists.js +3 -58
  52. package/lib/transform/db/views.js +8 -14
  53. package/lib/transform/effective/.eslintrc.json +4 -0
  54. package/lib/transform/effective/associations.js +101 -0
  55. package/lib/transform/effective/main.js +88 -0
  56. package/lib/transform/effective/misc.js +61 -0
  57. package/lib/transform/effective/queries.js +42 -0
  58. package/lib/transform/effective/types.js +121 -0
  59. package/lib/transform/forRelationalDB.js +12 -235
  60. package/lib/transform/localized.js +22 -3
  61. package/lib/transform/parseExpr.js +7 -3
  62. package/lib/transform/transformUtils.js +5 -22
  63. package/lib/transform/translateAssocsToJoins.js +42 -38
  64. package/lib/transform/universalCsn/universalCsnEnricher.js +17 -1
  65. package/package.json +1 -2
  66. package/lib/language/language.g4 +0 -3260
@@ -60,7 +60,7 @@ function translateAssocsToJoins(model, inputOptions = {})
60
60
  // create JOINs for foreign key paths
61
61
  const noJoinForFK = options.forHana ? !options.joinfk : true;
62
62
 
63
- // Note: This is called from the 'forHana' transformations, so it is controlled by its options)
63
+ // Note: This is called from the 'forHana' transformations, so it is controlled by its options
64
64
  const pathDelimiter = (options.forHana && options.sqlMapping === 'hdbcds') ? '.' : '_';
65
65
 
66
66
  forEachDefinition(model, prepareAssociations);
@@ -76,7 +76,9 @@ function translateAssocsToJoins(model, inputOptions = {})
76
76
  prepended to all source side paths of the resulting ON condition
77
77
  (cut off name.id from name.element)
78
78
  */
79
- art.$elementPrefix = art.name.element.slice(0, art.name.element.length - art.name.id.length).replace(/\./g, pathDelimiter);
79
+ art.$elementPrefix = '';
80
+ for(let parent = art._parent; parent?.kind === 'element'; parent = parent._parent)
81
+ art.$elementPrefix = parent.name.id + pathDelimiter + art.$elementPrefix;
80
82
 
81
83
  /*
82
84
  Create path prefix tree for Foreign Keys, required to substitute
@@ -207,7 +209,7 @@ function translateAssocsToJoins(model, inputOptions = {})
207
209
  let alias = taName;
208
210
  if (ta.name.$inferred === '$internal') {
209
211
  // query has no explicit table alias, i.e. is internal: make it visible and remove `$`
210
- alias = ta.name.alias.replace(/^[$]/, '_');
212
+ alias = ta.name.id.replace(/^[$]/, '_');
211
213
  ta.$inferred = undefined;
212
214
  ta.name.$inferred = undefined;
213
215
  }
@@ -221,11 +223,11 @@ function translateAssocsToJoins(model, inputOptions = {})
221
223
  }
222
224
  // Only subqueries of the FROM clause have a name (which is the alias)
223
225
  // TODO Discuss: a query does not have a name.id anymore
224
- const queryAlias = query._parent; // parent could also be outer query, or main entity
225
- if(query.op.val === 'SELECT' && query.name.id && queryAlias && queryAlias.kind === '$tableAlias')
226
- {
227
- query.name.id = queryAlias._parent.$tableAliases[query.name.id].$QA.name.id;
228
- }
226
+ // const queryAlias = query._parent; // parent could also be outer query, or main entity
227
+ // if(query.op.val === 'SELECT' && query.name.id && queryAlias && queryAlias.kind === '$tableAlias')
228
+ // {
229
+ // query.name.id = queryAlias._parent.$tableAliases[query.name.id].$QA.name.id;
230
+ // }
229
231
  }
230
232
 
231
233
  /*
@@ -430,7 +432,7 @@ function translateAssocsToJoins(model, inputOptions = {})
430
432
  if(art.kind === 'entity')
431
433
  {
432
434
  if(!childQat.$QA)
433
- childQat.$QA = createQA(env, art, art.name.absolute.split('.').pop(), childQat._namedArgs);
435
+ childQat.$QA = createQA(env, art, art.name.id.split('.').pop(), childQat._namedArgs);
434
436
  incAliasCount(env, childQat.$QA);
435
437
  newAssocLHS = childQat.$QA;
436
438
 
@@ -529,7 +531,7 @@ function translateAssocsToJoins(model, inputOptions = {})
529
531
  const xsnCard = {
530
532
  targetMax : { literal: 'number', val: 1 }
531
533
  };
532
- if(assoc.type._artifact._effectiveType.name.absolute === 'cds.Composition') {
534
+ if(assoc.type._artifact._effectiveType.name.id === 'cds.Composition') {
533
535
  xsnCard.sourceMin = { literal: 'number', val: 1 };
534
536
  xsnCard.sourceMax = { literal: 'number', val: 1 };
535
537
  }
@@ -658,7 +660,7 @@ function translateAssocsToJoins(model, inputOptions = {})
658
660
  // clone ON condition with rewritten paths and substituted backlink conditions
659
661
  function cloneOnCondition(expr)
660
662
  {
661
- if(expr.op && expr.op.val === 'xpr')
663
+ if(expr.op?.val === 'xpr')
662
664
  return cloneOnCondExprStream(expr);
663
665
  else
664
666
  return cloneOnCondExprTree(expr);
@@ -669,7 +671,7 @@ function translateAssocsToJoins(model, inputOptions = {})
669
671
  const result = { op: { val: expr.op.val }, args: [ ] };
670
672
  for(let i = 0; i < args.length; i++)
671
673
  {
672
- if(args[i].op && args[i].op.val === 'xpr')
674
+ if(args[i].op?.val === 'xpr')
673
675
  {
674
676
  result.args.push(cloneOnCondition(args[i]));
675
677
  }
@@ -696,11 +698,11 @@ function translateAssocsToJoins(model, inputOptions = {})
696
698
  i += 2; // skip next two tokens and continue with loop
697
699
  continue;
698
700
  }
699
- else
701
+ else // it's ensured that it's a path
700
702
  result.args.push(rewritePathNode(args[i]));
701
703
  }
702
- else
703
- result.args.push(rewritePathNode(args[i]));
704
+ else // could be `{op:…}`, clone generically
705
+ result.args.push(cloneOnCondition(args[i]));
704
706
  }
705
707
  return result;
706
708
  }
@@ -931,8 +933,8 @@ function translateAssocsToJoins(model, inputOptions = {})
931
933
 
932
934
  if(elt) {
933
935
  if(Array.isArray(elt)) {
934
- const names = elt.map(e => e._origin.name.absolute);
935
- error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: assoc.name.absolute, names },
936
+ const names = elt.map(e => (e._origin._main || e._origin).name.id);
937
+ error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: assoc._main, names },
936
938
  'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) is available from multiple query sources $(NAMES)');
937
939
  return pathNode.path;
938
940
  } else {
@@ -940,16 +942,16 @@ function translateAssocsToJoins(model, inputOptions = {})
940
942
  if(elt._origin._main !== path[0]._artifact._origin._main) {
941
943
  warning(null, [ assocQAT._origin.location, assocQAT._origin ], {
942
944
  elemref: path[0].id,
943
- id: assoc.name.id, art: assoc.name.absolute,
944
- name: path[0]._artifact._origin._main.name.absolute,
945
- alias: elt._origin._main.name.absolute,
946
- source: elt._main.name.absolute,
945
+ id: assoc.name.id, art: assoc._main.name.id,
946
+ name: path[0]._artifact._origin._main.name.id,
947
+ alias: elt._origin._main.name.id,
948
+ source: elt._main.name.id,
947
949
  }, 'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) originates from $(NAME) and from $(ALIAS) in $(SOURCE)');
948
950
  }
949
951
  _navigation = elt._parent;
950
952
  }
951
953
  } else {
952
- error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: assoc.name.absolute },
954
+ error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: (assoc._main || assoc) },
953
955
  'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) has not been found');
954
956
  return pathNode.path;
955
957
  }
@@ -1020,7 +1022,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1020
1022
  }
1021
1023
 
1022
1024
  const pathStep = {
1023
- id: artifact.name.absolute,
1025
+ id: (artifact._main || artifact).name.id,
1024
1026
  _artifact: artifact,
1025
1027
  _navigation : { name: { select: env.queryIndex + 1 } } // ???
1026
1028
  };
@@ -1320,20 +1322,20 @@ function translateAssocsToJoins(model, inputOptions = {})
1320
1322
  /* checks for all path steps */
1321
1323
  if(ps.args) {
1322
1324
  error(null, [pathDict.location, lead],
1323
- { name: lead.name.absolute, id: ps.id },
1324
- '$(NAME): $(ID) must not have parameters');
1325
+ { art: lead._main || lead, id: ps.id },
1326
+ '$(ART): $(ID) must not have parameters');
1325
1327
  pathDict.$check = false;
1326
1328
  }
1327
1329
  if(ps.where) {
1328
1330
  error(null, [pathDict.location, lead],
1329
- { name: lead.name.absolute, id: ps.id },
1330
- '$(NAME): $(ID) must not have a filter');
1331
+ { art: lead._main || lead, id: ps.id },
1332
+ '$(ART): $(ID) must not have a filter');
1331
1333
  pathDict.$check = false;
1332
1334
  }
1333
1335
  if(ps._artifact.virtual) {
1334
1336
  error(null, [pathDict.location, lead],
1335
- { name: lead.name.absolute, id: ps.id },
1336
- '$(NAME): $(ID) must not be virtual');
1337
+ { art: lead._main || lead, id: ps.id },
1338
+ '$(ART): $(ID) must not be virtual');
1337
1339
  pathDict.$check = false;
1338
1340
  }
1339
1341
  // checks for all path steps except the first one (if it is the name of the defining association)
@@ -1345,15 +1347,15 @@ function translateAssocsToJoins(model, inputOptions = {})
1345
1347
  if(ps._artifact.on)
1346
1348
  {
1347
1349
  error(null, [pathDict.location, lead],
1348
- { name: lead.name.absolute, id: ps.id, alias: pathAsStr(pathDict.path) },
1349
- '$(NAME): $(ID) in path $(ALIAS)" must not be an unmanaged association');
1350
+ { art: lead._main || lead, id: ps.id, alias: pathAsStr(pathDict.path) },
1351
+ '$(ART): $(ID) in path $(ALIAS) must not be an unmanaged association');
1350
1352
  pathDict.$check = false;
1351
1353
  }
1352
1354
  else if(ps._artifact.$fkPathPrefixTree)// must be managed
1353
1355
  {
1354
1356
  if(!ps._artifact.$fkPathPrefixTree.children[la1.id]) {
1355
1357
  error(null, [pathDict.location, lead],
1356
- { art: lead.name.absolute, id: la1.id, name: ps.id, alias: pathAsStr(pathDict.path) },
1358
+ { art: lead._main || lead, id: la1.id, name: ps.id, alias: pathAsStr(pathDict.path) },
1357
1359
  '$(ART): $(ID) is not foreign key of managed association $(NAME) in path $(ALIAS)' );
1358
1360
  pathDict.$check = false;
1359
1361
  }
@@ -1362,7 +1364,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1362
1364
  else {
1363
1365
  // it is the last path step => no association
1364
1366
  error(null, [pathDict.location, lead],
1365
- { art: lead.name.absolute, id: ps.id, alias: pathAsStr(pathDict.path) },
1367
+ { art: lead._main || lead, id: ps.id, alias: pathAsStr(pathDict.path) },
1366
1368
  '$(ART): $(ID) in path $(ALIAS) must not be an association');
1367
1369
  pathDict.$check = false;
1368
1370
  }
@@ -1373,7 +1375,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1373
1375
  const artifact = lastSegment && lastSegment._artifact && lastSegment._artifact.type && lastSegment._artifact.type._artifact && lastSegment._artifact.type._artifact;
1374
1376
  if (artifact && artifact.elements) {
1375
1377
  error(null, [pathDict.location, lead],
1376
- { art: lead.name.absolute, id: lastSegment.id },
1378
+ { art: lead._main || lead, id: lastSegment.id },
1377
1379
  '$(ART): $(ID) must have scalar type');
1378
1380
  pathDict.$check = false;
1379
1381
  }
@@ -1547,10 +1549,11 @@ function translateAssocsToJoins(model, inputOptions = {})
1547
1549
  */
1548
1550
  const art = qat._origin;
1549
1551
  if(noJoinForFK && qat._origin.target) {
1550
- if(!pathStep.where && // has no filter
1551
- !pathStep.args && // has no args
1552
+ if(!pathStep.args && // has no args
1552
1553
  (
1553
- ( // not leaf
1554
+ (
1555
+ !pathStep.where && // has no filter
1556
+ // not leaf + next step is foreign key
1554
1557
  i < tail.length-1 &&
1555
1558
  // path terminates on a scalar type
1556
1559
  // _effectiveType.elements can be removed if forRelationalDB can expand fk paths correctly
@@ -1562,7 +1565,8 @@ function translateAssocsToJoins(model, inputOptions = {})
1562
1565
  )
1563
1566
  ||
1564
1567
  (
1565
- // Non from block path terminates on association
1568
+ // Non-FROM block path terminates on association, e.g. publishing associations
1569
+ // with or without filter.
1566
1570
  i === tail.length-1 &&
1567
1571
  env.location !== 'from'
1568
1572
  )
@@ -96,7 +96,7 @@ module.exports = (csn, options) => {
96
96
  val: always,
97
97
  type: notWithItemsOrElements,
98
98
  target: notWithItemsOrElements,
99
- keys: notWithItemsOrElements,
99
+ keys: specialKeysRules,
100
100
  cardinality: notWithItemsOrElements,
101
101
  };
102
102
 
@@ -747,6 +747,22 @@ function specialItemsRules( prop, target, source ) {
747
747
  target[prop] = source[prop];
748
748
  }
749
749
 
750
+ /**
751
+ * Don't propagate property `keys` if there is an ON-condition. This happens for
752
+ * published managed associations with filters in views, which are transformed into
753
+ * unmanaged associations, i.e. get an ON-condition.
754
+ *
755
+ * Besides that, rules from notWithItemsOrElements() apply.
756
+ *
757
+ * @param {string} prop
758
+ * @param {CSN.Element} target
759
+ * @param {CSN.Element} source
760
+ */
761
+ function specialKeysRules( prop, target, source ) {
762
+ if (target.on === undefined)
763
+ notWithItemsOrElements( prop, target, source );
764
+ }
765
+
750
766
  /**
751
767
  * Don't propagate certain properties if the target already has a .items or .elements
752
768
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "4.2.4",
3
+ "version": "4.3.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)",
@@ -38,7 +38,6 @@
38
38
  "generateOdataAnnoRefs": "cross-env MAKEREFS='true' mocha test/testODataAnnotations.js",
39
39
  "generateToSqlRefs": "cross-env MAKEREFS='true' mocha test/testToSql.js",
40
40
  "generateToRenameRefs": "cross-env MAKEREFS='true' mocha test/testToRename.js",
41
- "generateChecksRefs": "cross-env MAKEREFS='true' mocha test/testChecks.js",
42
41
  "generateDraftRefs": "cross-env MAKEREFS='true' mocha test/testDraft.js",
43
42
  "generateAllRefs": "node scripts/verifyGrammarChecksum.js && cross-env MAKEREFS='true' mocha --reporter-option maxDiffSize=0 test/ test3/"
44
43
  },