@sap/cds-compiler 5.2.0 → 5.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 (54) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/bin/cdsc.js +5 -0
  3. package/bin/cdshi.js +8 -8
  4. package/doc/CHANGELOG_BETA.md +9 -4
  5. package/lib/api/validate.js +5 -0
  6. package/lib/base/message-registry.js +25 -1
  7. package/lib/base/messages.js +1 -1
  8. package/lib/base/model.js +0 -1
  9. package/lib/compiler/assert-consistency.js +2 -2
  10. package/lib/compiler/builtins.js +1 -1
  11. package/lib/compiler/checks.js +25 -6
  12. package/lib/compiler/define.js +24 -28
  13. package/lib/compiler/extend.js +11 -13
  14. package/lib/compiler/generate.js +3 -3
  15. package/lib/compiler/populate.js +13 -7
  16. package/lib/compiler/propagator.js +2 -2
  17. package/lib/compiler/resolve.js +58 -60
  18. package/lib/compiler/shared.js +5 -5
  19. package/lib/compiler/tweak-assocs.js +247 -34
  20. package/lib/compiler/utils.js +40 -32
  21. package/lib/compiler/xpr-rewrite.js +44 -58
  22. package/lib/edm/annotations/genericTranslation.js +4 -4
  23. package/lib/edm/csn2edm.js +2 -2
  24. package/lib/edm/edm.js +46 -21
  25. package/lib/edm/edmInboundChecks.js +0 -1
  26. package/lib/edm/edmPreprocessor.js +40 -27
  27. package/lib/edm/edmUtils.js +1 -1
  28. package/lib/gen/BaseParser.js +180 -122
  29. package/lib/gen/CdlParser.js +2226 -2170
  30. package/lib/gen/language.checksum +1 -1
  31. package/lib/gen/language.interp +1 -1
  32. package/lib/gen/languageParser.js +3820 -3777
  33. package/lib/inspect/inspectPropagation.js +1 -1
  34. package/lib/json/from-csn.js +5 -3
  35. package/lib/json/to-csn.js +7 -10
  36. package/lib/language/antlrParser.js +38 -4
  37. package/lib/language/errorStrategy.js +1 -1
  38. package/lib/language/genericAntlrParser.js +4 -4
  39. package/lib/language/multiLineStringParser.js +1 -1
  40. package/lib/main.d.ts +23 -0
  41. package/lib/model/cloneCsn.js +22 -13
  42. package/lib/optionProcessor.js +7 -7
  43. package/lib/parsers/AstBuildingParser.js +155 -37
  44. package/lib/parsers/CdlGrammar.g4 +154 -81
  45. package/lib/parsers/Lexer.js +20 -10
  46. package/lib/render/toCdl.js +23 -18
  47. package/lib/transform/addTenantFields.js +4 -4
  48. package/lib/transform/db/rewriteCalculatedElements.js +11 -5
  49. package/lib/transform/db/transformExists.js +43 -18
  50. package/lib/transform/effective/main.js +1 -1
  51. package/lib/transform/forRelationalDB.js +8 -7
  52. package/lib/utils/moduleResolve.js +1 -1
  53. package/package.json +1 -1
  54. package/share/messages/redirected-to-complex.md +6 -3
@@ -1331,6 +1331,9 @@ function csnToCdl( csn, options, msg ) {
1331
1331
  if (artifact.localized) // works even for type definitions
1332
1332
  result += 'localized ';
1333
1333
 
1334
+ // Some properties are always "top-level", even for "many", e.g. "default" or
1335
+ // "not null". Keep a reference to the outer artifact.
1336
+ const origArtifact = artifact;
1334
1337
  if (!artifact.type && artifact.items) {
1335
1338
  checkArrayedArtifact(artifact, env);
1336
1339
  result += 'many '; // alternative: 'array of'; but not used
@@ -1342,11 +1345,14 @@ function csnToCdl( csn, options, msg ) {
1342
1345
 
1343
1346
  if (!type && artifact.elements) {
1344
1347
  result += renderElements(artifact, env);
1345
- result += renderNullability(artifact);
1348
+ result += renderNullability(artifact.notNull);
1346
1349
  // structured default not possible at the moment
1347
1350
  return result;
1348
1351
  }
1349
1352
 
1353
+ const defaultValue = origArtifact.default ? origArtifact.default : artifact.default;
1354
+ const notNull = origArtifact.notNull ? origArtifact.notNull : artifact.notNull;
1355
+
1350
1356
  // Association type
1351
1357
  if (isDirectAssocOrComp(type)) {
1352
1358
  const isComp = type === 'cds.Composition';
@@ -1378,11 +1384,11 @@ function csnToCdl( csn, options, msg ) {
1378
1384
  if (artifact.keys && !artifact.on)
1379
1385
  result += ` ${ renderForeignKeys(artifact, env) }`;
1380
1386
 
1381
- if (artifact.notNull !== undefined && !artifact.on) // unmanaged associations can't be followed by "not null"
1382
- result += renderNullability(artifact);
1383
-
1384
- if (artifact.default && !artifact.on)
1385
- result += renderDefaultExpr(artifact, env);
1387
+ if (!artifact.on) {
1388
+ // unmanaged associations can't be followed by "not null" or "default"
1389
+ result += renderNullability(notNull);
1390
+ result += renderDefaultExpr(defaultValue, env.withSubPath([ 'default' ]));
1391
+ }
1386
1392
  return result;
1387
1393
  }
1388
1394
 
@@ -1409,13 +1415,12 @@ function csnToCdl( csn, options, msg ) {
1409
1415
 
1410
1416
  if (artifact.enum && !typeRefOnly)
1411
1417
  result += renderEnum(artifact.enum, env);
1412
- if (artifact.notNull !== undefined)
1413
- result += renderNullability(artifact);
1414
1418
 
1419
+ result += renderNullability(notNull);
1415
1420
  // If there is a default value, and it's a calculated element, do not
1416
1421
  // render the default (because it's not supported for calc elements).
1417
- if (artifact.default !== undefined && !artifact.value)
1418
- result += renderDefaultExpr(artifact, env);
1422
+ if (defaultValue !== undefined && !artifact.value)
1423
+ result += renderDefaultExpr(defaultValue, env.withSubPath([ 'default' ]));
1419
1424
 
1420
1425
  return result;
1421
1426
  }
@@ -1789,24 +1794,24 @@ function csnToCdl( csn, options, msg ) {
1789
1794
  return result;
1790
1795
  }
1791
1796
 
1792
- function renderDefaultExpr( art, env ) {
1793
- if (!art.default)
1797
+ function renderDefaultExpr( defaultValue, env ) {
1798
+ if (!defaultValue)
1794
1799
  return '';
1795
1800
  let result = ' default ';
1796
- if ( art.default.xpr && xprContainsCondition( art.default.xpr))
1797
- result += exprRenderer.renderSubExpr(withoutCast( art.default), env.withSubPath([ 'default' ]));
1801
+ if (defaultValue.xpr && xprContainsCondition( defaultValue.xpr))
1802
+ result += exprRenderer.renderSubExpr(withoutCast(defaultValue), env);
1798
1803
  else
1799
- result += exprRenderer.renderExpr(withoutCast( art.default), env.withSubPath([ 'default' ]));
1804
+ result += exprRenderer.renderExpr(withoutCast(defaultValue), env);
1800
1805
  return result;
1801
1806
  }
1802
1807
 
1803
1808
  // Render the nullability of an element or parameter (can be unset, true, or false)
1804
- function renderNullability( obj /* , env */) {
1805
- if (obj.notNull === undefined) {
1809
+ function renderNullability( notNull /* , env */) {
1810
+ if (notNull === undefined) {
1806
1811
  // Attribute not set at all
1807
1812
  return '';
1808
1813
  }
1809
- return obj.notNull ? ' not null' : ' null';
1814
+ return notNull ? ' not null' : ' null';
1810
1815
  }
1811
1816
 
1812
1817
  /**
@@ -78,7 +78,7 @@ function addTenantFields( csn, options, messageFunctions ) {
78
78
  else if (!independent && independent != null) {
79
79
  error( 'tenant-invalid-anno-value', msgLocations( csnPath ),
80
80
  { anno: annoTenantIndep, value: independent },
81
- // eslint-disable-next-line max-len
81
+ // eslint-disable-next-line @stylistic/js/max-len
82
82
  'Can\'t add $(ANNO) with value $(VALUE) to a non-entity, which is always tenant-independent' );
83
83
  }
84
84
  else if (art.includes) {
@@ -119,9 +119,9 @@ function addTenantFields( csn, options, messageFunctions ) {
119
119
  .filter( name => isTenantDepEntity( csn.definitions[name] ) );
120
120
  if (names.length) {
121
121
  error( 'tenant-invalid-include', msgLocations( csnPath ), { names }, {
122
- // eslint-disable-next-line max-len
122
+ // eslint-disable-next-line @stylistic/js/max-len
123
123
  std: 'Can\'t include the tenant-dependent entities $(NAMES) into a tenant-independent definition',
124
- // eslint-disable-next-line max-len
124
+ // eslint-disable-next-line @stylistic/js/max-len
125
125
  one: 'Can\'t include the tenant-dependent entity $(NAMES) into a tenant-independent definition',
126
126
  } );
127
127
  }
@@ -206,7 +206,7 @@ function addTenantFields( csn, options, messageFunctions ) {
206
206
  if (art[annoTenantIndep]) {
207
207
  error( 'tenant-expecting-tenant-source', msgLocations( csnPath ), { art: query },
208
208
  // TODO: better the final entity name of assoc navigation in FROM
209
- // eslint-disable-next-line max-len
209
+ // eslint-disable-next-line @stylistic/js/max-len
210
210
  'Expecting the query source $(ART) to be tenant-dependent for a tenant-dependent query entity' );
211
211
  }
212
212
  return true;
@@ -660,11 +660,12 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
660
660
 
661
661
  /**
662
662
  * @param {CSN.Model} csn
663
+ * @param {CSN.Options} options
663
664
  */
664
- function processCalculatedElementsInEntities( csn ) {
665
+ function processCalculatedElementsInEntities( csn, options ) {
665
666
  forEachDefinition(csn, (artifact, definitionName) => {
666
667
  if (artifact.kind === 'entity' && !(artifact.query || artifact.projection))
667
- removeDummyValueInEntity(artifact, [ 'definitions', definitionName ]);
668
+ removeDummyValueInEntity(artifact, [ 'definitions', definitionName ], options);
668
669
  });
669
670
  }
670
671
 
@@ -674,14 +675,19 @@ function processCalculatedElementsInEntities( csn ) {
674
675
  *
675
676
  * @param {CSN.Artifact} artifact
676
677
  * @param {CSN.Path} path
678
+ * @param {CSN.Options} options
677
679
  * @todo calculated elements that "live" on the database?
678
680
  * @todo error when artifact is empty afterwards? Probably better as a CSN check!
679
681
  */
680
- function removeDummyValueInEntity( artifact, path ) {
682
+ function removeDummyValueInEntity( artifact, path, options ) {
681
683
  applyTransformationsOnDictionary(artifact.elements, {
682
684
  value: (parent, prop, value, p, elements) => {
683
- if (!value.stored)
684
- delete elements[p[p.length - 1]];
685
+ if (!value.stored) {
686
+ if (options.transformation === 'effective' && parent.on)
687
+ delete parent.value;
688
+ else
689
+ delete elements[p.at(-1)];
690
+ }
685
691
  },
686
692
  }, {}, path.concat( 'elements' ));
687
693
  }
@@ -409,6 +409,7 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
409
409
  }
410
410
 
411
411
  /**
412
+ *
412
413
  * Translate an `EXISTS <unmanaged assoc>` into a part of a WHERE condition.
413
414
  *
414
415
  * A valid $self-backlink is handled in translateDollarSelfToWhere.
@@ -428,53 +429,77 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
428
429
  */
429
430
  function translateUnmanagedAssocToWhere( root, target, isPrefixedWithTableAlias, base, current ) {
430
431
  const whereExtension = [];
431
- for (let j = 0; j < root.on.length; j++) {
432
- const part = root.on[j];
432
+
433
+ for (let j = 0; j < root.on.length; j++)
434
+ j = processExpressionPart(root.on, root.$path.concat('on'), j, whereExtension);
435
+
436
+ return whereExtension;
437
+
438
+ /**
439
+ * Process the given expression and apply the steps described above.
440
+ *
441
+ * @param {Array} expression Expression we are processing
442
+ * @param {CSN.Path} path Path to the expression
443
+ * @param {number} expressionIndex Index in the current expression, imporant for paths and stuff
444
+ * @param {Array} collector Array to collect the processed expressionparts into
445
+ * @returns {number} How far along expression we have processed - so the main loop can jump ahead
446
+ */
447
+ function processExpressionPart(expression, path, expressionIndex, collector) {
448
+ const part = expression[expressionIndex];
449
+
450
+ if (part?.xpr) {
451
+ const xpr = { xpr: [] };
452
+ for (let i = 0; i < part.xpr.length; i++)
453
+ i = processExpressionPart(part.xpr, path.concat(expressionIndex, 'xpr'), i, xpr.xpr);
454
+
455
+ collector.push(xpr);
456
+ return expressionIndex;
457
+ }
433
458
 
434
459
  // we can only resolve stuff on refs - skip literals like =
435
460
  // but also keep along stuff like null and undefined, so compiler
436
461
  // can have a chance to complain/ we can fail later nicely maybe
437
462
  if (!(part && part.ref)) {
438
- whereExtension.push(part);
439
- continue;
463
+ collector.push(part);
464
+ return expressionIndex;
440
465
  }
441
466
 
442
467
  // root.$path should be safe - we can only reference things in exists that exist when we enrich
443
468
  // so all of them should have a $path.
444
- const { art, links } = inspectRef(root.$path.concat([ 'on', j ]));
469
+ const { art, links } = inspectRef(path.concat(expressionIndex));
445
470
  // Dollar Self Backlink
446
- if (isValidDollarSelf(root.on[j], root.$path.concat([ 'on', j ]), root.on[j + 1], root.on[j + 2], root.$path.concat([ 'on', j + 2 ]))) {
447
- if (root.on[j].ref[0] === '$self' && root.on[j].ref.length === 1)
448
- whereExtension.push(...translateDollarSelfToWhere(base, target, root.on[j + 2], root.$path.concat([ 'on', j + 2 ])));
471
+ if (isValidDollarSelf(expression[expressionIndex], path.concat(expressionIndex), expression[expressionIndex + 1], expression[expressionIndex + 2], path.concat(expressionIndex + 2 ))) {
472
+ if (expression[expressionIndex].ref[0] === '$self' && expression[expressionIndex].ref.length === 1)
473
+ collector.push(...translateDollarSelfToWhere(base, target, expression[expressionIndex + 2], path.concat(expressionIndex + 2 )));
449
474
  else
450
- whereExtension.push(...translateDollarSelfToWhere(base, target, root.on[j], root.$path.concat([ 'on', j ])));
475
+ collector.push(...translateDollarSelfToWhere(base, target, expression[expressionIndex], path.concat(expressionIndex)));
451
476
 
452
- j += 2;
477
+ return expressionIndex + 2;
453
478
  }
454
479
  else if (links && links[0].art === root) { // target side
455
- whereExtension.push({ ref: [ target, ...part.ref.slice(1) ] });
480
+ collector.push({ ref: [ target, ...part.ref.slice(1) ] });
456
481
  }
457
482
  else if (part.$scope === '$self') { // source side - "absolute" scope
458
483
  const column = part._art._column;
459
484
  if (column && column.as) { // Replace with the "original" expression (the .ref, .xpr etc.)
460
- whereExtension.push(translateToSourceSide(column));
485
+ collector.push(translateToSourceSide(column));
461
486
  }
462
487
  else {
463
- whereExtension.push(assignAndDeleteAs({}, part, { ref: [ base, ...part.ref.slice(1) ] }));
488
+ collector.push(assignAndDeleteAs({}, part, { ref: [ base, ...part.ref.slice(1) ] }));
464
489
  }
465
490
  }
466
491
  else if (art) { // source side - with local scope
467
492
  if (isPrefixedWithTableAlias || part.$scope === 'alias')
468
- whereExtension.push({ ref: [ ...current.ref.slice(0, -1), ...part.ref ] });
493
+ collector.push({ ref: [ ...current.ref.slice(0, -1), ...part.ref ] });
469
494
  else
470
- whereExtension.push({ ref: [ base, ...current.ref.slice(0, -1), ...part.ref ] });
495
+ collector.push({ ref: [ base, ...current.ref.slice(0, -1), ...part.ref ] });
471
496
  }
472
497
  else { // operator - or any other leftover
473
- whereExtension.push(part);
498
+ collector.push(part);
474
499
  }
475
- }
476
500
 
477
- return whereExtension;
501
+ return expressionIndex;
502
+ }
478
503
 
479
504
 
480
505
  /**
@@ -75,7 +75,7 @@ function effectiveCsn( model, options, messageFunctions ) {
75
75
  // ensure getElement works on flattened struct_assoc columns
76
76
  csnUtils = getUtils(csn, 'init-all');
77
77
 
78
- processCalculatedElementsInEntities(csn);
78
+ processCalculatedElementsInEntities(csn, options);
79
79
  associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
80
80
  associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
81
81
  const transformers = mergeTransformers([ options.addCdsPersistenceName ? misc.attachPersistenceName(csn, options, csnUtils) : {}, options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {}, misc.removeDefinitionsAndProperties(csn, options) ], null);
@@ -248,7 +248,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
248
248
  }
249
249
  });
250
250
 
251
- processCalculatedElementsInEntities(csn);
251
+ processCalculatedElementsInEntities(csn, options);
252
252
 
253
253
  timetrace.start('Transform CSN')
254
254
 
@@ -817,16 +817,17 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
817
817
  function checkTypeParameters(artifact, artifactName) {
818
818
  forEachMemberRecursively(artifact, (member, memberName, prop, path) => {
819
819
  // Check type parameters (length, precision, scale ...)
820
- if (!member.$ignore && member.type)
821
- _check(member, memberName, csn, path);
822
-
823
- if (!member.$ignore && member.items && member.items.type)
824
- _check(member.items, memberName, csn, path.concat([ 'items' ]));
820
+ if (!member.$ignore) {
821
+ if (member.type)
822
+ _check(member, memberName, csn, path);
823
+ if (member.items?.type)
824
+ _check(member.items, memberName, csn, path.concat([ 'items' ]));
825
+ }
825
826
  }, [ 'definitions', artifactName ]);
826
827
 
827
828
  // Check that required actual parameters on 'node.type' are set, that their values are in the correct range etc.
828
829
  function _check(node, nodeName, model, path) {
829
- if (node.type) {
830
+ if (node.type && !node.virtual) {
830
831
  const absolute = node.type;
831
832
  switch (absolute) {
832
833
  case 'cds.String':
@@ -579,7 +579,7 @@ function checkFileCase( dep, realpath, nativeRealpath, { warning } ) {
579
579
  }
580
580
  for (const using of dep.usingFroms) {
581
581
  warning('file-unexpected-case-mismatch', [ using.location, using ], {},
582
- // eslint-disable-next-line max-len
582
+ // eslint-disable-next-line @stylistic/js/max-len
583
583
  'The imported filename differs on the filesystem; ensure that capitalization matches the actual file\'s name');
584
584
  }
585
585
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "5.2.0",
3
+ "version": "5.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)",
@@ -53,9 +53,12 @@ CrossJoin
53
53
 
54
54
  ## How to Fix
55
55
 
56
- Ensure that the redirected association points to an entity that is a reasonable
57
- redirection target. That means, the redirection target shouldn't accidentally
58
- make it a to-many association.
56
+ First, ensure that the redirected association points to an entity that is
57
+ a reasonable redirection target. That means, the redirection target shouldn't
58
+ accidentally make it a to-many association.
59
+
60
+ Then add an explicit ON-condition or explicit foreign keys to the redirected
61
+ association. That will silence the compiler message.
59
62
 
60
63
  ## Related Messages
61
64