@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
@@ -99,6 +99,7 @@ function resolve( model ) {
99
99
  Object.assign( model.$functions, {
100
100
  resolveExpr,
101
101
  addForeignKeyNavigations,
102
+ redirectionChain,
102
103
  } );
103
104
 
104
105
  const ignoreSpecifiedElements
@@ -321,7 +322,7 @@ function resolve( model ) {
321
322
  propagateKeys = false;
322
323
  info( 'query-from-many', [ toMany.location, query ], { art: toMany }, {
323
324
  std: 'Key properties are not propagated because a to-many association $(ART) is selected',
324
- // eslint-disable-next-line max-len
325
+ // eslint-disable-next-line @stylistic/js/max-len
325
326
  element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
326
327
  } );
327
328
  }
@@ -362,9 +363,9 @@ function resolve( model ) {
362
363
  // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
363
364
  info( 'query-navigate-many', [ art.location, user || query ], { art }, {
364
365
  std: 'Navigating along to-many association $(ART) - key properties are not propagated',
365
- // eslint-disable-next-line max-len
366
+ // eslint-disable-next-line @stylistic/js/max-len
366
367
  element: 'Navigating along to-many association $(MEMBER) of $(ART) - key properties are not propagated',
367
- // eslint-disable-next-line max-len
368
+ // eslint-disable-next-line @stylistic/js/max-len
368
369
  alias: 'Navigating along to-many mixin association $(MEMBER) - key properties are not propagated',
369
370
  } );
370
371
  }
@@ -400,9 +401,6 @@ function resolve( model ) {
400
401
  function resolveRefs( art ) {
401
402
  if (art.builtin)
402
403
  return;
403
- // console.log(info( null, [ art.location, art ], {}, 'REFS').toString());
404
- // console.log(info( null, [ art.location, art ], { art: art.target || 'none' },
405
- // 'RR: $(ART)').toString());
406
404
  const parent = art._parent;
407
405
  const allowedInMain = [ 'entity', 'aspect', 'event' ].includes( adHocOrMainKind( art ) );
408
406
  const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
@@ -518,8 +516,6 @@ function resolve( model ) {
518
516
  }
519
517
  }
520
518
  if (obj.target) {
521
- // console.log(error( 'test-target', [ obj.location, obj ],
522
- // { target: obj.target, kind: obj.kind }, 'Target: $(TARGET), Kind $(KIND)'));
523
519
  if (!obj.target.$inferred || obj.target.$inferred === 'aspect-composition')
524
520
  resolveTarget( art, obj );
525
521
  else
@@ -535,7 +531,6 @@ function resolve( model ) {
535
531
  resolvePath( art.targetElement, 'targetElement', art );
536
532
 
537
533
  // Resolve projections/views
538
- // if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );
539
534
 
540
535
  if (art.$queries)
541
536
  art.$queries.forEach( resolveQuery );
@@ -928,14 +923,12 @@ function resolve( model ) {
928
923
  // TODO: or set silent dependencies in init?
929
924
  forEachGeneric( query, 'elements', elem => dependsOnSilent( query, elem ) );
930
925
  forEachGeneric( query, '$tableAliases', ( alias ) => {
931
- // console.log( info( null, [alias.location,alias], 'SQA:' ).toString() );
932
926
  if (alias.kind === 'mixin')
933
927
  resolveRefs( alias ); // mixin element
934
928
  else if (alias.kind !== '$self')
935
929
  // pure path has been resolved, resolve args and filter now:
936
930
  resolveExpr( alias, 'from', query._parent );
937
931
  } );
938
- // if (!query.$inlines) console.log('RQ:',query)
939
932
  for (const col of query.$inlines)
940
933
  resolveExpr( col.value, 'column', col );
941
934
  // for (const col of query.$inlines)
@@ -1095,17 +1088,8 @@ function resolve( model ) {
1095
1088
  issue['#'] = 'order';
1096
1089
  }
1097
1090
  }
1098
- if (issue['#']) {
1099
- /* eslint-disable max-len */
1100
- message( 'type-expecting-service-target', [ art.target.location, art ], issue, {
1101
- std: 'Expecting service entity $(TARGET)',
1102
- ref: 'Expecting service entity $(TARGET); its element $(ID) referred to at line $(LINE), column $(COL) is not from an element with the same name in the provided model target',
1103
- key: 'Expecting service entity $(TARGET); its key element $(ID) is not from a key element with the same name in the provided model target',
1104
- missing: 'Expecting service entity $(TARGET); it does not have the key element $(ID) of the provided model target',
1105
- order: 'Expecting service entity $(TARGET); its key elements are in a different order than those of the provided model target',
1106
- } );
1107
- /* eslint-enable max-len */
1108
- }
1091
+ if (issue['#'])
1092
+ message( 'type-expecting-service-target', [ art.target.location, art ], issue );
1109
1093
  }
1110
1094
 
1111
1095
  function keyElementNames( elements ) {
@@ -1143,7 +1127,7 @@ function resolve( model ) {
1143
1127
  function addImplicitForeignKeys( art, obj, target ) {
1144
1128
  obj.foreignKeys = Object.create( null );
1145
1129
  forEachInOrder( target, 'elements', ( elem, name ) => {
1146
- if (elem.key && elem.key.val) {
1130
+ if (elem.key?.val) {
1147
1131
  const location = weakLocation( obj.target.location );
1148
1132
  const key = {
1149
1133
  name: { location, id: elem.name.id, $inferred: 'keys' },
@@ -1228,7 +1212,7 @@ function resolve( model ) {
1228
1212
  const text = (item !== last) ? 'sub' : 'std';
1229
1213
  error( 'duplicate-key-ref', [ item.location, key ], { '#': text, name }, {
1230
1214
  std: 'Foreign key $(NAME) already refers to the same target element',
1231
- // eslint-disable-next-line max-len
1215
+ // eslint-disable-next-line @stylistic/js/max-len
1232
1216
  sub: 'Foreign key $(NAME) already refers to the target element whose sub element is again referred to here',
1233
1217
  // TODO: please add ideas for a better text, e.g. to (closed) PR #11325
1234
1218
  } );
@@ -1246,9 +1230,8 @@ function resolve( model ) {
1246
1230
  const origType = assoc && effectiveType( assoc );
1247
1231
  if (origType === 0)
1248
1232
  return;
1249
- if (!origType || !origType.target) {
1250
- const path = (elem.value && elem.value.path);
1251
- const loc = (path && path[path.length - 1] || elem.value || elem).location;
1233
+ if (!origType?.target) {
1234
+ const loc = (elem.value?.path?.at(-1) || elem.value || elem).location;
1252
1235
  error( 'redirected-no-assoc', [ loc, elem ], {},
1253
1236
  'Only an association can be redirected' );
1254
1237
  return;
@@ -1258,52 +1241,68 @@ function resolve( model ) {
1258
1241
  error( 'type-invalid-cast', [ elem.type.location, elem ], { '#': 'assoc' } );
1259
1242
  return;
1260
1243
  }
1244
+
1261
1245
  const origTarget = origType.target._artifact;
1262
- if (!origTarget || !target)
1263
- return;
1264
1246
 
1265
- const chain = [];
1266
- if (target === origTarget) {
1267
- if (!elem.target.$inferred && !elem.on && !elem.foreignKeys) {
1268
- // Only a managed redirection gets this info message. Because otherwise
1269
- // we'd have to check whether on-condition/foreignKeys are the same.
1270
- // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
1271
- info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
1272
- 'The redirected target is the original $(ART)' );
1273
- }
1274
- setLink( elem, '_redirected', chain ); // store the chain
1275
- return;
1247
+ if (target === origTarget && !elem.target.$inferred && !elem.on && !elem.foreignKeys) {
1248
+ // Only a managed redirection gets this info message. Because otherwise
1249
+ // we'd have to check whether on-condition/foreignKeys are the same.
1250
+ // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
1251
+ info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
1252
+ 'The redirected target is the original $(ART)' );
1276
1253
  }
1254
+
1277
1255
  if (elem.foreignKeys || elem.on)
1278
1256
  return; // TODO: or should we still bring an msg if nothing in common?
1257
+
1258
+ const chain = redirectionChain( elem, target, origTarget );
1259
+ setLink( elem, '_redirected', chain );
1260
+ }
1261
+
1262
+ /**
1263
+ * Get the redirection chain between a target and the original target.
1264
+ *
1265
+ * @param {XSN.Artifact} elem
1266
+ * @param {XSN.Artifact} target
1267
+ * @param {XSN.Artifact} origTarget
1268
+ * @param {boolean} [silent] Whether to report error and other messages.
1269
+ * @returns {XSN.Artifact[]|null}
1270
+ */
1271
+ function redirectionChain( elem, target, origTarget, silent = false ) {
1272
+ if (!origTarget || !target)
1273
+ return null;
1274
+ if (target === origTarget)
1275
+ return []; // e.g. explicit ON-condition/foreign keys or original target
1276
+
1277
+ const chain = [];
1279
1278
  // now check whether target and origTarget are "related"
1279
+ // first: check via simple projections
1280
1280
  while (target.query) {
1281
1281
  const from = target.query.args ? {} : target.query.from;
1282
1282
  if (!from)
1283
- return; // parse error - TODO: or UNION?
1283
+ return null; // parse error - TODO: or UNION?
1284
1284
  if (!from.path) {
1285
+ if (silent)
1286
+ break;
1285
1287
  const isTarget = target === elem.target._artifact;
1286
1288
  const op = from.op?.val || target.query.op?.val;
1287
1289
  const variant = (!isTarget && 'std') || (op && 'targetOp') || 'target';
1288
- // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
1289
1290
  info( 'redirected-to-complex', [ elem.target.location, elem ],
1290
- { art: target, '#': variant, keyword: op || '' }, {
1291
- std: 'Redirection involves the complex view $(ART)',
1292
- target: 'The redirected target $(ART) is a complex view',
1293
- targetOp: 'The redirected target $(ART) is a complex view with $(KEYWORD)',
1294
- } );
1291
+ { art: target, '#': variant, keyword: op || '' } );
1295
1292
  break;
1296
1293
  }
1297
1294
  target = from._artifact;
1298
1295
  if (!target)
1299
- return;
1296
+ return null;
1300
1297
  chain.push( from );
1301
1298
  if (target === origTarget) {
1299
+ // found in simple projection chain
1302
1300
  chain.reverse();
1303
- setLink( elem, '_redirected', chain );
1304
- return;
1301
+ return chain;
1305
1302
  }
1306
1303
  }
1304
+
1305
+ // there is a complex view in-between; search through table aliases
1307
1306
  let redirected = null;
1308
1307
  chain.reverse();
1309
1308
  let news = [ { chain, sources: [ target ] } ];
@@ -1321,25 +1320,25 @@ function resolve( model ) {
1321
1320
  else if (!redirected) {
1322
1321
  redirected = (s.kind === '$tableAlias') ? [ s, ...o.chain ] : o.chain;
1323
1322
  }
1324
- else {
1323
+ else if (!silent) {
1325
1324
  error( 'redirected-to-ambiguous', [ elem.target.location, elem ], { art: origTarget },
1326
1325
  'The redirected target originates more than once from $(ART)' );
1327
- return;
1326
+ return null;
1327
+ }
1328
+ else {
1329
+ return null;
1328
1330
  }
1329
1331
  }
1330
1332
  }
1331
1333
  }
1332
- if (redirected) {
1333
- setLink( elem, '_redirected', redirected );
1334
- }
1335
- else if (redirected == null) {
1334
+ if (!silent && redirected == null) {
1336
1335
  error( 'redirected-to-unrelated', [ elem.target.location, elem ], { art: origTarget },
1337
1336
  'The redirected target does not originate from $(ART)' );
1338
1337
  }
1339
- return;
1338
+ return redirected;
1340
1339
 
1341
1340
  // B = proj on A, C = A x B, X = { a: assoc to A on a.Q1 = ...}, Y = X.{ a: redirected to C }
1342
- // what does a: redirected to C means?
1341
+ // what does 'a: redirected to C' mean?
1343
1342
  // -> collect all elements Qi used in ON (corr: foreign keys)
1344
1343
  // -> only use an tableAlias which has propagation for all elements
1345
1344
  // no - error if the original target can be reached twice
@@ -1430,7 +1429,6 @@ function resolve( model ) {
1430
1429
  }
1431
1430
 
1432
1431
  function resolveExpr( expr, exprCtx, user ) {
1433
- // console.log(expr?.location.line,exprCtx)
1434
1432
  traverseExpr( expr, exprCtx, user, resolveExprItem );
1435
1433
  }
1436
1434
 
@@ -1507,7 +1505,7 @@ function resolve( model ) {
1507
1505
  message( 'expr-unexpected-filter', [ location, user ], { '#': variant }, {
1508
1506
  std: 'A filter can only be provided when navigating along associations',
1509
1507
  // to help users for `… from E:toF { toF[…].x }`
1510
- // eslint-disable-next-line max-len
1508
+ // eslint-disable-next-line @stylistic/js/max-len
1511
1509
  tableAlias: 'A filter can only be provided when navigating along associations, but found table alias',
1512
1510
  from: 'A filter can only be provided for the source entity or associations',
1513
1511
  } );
@@ -1225,7 +1225,7 @@ function fns( model ) {
1225
1225
  // of invisible table aliases; at least one stakeholder uses this,
1226
1226
  // so it can't be an error (yet).
1227
1227
  message( 'ref-deprecated-self-element', [ ref.path[0].location, user._user ], {},
1228
- // eslint-disable-next-line max-len
1228
+ // eslint-disable-next-line @stylistic/js/max-len
1229
1229
  'Referring to the query\'s own elements here might lead to invalid SQL references; use source elements only' );
1230
1230
  return false;
1231
1231
  default:
@@ -1617,7 +1617,7 @@ function fns( model ) {
1617
1617
  else if (target._artifact && target._artifact !== user._main && user._main.kind === 'entity') {
1618
1618
  const last = path[path.length - 1];
1619
1619
  warning( 'ref-invalid-backlink', [ last.location, user ], { art: target, id: '$self' },
1620
- // eslint-disable-next-line max-len
1620
+ // eslint-disable-next-line @stylistic/js/max-len
1621
1621
  'The target $(ART) of the association is not the current entity represented by $(ID)' );
1622
1622
  }
1623
1623
  }
@@ -1707,11 +1707,11 @@ function fns( model ) {
1707
1707
  std: 'Can follow association $(ART) only to its foreign key references, not to $(NAME)',
1708
1708
  keys: 'Can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
1709
1709
  complete: 'The reference must cover a full foreign key reference of association $(ART)',
1710
- // eslint-disable-next-line max-len
1710
+ // eslint-disable-next-line @stylistic/js/max-len
1711
1711
  'self-std': 'In column ref starting with $(ALIAS), we can follow association $(ART) only to its foreign key references, not to $(NAME)',
1712
- // eslint-disable-next-line max-len
1712
+ // eslint-disable-next-line @stylistic/js/max-len
1713
1713
  'self-keys': 'In column ref starting with $(ALIAS), we can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
1714
- // eslint-disable-next-line max-len
1714
+ // eslint-disable-next-line @stylistic/js/max-len
1715
1715
  'self-complete': 'The column reference starting with $(ALIAS) must cover a full foreign key reference of association $(ART)',
1716
1716
  } );
1717
1717
  // TODO later: mention allowed ones