@sap/cds-compiler 3.8.2 → 3.9.4

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 (82) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +26 -5
  4. package/lib/api/.eslintrc.json +3 -2
  5. package/lib/api/options.js +3 -1
  6. package/lib/api/validate.js +1 -1
  7. package/lib/base/message-registry.js +28 -19
  8. package/lib/base/messages.js +6 -1
  9. package/lib/base/model.js +2 -2
  10. package/lib/checks/.eslintrc.json +1 -0
  11. package/lib/checks/actionsFunctions.js +6 -6
  12. package/lib/checks/annotationsOData.js +1 -1
  13. package/lib/checks/elements.js +28 -17
  14. package/lib/checks/foreignKeys.js +1 -1
  15. package/lib/checks/invalidTarget.js +1 -1
  16. package/lib/checks/onConditions.js +11 -6
  17. package/lib/checks/queryNoDbArtifacts.js +1 -1
  18. package/lib/checks/types.js +1 -1
  19. package/lib/checks/utils.js +1 -1
  20. package/lib/checks/validator.js +3 -2
  21. package/lib/compiler/assert-consistency.js +7 -2
  22. package/lib/compiler/base.js +8 -4
  23. package/lib/compiler/builtins.js +7 -0
  24. package/lib/compiler/checks.js +73 -6
  25. package/lib/compiler/define.js +10 -5
  26. package/lib/compiler/extend.js +910 -1711
  27. package/lib/compiler/finalize-parse-cdl.js +1 -1
  28. package/lib/compiler/generate.js +838 -0
  29. package/lib/compiler/index.js +2 -0
  30. package/lib/compiler/populate.js +2 -2
  31. package/lib/compiler/propagator.js +20 -8
  32. package/lib/compiler/resolve.js +3 -3
  33. package/lib/compiler/shared.js +3 -1
  34. package/lib/edm/annotations/genericTranslation.js +18 -8
  35. package/lib/edm/csn2edm.js +14 -14
  36. package/lib/edm/edm.js +25 -11
  37. package/lib/edm/edmPreprocessor.js +47 -23
  38. package/lib/edm/edmUtils.js +37 -9
  39. package/lib/gen/Dictionary.json +5 -7
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +3 -1
  42. package/lib/gen/language.tokens +24 -23
  43. package/lib/gen/languageLexer.interp +4 -1
  44. package/lib/gen/languageLexer.js +792 -784
  45. package/lib/gen/languageLexer.tokens +12 -11
  46. package/lib/gen/languageParser.js +3564 -3493
  47. package/lib/json/from-csn.js +28 -6
  48. package/lib/json/to-csn.js +10 -6
  49. package/lib/language/antlrParser.js +11 -3
  50. package/lib/language/genericAntlrParser.js +2 -1
  51. package/lib/language/language.g4 +14 -3
  52. package/lib/model/csnRefs.js +10 -5
  53. package/lib/model/csnUtils.js +41 -76
  54. package/lib/modelCompare/utils/.eslintrc.json +1 -1
  55. package/lib/optionProcessor.js +7 -4
  56. package/lib/render/.eslintrc.json +1 -1
  57. package/lib/render/toCdl.js +244 -168
  58. package/lib/render/toHdbcds.js +18 -10
  59. package/lib/render/toSql.js +24 -2
  60. package/lib/transform/db/.eslintrc.json +4 -3
  61. package/lib/transform/db/cdsPersistence.js +1 -1
  62. package/lib/transform/db/expansion.js +11 -6
  63. package/lib/transform/db/flattening.js +22 -15
  64. package/lib/transform/db/rewriteCalculatedElements.js +50 -29
  65. package/lib/transform/db/temporal.js +1 -1
  66. package/lib/transform/db/views.js +1 -1
  67. package/lib/transform/draft/db.js +1 -1
  68. package/lib/transform/draft/odata.js +3 -4
  69. package/lib/transform/forOdataNew.js +5 -6
  70. package/lib/transform/forRelationalDB.js +7 -7
  71. package/lib/transform/localized.js +1 -1
  72. package/lib/transform/odata/toFinalBaseType.js +6 -6
  73. package/lib/transform/odata/typesExposure.js +12 -3
  74. package/lib/transform/odata/utils.js +3 -0
  75. package/lib/transform/transformUtilsNew.js +11 -26
  76. package/lib/transform/translateAssocsToJoins.js +9 -9
  77. package/lib/transform/universalCsn/.eslintrc.json +3 -2
  78. package/lib/transform/universalCsn/coreComputed.js +1 -1
  79. package/lib/transform/universalCsn/universalCsnEnricher.js +6 -4
  80. package/lib/utils/file.js +3 -3
  81. package/lib/utils/moduleResolve.js +1 -1
  82. package/package.json +1 -1
@@ -21,7 +21,7 @@ const { makeMessageFunction } = require('../base/messages');
21
21
  const { timetrace } = require('../utils/timetrace');
22
22
 
23
23
  const { smartId, delimitedId } = require('../sql-identifier');
24
- const { ModelError } = require('../base/error');
24
+ const { ModelError, CompilerAssertion } = require('../base/error');
25
25
 
26
26
  const $PROJECTION = '$projection';
27
27
  const $SELF = '$self';
@@ -60,7 +60,7 @@ function toHdbcdsSource( csn, options ) {
60
60
  const hdbcdsNames = options.sqlMapping === 'hdbcds';
61
61
 
62
62
  const {
63
- info, warning, error, throwWithAnyError,
63
+ info, warning, error, throwWithAnyError, message,
64
64
  } = makeMessageFunction(csn, options, 'to.hdbcds');
65
65
 
66
66
  const exprRenderer = createExpressionRenderer({
@@ -1051,7 +1051,7 @@ function toHdbcdsSource( csn, options ) {
1051
1051
  result += (elm.localized ? 'localized ' : '');
1052
1052
 
1053
1053
  // Anonymous structured type
1054
- if (!elm.type) {
1054
+ if (!elm.type && !elm.value) {
1055
1055
  if (!elm.elements)
1056
1056
  throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
1057
1057
 
@@ -1070,14 +1070,14 @@ function toHdbcdsSource( csn, options ) {
1070
1070
  if ([ 'cds.Association', 'cds.Composition' ].includes(elm.type))
1071
1071
  return result + renderAssociationType(elm, env);
1072
1072
 
1073
- // Reference to another element
1074
- if (elm.type.ref) {
1073
+
1074
+ if (elm.type?.ref) {
1075
+ // Reference to another element
1075
1076
  // For HANA CDS, we need a 'type of'
1076
- return `type of ${renderAbsolutePath(elm.type, env)}`;
1077
+ result += `type of ${renderAbsolutePath(elm.type, env)}`;
1077
1078
  }
1078
-
1079
- // If we get here, it must be a named type
1080
- if (isBuiltinType(elm.type)) {
1079
+ else if (isBuiltinType(elm.type)) {
1080
+ // If we get here, it must be a named type
1081
1081
  result += renderBuiltinType(elm);
1082
1082
  }
1083
1083
  else {
@@ -1086,6 +1086,14 @@ function toHdbcdsSource( csn, options ) {
1086
1086
  result += renderAbsoluteNameWithQuotes(elm.type, env);
1087
1087
  }
1088
1088
 
1089
+ if (elm.value) {
1090
+ if (!elm.value.stored)
1091
+ throw new CompilerAssertion('Found calculated element on-read in rendering; should have been replaced!');
1092
+ message('def-unsupported-calc-elem', env.path, { '#': 'hdbcds' });
1093
+ result += ` GENERATED ALWAYS AS ${renderExpr(elm.value)}`;
1094
+ return result;
1095
+ }
1096
+
1089
1097
  return result;
1090
1098
  }
1091
1099
 
@@ -1282,7 +1290,7 @@ function toHdbcdsSource( csn, options ) {
1282
1290
  else if (x.ref[1] === 'locale')
1283
1291
  return 'SESSION_CONTEXT(\'LOCALE\')';
1284
1292
  }
1285
- else if (x.ref[0] === '$at') {
1293
+ else if (x.ref[0] === '$at' || x.ref[0] === '$valid') {
1286
1294
  if (x.ref[1] === 'from')
1287
1295
  return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
1288
1296
 
@@ -1250,7 +1250,7 @@ function toSqlDdl( csn, options ) {
1250
1250
  let result = '';
1251
1251
 
1252
1252
  // Anonymous structured type: Not supported with SQL (but shouldn't happen anyway after forHana flattened them)
1253
- if (!elm.type) {
1253
+ if (!elm.type && !elm.value) {
1254
1254
  if (!elm.elements)
1255
1255
  throw new ModelError(`Missing type of: ${elementName}`);
1256
1256
 
@@ -1278,6 +1278,20 @@ function toSqlDdl( csn, options ) {
1278
1278
  throw new ModelError(`Unexpected non-primitive type of: ${artifactName}.${elementName}`);
1279
1279
  }
1280
1280
  result += renderTypeParameters(elm);
1281
+
1282
+ if (elm.value) {
1283
+ if (!elm.value.stored)
1284
+ throw new CompilerAssertion('Found calculated element on-read in rendering; should have been replaced!');
1285
+ // TODO: Properly implement calculated elements on-write:
1286
+ // The SQL standard 2016 describes the syntax in section 11.3 - 11.4
1287
+ // of the SQL Foundation spec (for 2003 in 5WD-02-Foundation-2003-09.pdf). Summarized:
1288
+ // <generation clause> ::= GENERATED ALWAYS AS '(' <value expression> ')'
1289
+ result += ` GENERATED ALWAYS AS (${renderExpr(elm.value)})`;
1290
+ // However, it appears many databases require a trailing "STORED".
1291
+ if (options.sqlDialect === 'sqlite' || options.sqlDialect === 'postgres')
1292
+ result += ' STORED';
1293
+ return result;
1294
+ }
1281
1295
  return result;
1282
1296
  }
1283
1297
 
@@ -1397,7 +1411,7 @@ function toSqlDdl( csn, options ) {
1397
1411
  if (result)
1398
1412
  return result;
1399
1413
  }
1400
- else if (x.ref[0] === '$at') {
1414
+ else if (x.ref[0] === '$at' || x.ref[0] === '$valid') {
1401
1415
  const result = render$at();
1402
1416
  // Invalid second path step doesn't cause a return
1403
1417
  if (result)
@@ -1444,6 +1458,8 @@ function toSqlDdl( csn, options ) {
1444
1458
  return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
1445
1459
  else if (options.sqlDialect === 'postgres')
1446
1460
  return 'current_setting(\'CAP.APPLICATIONUSER\')';
1461
+ else if (options.betterSqliteSessionVariables && options.sqlDialect === 'sqlite')
1462
+ return 'session_context( \'$user.id\' )';
1447
1463
  warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
1448
1464
  return '\'$user.id\'';
1449
1465
  }
@@ -1452,6 +1468,8 @@ function toSqlDdl( csn, options ) {
1452
1468
  return 'SESSION_CONTEXT(\'LOCALE\')';
1453
1469
  else if (options.sqlDialect === 'postgres')
1454
1470
  return 'current_setting(\'CAP.LOCALE\')';
1471
+ else if (options.betterSqliteSessionVariables && options.sqlDialect === 'sqlite')
1472
+ return 'session_context( \'$user.locale\' )';
1455
1473
  return '\'en\''; // default language
1456
1474
  }
1457
1475
  // Basically: Second path step was invalid, do nothing - should not happen.
@@ -1473,6 +1491,8 @@ function toSqlDdl( csn, options ) {
1473
1491
  if (x.ref[1] === 'from') {
1474
1492
  switch (options.sqlDialect) {
1475
1493
  case 'sqlite': {
1494
+ if (options.betterSqliteSessionVariables)
1495
+ return 'session_context( \'$valid.from\' )';
1476
1496
  const dateFromFormat = '%Y-%m-%dT%H:%M:%S.000Z';
1477
1497
  return `strftime('${dateFromFormat}', 'now')`;
1478
1498
  }
@@ -1491,6 +1511,8 @@ function toSqlDdl( csn, options ) {
1491
1511
  if (x.ref[1] === 'to') {
1492
1512
  switch (options.sqlDialect) {
1493
1513
  case 'sqlite': {
1514
+ if (options.betterSqliteSessionVariables)
1515
+ return 'session_context( \'$valid.to\' )';
1494
1516
  // + 1ms compared to $at.from
1495
1517
  const dateToFormat = '%Y-%m-%dT%H:%M:%S.001Z';
1496
1518
  return `strftime('${dateToFormat}', 'now')`;
@@ -21,14 +21,15 @@
21
21
  "sonarjs/cognitive-complexity": "off",
22
22
  "sonarjs/no-duplicate-string": "off",
23
23
  // Does not recognize TS types
24
- "jsdoc/no-undefined-types": "off"
24
+ "jsdoc/no-undefined-types": "off",
25
+ "jsdoc/tag-lines": "off"
25
26
  },
26
27
  "parserOptions": {
27
- "ecmaVersion": 2020,
28
+ "ecmaVersion": 2022,
28
29
  "sourceType": "script"
29
30
  },
30
31
  "env": {
31
- "es2020": true,
32
+ "es2022": true,
32
33
  "node": true
33
34
  },
34
35
  "settings": {
@@ -82,7 +82,7 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
82
82
  function ignore( member, memberName, prop, path ) {
83
83
  if (options.sqlDialect === 'hana' &&
84
84
  !member._ignore && member.target &&
85
- isAssocOrComposition(member.type) &&
85
+ isAssocOrComposition(member) &&
86
86
  !isPersistedOnDatabase(csn.definitions[member.target])) {
87
87
  info(null, path,
88
88
  { target: member.target, anno: '@cds.persistence.skip' },
@@ -26,7 +26,7 @@ const { forEach } = require('../../utils/objectUtils');
26
26
  */
27
27
  function expandStructureReferences( csn, options, pathDelimiter, { error, info, throwWithAnyError }, csnUtils, iterateOptions = {} ) {
28
28
  const {
29
- isStructured, get$combined, getFinalBaseTypeWithProps,
29
+ isStructured, get$combined, getFinalTypeInfo,
30
30
  } = csnUtils;
31
31
  let { effectiveType, inspectRef } = csnUtils;
32
32
 
@@ -76,6 +76,10 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
76
76
  // We can directly use SELECT here, as only projections and SELECT can have .columns
77
77
  const root = get$combined({ SELECT: parent });
78
78
  if (!hasAnnotationValue(artifact, '@cds.persistence.table')) {
79
+ // Make root look like normal .elements - we never cared about conflict afaik anyway
80
+ Object.keys(root).forEach((key) => {
81
+ root[key] = root[key][0].element;
82
+ });
79
83
  const rewritten = rewrite(root, parent.columns, parent.excluding);
80
84
  /*
81
85
  * Do not remove unexpandable many columns in OData
@@ -240,7 +244,7 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
240
244
  */
241
245
  function nextBase( parent, base ) {
242
246
  if (parent.ref) {
243
- const finalBaseType = getFinalBaseTypeWithProps(parent._art.type);
247
+ const finalBaseType = getFinalTypeInfo(parent._art.type);
244
248
  const art = parent._art;
245
249
 
246
250
  if (finalBaseType && (finalBaseType.type === 'cds.Association' || finalBaseType.type === 'cds.Composition'))
@@ -306,11 +310,11 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
306
310
 
307
311
  /**
308
312
  * Rewrite the expand/inline. For expand, keep along the alias - for inline, only leaf-alias has effect.
309
- * Expand * into the corresponding leaves - correctly handling .exlcluding and shadowing.
313
+ * Expand * into the corresponding leaves - correctly handling .excluding and shadowing.
310
314
  *
311
315
  * Iterative, to not run into stack overflow.
312
316
  *
313
- * @param {CSN.Artifact} root All elements visible fromt he query source ($combined)
317
+ * @param {CSN.Artifact} root All elements visible from the query source ($combined)
314
318
  * @param {CSN.Column} col Column to expand
315
319
  * @param {Array} ref Ref so far
316
320
  * @param {Array} alias Any start-alias
@@ -361,8 +365,9 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
361
365
  expanded.push(Object.assign({}, current, { as: currentAlias.join(pathDelimiter) } ));
362
366
  }
363
367
  else { // preserve stuff like .cast for redirection
364
- if (base[currentAlias[currentAlias.length - 1]]?.value)
365
- error('query-unsupported-calc', col.$path, { '#': 'inside' });
368
+ const thing = base[currentAlias[currentAlias.length - 1]];
369
+ if (current?._art?.value || thing?.value)
370
+ error('query-unsupported-calc', current.$path || col.$path, { '#': 'inside' });
366
371
  expanded.push(Object.assign({}, current, { ref: currentRef, as: currentAlias.join(pathDelimiter) } ));
367
372
  }
368
373
  }
@@ -9,6 +9,7 @@ const transformUtils = require('../transformUtilsNew');
9
9
  const { csnRefs } = require('../../model/csnRefs');
10
10
  const { setProp } = require('../../base/model');
11
11
  const { forEach } = require('../../utils/objectUtils');
12
+ const { cardinality2str } = require('../../model/csnUtils');
12
13
 
13
14
  /**
14
15
  * Strip off leading $self from refs where applicable
@@ -67,7 +68,7 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
67
68
  }
68
69
  }
69
70
  const { toFinalBaseType, csnUtils } = transformUtils.getTransformers(csn, options, pathDelimiter);
70
- const { getServiceName, getFinalBaseTypeWithProps } = csnUtils;
71
+ const { getServiceName, getFinalTypeInfo } = csnUtils;
71
72
 
72
73
  // We don't want to iterate over actions
73
74
  if (iterateOptions.skipDict && !iterateOptions.skipDict.actions)
@@ -155,7 +156,7 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
155
156
  return false;
156
157
 
157
158
  const typeServiceName = getServiceName(typeName);
158
- const finalBaseType = getFinalBaseTypeWithProps(typeName)?.type;
159
+ const finalBaseType = getFinalTypeInfo(typeName)?.type;
159
160
  // we need the service of the current definition
160
161
  const currDefServiceName = getServiceName(path[1]);
161
162
 
@@ -313,7 +314,7 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
313
314
  flatElement.notNull = true;
314
315
 
315
316
 
316
- if (flatElement.type && isAssocOrComposition(flatElement.type) && flatElement.on) {
317
+ if (flatElement.type && isAssocOrComposition(flatElement) && flatElement.on) {
317
318
  // Make refs resolvable by fixing the first ref step
318
319
  for (const onPart of flatElement.on) {
319
320
  if (onPart.ref) {
@@ -406,12 +407,13 @@ function getBranches( element, elementName, effectiveType, pathDelimiter ) {
406
407
  * @param {CSN.Model} csn
407
408
  * @param {CSN.Options} options
408
409
  * @param {Function} error
410
+ * @param {Function} warning
409
411
  * @param {string} pathDelimiter
410
412
  * @param {boolean} flattenKeyRefs
411
413
  * @param {object} csnUtils
412
414
  * @param {object} iterateOptions
413
415
  */
414
- function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, pathDelimiter, flattenKeyRefs, csnUtils, iterateOptions = {} ) {
416
+ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, warning, pathDelimiter, flattenKeyRefs, csnUtils, iterateOptions = {} ) {
415
417
  const { isManagedAssociation, inspectRef, isStructured } = csnUtils;
416
418
  const { flattenStructStepsInRef, flattenStructuredElement } = transformUtils.getTransformers(csn, options, pathDelimiter);
417
419
  if (flattenKeyRefs) {
@@ -642,8 +644,13 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, pat
642
644
  if (element.cardinality === undefined)
643
645
  element.cardinality = {};
644
646
  // min=0 is falsy => check for undefined
645
- if (element.cardinality.min === undefined)
647
+ if (element.cardinality.min === undefined) {
646
648
  element.cardinality.min = 1;
649
+ }
650
+ else if (element.cardinality.min === 0) {
651
+ warning(null, element.$path, { value: cardinality2str(element, false), code: 'not null' },
652
+ 'Expected target cardinality $(VALUE) and $(CODE) to match');
653
+ }
647
654
  }
648
655
  }
649
656
  orderedElements.push(...fks);
@@ -724,16 +731,6 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
724
731
  else if (lvl === 0) {
725
732
  return fks;
726
733
  }
727
- // we have reached a leaf element, create a foreign key
728
- else if (finalElement && isBuiltinType(finalElement.type)) {
729
- const newFk = Object.create(null);
730
- for (const prop of [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type' ]) {
731
- // copy props from original element to preserve derived types!
732
- if (element[prop] !== undefined)
733
- newFk[prop] = element[prop];
734
- }
735
- return [ [ prefix, newFk ] ];
736
- }
737
734
  else if (finalElement.elements) {
738
735
  Object.entries(finalElement.elements).forEach(([ elemName, elem ]) => {
739
736
  // Skip already produced foreign keys
@@ -743,6 +740,16 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
743
740
  }
744
741
  });
745
742
  }
743
+ // we have reached a leaf element, create a foreign key
744
+ else if (finalElement && (finalElement.type == null || isBuiltinType(finalElement.type))) {
745
+ const newFk = Object.create(null);
746
+ for (const prop of [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type' ]) {
747
+ // copy props from original element to preserve derived types!
748
+ if (element[prop] !== undefined)
749
+ newFk[prop] = element[prop];
750
+ }
751
+ return [ [ prefix, newFk ] ];
752
+ }
746
753
 
747
754
  /**
748
755
  * Get the path to continue resolving references
@@ -13,6 +13,8 @@ const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '
13
13
  * Rewrite usage of calculated Elements into the expression itself.
14
14
  * Delete calculated elements in entities after processing so they don't materialize on the db.
15
15
  *
16
+ * TODO: Calculated elements on-write (`stored: true`)
17
+ *
16
18
  * @param {CSN.Model} csn
17
19
  * @param {CSN.Options} options
18
20
  * @param {string} pathDelimiter
@@ -37,24 +39,21 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
37
39
  }
38
40
  });
39
41
 
40
- // Replace calculated elements in filters (if the root-association element is in an entity).
42
+ // Replace calculated elements in filters, functions and other places (if the root-association element is in an entity).
41
43
  // Depends on the first pass!
42
44
  entities.forEach(({ artifactName }) => {
43
45
  applyTransformationsOnNonDictionary(csn.definitions, artifactName, {
44
- where: (parent, prop) => {
45
- applyTransformationsOnNonDictionary(parent, prop, {
46
- ref: (_parent, _prop, ref, _path, root, index) => {
47
- if (_parent._art && _parent._art.value) {
48
- root[index] = _parent._art.value;
49
- applyTransformationsOnNonDictionary(root, index, {
50
- ref: (__parent, _, _ref) => {
51
- if (_ref[0] === '$self' || _ref[0] === '$projection')
52
- __parent.ref = _ref.slice(-1);
53
- },
54
- });
55
- }
56
- },
57
- });
46
+ ref: (_parent, _prop, ref, _path, root, index) => {
47
+ if (_parent._art && _parent._art.value) {
48
+ root[index] = _parent._art.value;
49
+ // Note: Depends on A2J rejecting deeply nested filters
50
+ applyTransformationsOnNonDictionary(root, index, {
51
+ ref: (__parent, _, _ref) => {
52
+ if (_ref[0] === '$self' || _ref[0] === '$projection')
53
+ __parent.ref = _ref.slice(-1);
54
+ },
55
+ });
56
+ }
58
57
  },
59
58
  }, { drillRef: true }, [ 'definitions' ]);
60
59
  });
@@ -100,10 +99,11 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
100
99
  */
101
100
  function rewriteInView( SELECT, elements, path ) {
102
101
  const containsExpandInline = hasExpandInline(SELECT);
102
+ let cleanupCallbacks;
103
103
  if (!SELECT.columns) // needs to happen for all subqueries!
104
- calculateColumns(elements, SELECT);
104
+ cleanupCallbacks = calculateColumns(elements, SELECT);
105
105
  else
106
- makeAllCalculatedElementsExplicitColumns(elements, SELECT, containsExpandInline);
106
+ cleanupCallbacks = makeAllCalculatedElementsExplicitColumns(elements, SELECT, containsExpandInline);
107
107
 
108
108
  const name = SELECT.from.args ? undefined : SELECT.from.as || (SELECT.from.ref && implicitAs(SELECT.from.ref));
109
109
 
@@ -114,7 +114,8 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
114
114
  art, env, links, scope,
115
115
  } = getRefInfo(parent, p);
116
116
 
117
- if (art?.value) {
117
+ // TODO: Calculated elements on-write
118
+ if (art?.value && !art.value.stored) {
118
119
  const alias = parent.as || implicitAs(parent.ref);
119
120
  // TODO: What about other scopes? expand/inline?
120
121
  const value = (scope !== 'ref-target') ? absolutifyPaths(env, art, ref, links, name).value : keepAssocStepsInRef(ref, links, art).value;
@@ -129,10 +130,21 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
129
130
  root[p[p.length - 1]].as = alias;
130
131
  else
131
132
  delete root[p[p.length - 1]].as;
133
+
134
+ // If the calculated element has a type, use it. But only if the column did not have an explicit type.
135
+ // Note: We should not check `art.type`, because we only need the type for columns, not filters.
136
+ if (parent.cast)
137
+ root[p[p.length - 1]].cast = parent.cast;
138
+ else if (parent._element?.type)
139
+ root[p[p.length - 1]].cast = { type: parent._element.type };
140
+
141
+ // TODO: Copy annotations? May become relevant in the future
132
142
  }
133
143
  },
134
144
  }, {}, path);
135
145
  }
146
+
147
+ cleanupCallbacks.forEach(fn => fn());
136
148
  }
137
149
 
138
150
  /**
@@ -198,6 +210,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
198
210
  });
199
211
  }
200
212
  else if (current.value.ref && current.value._art?.value) {
213
+ // TODO: Check for calculated elements on-write
201
214
  const linksBase = current.value._links;
202
215
  const refBase = current.value.ref;
203
216
  const parentIndex = Array.isArray(current.parent) ? current.parent.indexOf(current.value) : -1;
@@ -285,9 +298,10 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
285
298
  */
286
299
  function calculateColumns( elements, carrier ) {
287
300
  carrier.columns = [ '*' ];
288
- makeAllCalculatedElementsExplicitColumns(elements, carrier, false);
301
+ const cleanupCallbacks = makeAllCalculatedElementsExplicitColumns(elements, carrier, false);
289
302
  if (carrier.columns.length === 1 && carrier.columns[0] === '*')
290
303
  delete carrier.columns;
304
+ return cleanupCallbacks;
291
305
  }
292
306
 
293
307
  /**
@@ -304,7 +318,6 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
304
318
  return from.SELECT.elements;
305
319
  }
306
320
  else if (from.SET) {
307
- // FIXME: Check if this is correct
308
321
  // args[0] could be SELECT or UNION
309
322
  return getDirectlyAdressableElements({ from: from.SET.args[0] });
310
323
  }
@@ -316,7 +329,6 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
316
329
  mergedElements[elementName] = arg._art.elements[elementName];
317
330
  }
318
331
  else if (arg.SET) {
319
- // FIXME: Check if this is correct
320
332
  return getDirectlyAdressableElements({ from: arg.SET.args[0] });
321
333
  }
322
334
  else if (arg.SELECT) { // TODO: UNION
@@ -348,6 +360,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
348
360
  * @param {boolean} containsExpandInline
349
361
  */
350
362
  function makeAllCalculatedElementsExplicitColumns( elements, SELECT, containsExpandInline ) {
363
+ const cleanupCallbacks = [];
351
364
  const root = getDirectlyAdressableElements(SELECT);
352
365
  const columnMap = getColumnMap( { SELECT });
353
366
  const hasStar = SELECT.columns.includes('*');
@@ -368,10 +381,16 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
368
381
  containsCalculated = true;
369
382
  const columns = [];
370
383
  for (const branchName in branches) {
371
- if (columnMap[branchName]) // Existing column - don't overwrite, we need $env!
384
+ if (columnMap[branchName]) { // Existing column - don't overwrite, we need $env!
372
385
  columns.push(columnMap[branchName]);
373
- else // TODO: Hm, will we have a $env in the leaf of the thing then?
374
- columns.push({ ref: [ ...originalRef, ...branches[branchName].ref.slice(1) ], as: branchName });
386
+ }
387
+ else {
388
+ // TODO: Hm, will we have a $env in the leaf of the thing then?
389
+ const column = { ref: [ ...originalRef, ...branches[branchName].ref.slice(1) ], as: branchName };
390
+ setProp(column, '_element', element);
391
+ cleanupCallbacks.push(() => delete column._element);
392
+ columns.push(column);
393
+ }
375
394
  }
376
395
  if (columnMap[name]) {
377
396
  unfoldingMap[name] = [ false, [ ...columns ] ];
@@ -406,6 +425,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
406
425
 
407
426
  SELECT.columns = newColumns;
408
427
  }
428
+ return cleanupCallbacks;
409
429
  }
410
430
 
411
431
  /**
@@ -553,9 +573,8 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
553
573
 
554
574
  /**
555
575
  * @param {CSN.Model} csn
556
- * @param {CSN.Options} _options
557
576
  */
558
- function processCalculatedElementsInEntities( csn, _options ) {
577
+ function processCalculatedElementsInEntities( csn ) {
559
578
  forEachDefinition(csn, (artifact, artifactName) => {
560
579
  if (artifact.kind === 'entity' && !(artifact.query || artifact.projection))
561
580
  killInEntity(artifact, [ 'definitions', artifactName ]);
@@ -574,7 +593,8 @@ function processCalculatedElementsInEntities( csn, _options ) {
574
593
  function killInEntity( artifact, path ) {
575
594
  applyTransformationsOnDictionary(artifact.elements, {
576
595
  value: (parent, prop, value, p, root) => {
577
- delete root[p[p.length - 1]];
596
+ if (!value.stored)
597
+ delete root[p[p.length - 1]];
578
598
  },
579
599
  }, {}, path);
580
600
  }
@@ -588,8 +608,9 @@ function killInEntity( artifact, path ) {
588
608
  */
589
609
  function dummifyInEntity( artifact, path ) {
590
610
  applyTransformationsOnDictionary(artifact.elements, {
591
- value: (parent) => {
592
- parent.value = { val: 1 };
611
+ value: (parent, _prop, value) => {
612
+ if (!value.stored)
613
+ parent.value = { val: 'DUMMY' };
593
614
  },
594
615
  }, {}, path);
595
616
  }
@@ -67,7 +67,7 @@ function getViewDecorator( csn, messageFunctions, csnUtils ) {
67
67
  ],
68
68
  };
69
69
 
70
-
70
+ // Clarify: What about $valid?
71
71
  const atFrom = { ref: [ '$at', 'from' ] };
72
72
  const atTo = { ref: [ '$at', 'to' ] };
73
73
 
@@ -294,7 +294,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
294
294
  function addImplicitAliasWithAssoc( col, path ) {
295
295
  if (!col.as && col.ref && col.ref.length > 1) {
296
296
  const { links } = inspectRef(path);
297
- if (links && links.slice(0, -1).some(({ art }) => isAssocOrComposition(art && art.type || '')))
297
+ if (links && links.slice(0, -1).some(({ art }) => art && isAssocOrComposition(art)))
298
298
  col.as = getLastRefStepString(col.ref);
299
299
  }
300
300
  }
@@ -75,7 +75,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
75
75
  // Follow all composition targets in elements of 'artifact'
76
76
  for (const elemName in artifact.elements) {
77
77
  const elem = artifact.elements[elemName];
78
- if (elem.target && isComposition(elem.type)) {
78
+ if (elem.target && isComposition(elem)) {
79
79
  const draftNode = getCsnDef(elem.target);
80
80
  const draftNodeName = elem.target;
81
81
  // Sanity check
@@ -33,10 +33,9 @@ function generateDrafts( csn, options, services ) {
33
33
  csnUtils,
34
34
  } = getTransformers(csn, options);
35
35
  const {
36
- getFinalType,
37
36
  getServiceName,
38
37
  hasAnnotationValue,
39
- getFinalBaseTypeWithProps,
38
+ getFinalTypeInfo,
40
39
  } = csnUtils;
41
40
 
42
41
  const { error, info } = makeMessageFunction(csn, options, 'for.odata');
@@ -173,7 +172,7 @@ function generateDrafts( csn, options, services ) {
173
172
 
174
173
  // Draft-enable the targets of composition elements (draft nodes), too
175
174
  // TODO rewrite
176
- if (elem.target && elem.type && getFinalType(elem.type) === 'cds.Composition') {
175
+ if (elem.target && elem.type && getFinalTypeInfo(elem.type)?.type === 'cds.Composition') {
177
176
  const draftNode = csn.definitions[elem.target];
178
177
 
179
178
  // Ignore if that is our own draft root
@@ -196,7 +195,7 @@ function generateDrafts( csn, options, services ) {
196
195
  stack.push(elem);
197
196
  }
198
197
  else if (elem.type) { // types - possibly structured
199
- const typeDef = getFinalBaseTypeWithProps(elem.type);
198
+ const typeDef = getFinalTypeInfo(elem.type);
200
199
  if (typeDef?.elements)
201
200
  stack.push(typeDef);
202
201
  }
@@ -98,7 +98,7 @@ function transform4odataWithCsn(inputModel, options) {
98
98
  inspectRef,
99
99
  artifactRef,
100
100
  effectiveType,
101
- getFinalBaseTypeWithProps
101
+ getFinalTypeInfo
102
102
  } = csnUtils;
103
103
 
104
104
  // are we working with structured OData or not
@@ -130,7 +130,7 @@ function transform4odataWithCsn(inputModel, options) {
130
130
  transformUtils.rewriteBuiltinTypeRef(csn);
131
131
 
132
132
  const cleanup = validate.forOdata(csn, {
133
- message, error, warning, info, inspectRef, effectiveType, getFinalBaseTypeWithProps, artifactRef, csn, options, csnUtils, services, isAspect, isExternalServiceMember
133
+ message, error, warning, info, inspectRef, effectiveType, getFinalTypeInfo, artifactRef, csn, options, csnUtils, services, isAspect, isExternalServiceMember
134
134
  });
135
135
 
136
136
 
@@ -186,7 +186,7 @@ function transform4odataWithCsn(inputModel, options) {
186
186
 
187
187
  // TODO: add the generated foreign keys to the columns when we are in a view
188
188
  // see db/views.js::addForeignKeysToColumns
189
- flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, '_', !structuredOData, csnUtils,{ skipArtifact: isExternalServiceMember });
189
+ flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, warning, '_', !structuredOData, csnUtils,{ skipArtifact: isExternalServiceMember });
190
190
 
191
191
  // Allow using managed associations as steps in on-conditions to access their fks
192
192
  // To be done after handleManagedAssociationsAndCreateForeignKeys,
@@ -398,8 +398,7 @@ function transform4odataWithCsn(inputModel, options) {
398
398
  // Handles on-conditions in unmanaged associations
399
399
  function processOnCond(def) {
400
400
  forEachMemberRecursively(def, (member) => {
401
- // @ts-ignore
402
- if (member.type && isAssocOrComposition(member.type) && member.on) {
401
+ if (member.on && isAssocOrComposition(member)) {
403
402
  removeLeadingDollarSelfInOnCondition(member);
404
403
  }
405
404
  });
@@ -432,7 +431,7 @@ function transform4odataWithCsn(inputModel, options) {
432
431
  // TODO: test???
433
432
  function addCommonValueListviaAssociation(member, memberName) {
434
433
  let vlAnno = '@Common.ValueList.viaAssociation';
435
- if (isAssociation(member.type)) {
434
+ if (isAssociation(member)) {
436
435
  let navigable = member['@odata.navigable'] !== false; // navigable disabled only if explicitly set to false
437
436
  let targetDef = getCsnDef(member.target);
438
437
  if (navigable && targetDef['@cds.odata.valuelist'] && !member[vlAnno]) {