@sap/cds-compiler 4.2.4 → 4.3.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 (66) hide show
  1. package/CHANGELOG.md +33 -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 +9 -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 +44 -39
  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
  }
@@ -731,7 +733,8 @@ function translateAssocsToJoins(model, inputOptions = {})
731
733
  // this will substitute multiple backlink conditions ($self = ... AND $self = ...AND ...)
732
734
  if(expr.op) {
733
735
  let x = clone(expr);
734
- x.args = expr.args.map(cloneOnCondition);
736
+ if(expr.args)
737
+ x.args = expr.args.map(cloneOnCondition);
735
738
  return x;
736
739
  }
737
740
 
@@ -931,8 +934,8 @@ function translateAssocsToJoins(model, inputOptions = {})
931
934
 
932
935
  if(elt) {
933
936
  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 },
937
+ const names = elt.map(e => (e._origin._main || e._origin).name.id);
938
+ error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: assoc._main, names },
936
939
  'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) is available from multiple query sources $(NAMES)');
937
940
  return pathNode.path;
938
941
  } else {
@@ -940,16 +943,16 @@ function translateAssocsToJoins(model, inputOptions = {})
940
943
  if(elt._origin._main !== path[0]._artifact._origin._main) {
941
944
  warning(null, [ assocQAT._origin.location, assocQAT._origin ], {
942
945
  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,
946
+ id: assoc.name.id, art: assoc._main.name.id,
947
+ name: path[0]._artifact._origin._main.name.id,
948
+ alias: elt._origin._main.name.id,
949
+ source: elt._main.name.id,
947
950
  }, 'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) originates from $(NAME) and from $(ALIAS) in $(SOURCE)');
948
951
  }
949
952
  _navigation = elt._parent;
950
953
  }
951
954
  } else {
952
- error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: assoc.name.absolute },
955
+ error(null, [ assocQAT._origin.location, assocQAT._origin ], { elemref: path[0].id, id: assoc.name.id, art: (assoc._main || assoc) },
953
956
  'Element $(ELEMREF) referred in association $(ID) of artifact $(ART) has not been found');
954
957
  return pathNode.path;
955
958
  }
@@ -1020,7 +1023,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1020
1023
  }
1021
1024
 
1022
1025
  const pathStep = {
1023
- id: artifact.name.absolute,
1026
+ id: (artifact._main || artifact).name.id,
1024
1027
  _artifact: artifact,
1025
1028
  _navigation : { name: { select: env.queryIndex + 1 } } // ???
1026
1029
  };
@@ -1320,20 +1323,20 @@ function translateAssocsToJoins(model, inputOptions = {})
1320
1323
  /* checks for all path steps */
1321
1324
  if(ps.args) {
1322
1325
  error(null, [pathDict.location, lead],
1323
- { name: lead.name.absolute, id: ps.id },
1324
- '$(NAME): $(ID) must not have parameters');
1326
+ { art: lead._main || lead, id: ps.id },
1327
+ '$(ART): $(ID) must not have parameters');
1325
1328
  pathDict.$check = false;
1326
1329
  }
1327
1330
  if(ps.where) {
1328
1331
  error(null, [pathDict.location, lead],
1329
- { name: lead.name.absolute, id: ps.id },
1330
- '$(NAME): $(ID) must not have a filter');
1332
+ { art: lead._main || lead, id: ps.id },
1333
+ '$(ART): $(ID) must not have a filter');
1331
1334
  pathDict.$check = false;
1332
1335
  }
1333
1336
  if(ps._artifact.virtual) {
1334
1337
  error(null, [pathDict.location, lead],
1335
- { name: lead.name.absolute, id: ps.id },
1336
- '$(NAME): $(ID) must not be virtual');
1338
+ { art: lead._main || lead, id: ps.id },
1339
+ '$(ART): $(ID) must not be virtual');
1337
1340
  pathDict.$check = false;
1338
1341
  }
1339
1342
  // checks for all path steps except the first one (if it is the name of the defining association)
@@ -1345,15 +1348,15 @@ function translateAssocsToJoins(model, inputOptions = {})
1345
1348
  if(ps._artifact.on)
1346
1349
  {
1347
1350
  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');
1351
+ { art: lead._main || lead, id: ps.id, alias: pathAsStr(pathDict.path) },
1352
+ '$(ART): $(ID) in path $(ALIAS) must not be an unmanaged association');
1350
1353
  pathDict.$check = false;
1351
1354
  }
1352
1355
  else if(ps._artifact.$fkPathPrefixTree)// must be managed
1353
1356
  {
1354
1357
  if(!ps._artifact.$fkPathPrefixTree.children[la1.id]) {
1355
1358
  error(null, [pathDict.location, lead],
1356
- { art: lead.name.absolute, id: la1.id, name: ps.id, alias: pathAsStr(pathDict.path) },
1359
+ { art: lead._main || lead, id: la1.id, name: ps.id, alias: pathAsStr(pathDict.path) },
1357
1360
  '$(ART): $(ID) is not foreign key of managed association $(NAME) in path $(ALIAS)' );
1358
1361
  pathDict.$check = false;
1359
1362
  }
@@ -1362,7 +1365,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1362
1365
  else {
1363
1366
  // it is the last path step => no association
1364
1367
  error(null, [pathDict.location, lead],
1365
- { art: lead.name.absolute, id: ps.id, alias: pathAsStr(pathDict.path) },
1368
+ { art: lead._main || lead, id: ps.id, alias: pathAsStr(pathDict.path) },
1366
1369
  '$(ART): $(ID) in path $(ALIAS) must not be an association');
1367
1370
  pathDict.$check = false;
1368
1371
  }
@@ -1373,7 +1376,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1373
1376
  const artifact = lastSegment && lastSegment._artifact && lastSegment._artifact.type && lastSegment._artifact.type._artifact && lastSegment._artifact.type._artifact;
1374
1377
  if (artifact && artifact.elements) {
1375
1378
  error(null, [pathDict.location, lead],
1376
- { art: lead.name.absolute, id: lastSegment.id },
1379
+ { art: lead._main || lead, id: lastSegment.id },
1377
1380
  '$(ART): $(ID) must have scalar type');
1378
1381
  pathDict.$check = false;
1379
1382
  }
@@ -1547,10 +1550,11 @@ function translateAssocsToJoins(model, inputOptions = {})
1547
1550
  */
1548
1551
  const art = qat._origin;
1549
1552
  if(noJoinForFK && qat._origin.target) {
1550
- if(!pathStep.where && // has no filter
1551
- !pathStep.args && // has no args
1553
+ if(!pathStep.args && // has no args
1552
1554
  (
1553
- ( // not leaf
1555
+ (
1556
+ !pathStep.where && // has no filter
1557
+ // not leaf + next step is foreign key
1554
1558
  i < tail.length-1 &&
1555
1559
  // path terminates on a scalar type
1556
1560
  // _effectiveType.elements can be removed if forRelationalDB can expand fk paths correctly
@@ -1562,7 +1566,8 @@ function translateAssocsToJoins(model, inputOptions = {})
1562
1566
  )
1563
1567
  ||
1564
1568
  (
1565
- // Non from block path terminates on association
1569
+ // Non-FROM block path terminates on association, e.g. publishing associations
1570
+ // with or without filter.
1566
1571
  i === tail.length-1 &&
1567
1572
  env.location !== 'from'
1568
1573
  )
@@ -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.2",
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
  },