@sap/cds-compiler 3.1.2 → 3.4.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 (117) hide show
  1. package/CHANGELOG.md +101 -3
  2. package/bin/cdsc.js +4 -2
  3. package/doc/CHANGELOG_BETA.md +35 -0
  4. package/lib/api/main.js +153 -29
  5. package/lib/api/validate.js +8 -3
  6. package/lib/base/dictionaries.js +6 -6
  7. package/lib/base/error.js +2 -2
  8. package/lib/base/keywords.js +106 -24
  9. package/lib/base/message-registry.js +177 -79
  10. package/lib/base/messages.js +78 -57
  11. package/lib/base/model.js +2 -1
  12. package/lib/checks/actionsFunctions.js +1 -1
  13. package/lib/checks/annotationsOData.js +2 -2
  14. package/lib/checks/arrayOfs.js +15 -7
  15. package/lib/checks/cdsPersistence.js +1 -1
  16. package/lib/checks/checkForTypes.js +53 -0
  17. package/lib/checks/defaultValues.js +4 -2
  18. package/lib/checks/elements.js +81 -6
  19. package/lib/checks/foreignKeys.js +12 -13
  20. package/lib/checks/invalidTarget.js +10 -11
  21. package/lib/checks/managedInType.js +21 -15
  22. package/lib/checks/nullableKeys.js +1 -1
  23. package/lib/checks/onConditions.js +9 -9
  24. package/lib/checks/parameters.js +23 -0
  25. package/lib/checks/queryNoDbArtifacts.js +1 -1
  26. package/lib/checks/selectItems.js +1 -1
  27. package/lib/checks/sql-snippets.js +12 -10
  28. package/lib/checks/types.js +2 -2
  29. package/lib/checks/utils.js +17 -7
  30. package/lib/checks/validator.js +36 -14
  31. package/lib/compiler/assert-consistency.js +21 -13
  32. package/lib/compiler/builtins.js +8 -0
  33. package/lib/compiler/checks.js +57 -40
  34. package/lib/compiler/define.js +139 -69
  35. package/lib/compiler/extend.js +319 -50
  36. package/lib/compiler/finalize-parse-cdl.js +14 -9
  37. package/lib/compiler/kick-start.js +2 -35
  38. package/lib/compiler/populate.js +111 -68
  39. package/lib/compiler/propagator.js +5 -3
  40. package/lib/compiler/resolve.js +71 -108
  41. package/lib/compiler/shared.js +82 -54
  42. package/lib/compiler/tweak-assocs.js +26 -14
  43. package/lib/compiler/utils.js +13 -2
  44. package/lib/edm/annotations/genericTranslation.js +10 -7
  45. package/lib/edm/csn2edm.js +11 -11
  46. package/lib/edm/edm.js +17 -9
  47. package/lib/edm/edmPreprocessor.js +53 -30
  48. package/lib/edm/edmUtils.js +7 -2
  49. package/lib/gen/Dictionary.json +14 -0
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -2
  52. package/lib/gen/languageParser.js +4312 -4186
  53. package/lib/inspect/inspectModelStatistics.js +1 -1
  54. package/lib/inspect/inspectPropagation.js +23 -9
  55. package/lib/json/csnVersion.js +13 -13
  56. package/lib/json/from-csn.js +161 -172
  57. package/lib/json/to-csn.js +70 -10
  58. package/lib/language/.eslintrc.json +4 -0
  59. package/lib/language/antlrParser.js +8 -11
  60. package/lib/language/docCommentParser.js +1 -2
  61. package/lib/language/errorStrategy.js +54 -27
  62. package/lib/language/genericAntlrParser.js +140 -93
  63. package/lib/language/language.g4 +57 -33
  64. package/lib/language/multiLineStringParser.js +75 -63
  65. package/lib/main.d.ts +3 -6
  66. package/lib/main.js +1 -0
  67. package/lib/model/.eslintrc.json +13 -0
  68. package/lib/model/api.js +4 -2
  69. package/lib/model/csnRefs.js +78 -50
  70. package/lib/model/csnUtils.js +272 -222
  71. package/lib/model/enrichCsn.js +41 -31
  72. package/lib/model/revealInternalProperties.js +61 -57
  73. package/lib/model/sortViews.js +35 -31
  74. package/lib/modelCompare/compare.js +52 -18
  75. package/lib/modelCompare/filter.js +83 -0
  76. package/lib/optionProcessor.js +10 -1
  77. package/lib/render/manageConstraints.js +11 -7
  78. package/lib/render/toCdl.js +151 -106
  79. package/lib/render/toHdbcds.js +8 -6
  80. package/lib/render/toRename.js +4 -4
  81. package/lib/render/toSql.js +17 -7
  82. package/lib/render/utils/common.js +27 -9
  83. package/lib/render/utils/sql.js +5 -5
  84. package/lib/sql-identifier.js +7 -0
  85. package/lib/transform/db/applyTransformations.js +32 -3
  86. package/lib/transform/db/assertUnique.js +27 -38
  87. package/lib/transform/db/expansion.js +92 -41
  88. package/lib/transform/db/flattening.js +1 -1
  89. package/lib/transform/db/temporal.js +3 -1
  90. package/lib/transform/db/transformExists.js +8 -2
  91. package/lib/transform/db/views.js +42 -13
  92. package/lib/transform/draft/db.js +2 -2
  93. package/lib/transform/forOdataNew.js +10 -7
  94. package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
  95. package/lib/transform/localized.js +29 -20
  96. package/lib/transform/odata/toFinalBaseType.js +8 -11
  97. package/lib/transform/odata/typesExposure.js +2 -1
  98. package/lib/transform/parseExpr.js +245 -0
  99. package/lib/transform/transformUtilsNew.js +122 -51
  100. package/lib/transform/translateAssocsToJoins.js +17 -16
  101. package/lib/utils/moduleResolve.js +5 -5
  102. package/lib/utils/objectUtils.js +3 -3
  103. package/lib/utils/term.js +5 -5
  104. package/package.json +2 -2
  105. package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
  106. package/share/messages/check-proper-type-of.md +4 -4
  107. package/share/messages/check-proper-type.md +2 -2
  108. package/share/messages/duplicate-autoexposed.md +4 -4
  109. package/share/messages/extend-repeated-intralayer.md +4 -5
  110. package/share/messages/extend-unrelated-layer.md +4 -4
  111. package/share/messages/message-explanations.json +3 -1
  112. package/share/messages/redirected-to-ambiguous.md +7 -6
  113. package/share/messages/redirected-to-complex.md +63 -0
  114. package/share/messages/redirected-to-unrelated.md +6 -5
  115. package/share/messages/rewrite-not-supported.md +4 -4
  116. package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
  117. package/share/messages/wildcard-excluding-one.md +37 -0
@@ -242,6 +242,10 @@ const cdsToSqlTypes = {
242
242
  'cds.DecimalFloat': 'DECIMAL',
243
243
  'cds.Integer64': 'BIGINT',
244
244
  'cds.Integer': 'INTEGER',
245
+ 'cds.Int64': 'BIGINT',
246
+ 'cds.Int32': 'INTEGER',
247
+ 'cds.Int16': 'SMALLINT',
248
+ 'cds.UInt8': 'TINYINT',
245
249
  'cds.hana.SMALLINT': 'SMALLINT',
246
250
  'cds.hana.TINYINT': 'TINYINT', // not a Standard SQL type
247
251
  'cds.Double': 'DOUBLE',
@@ -279,20 +283,33 @@ const cdsToSqlTypes = {
279
283
  'cds.hana.BINARY': 'BINARY',
280
284
  'cds.hana.SMALLDECIMAL': 'DECIMAL',
281
285
  },
286
+ h2: {
287
+ 'cds.Binary': 'VARBINARY', // same as for plain
288
+ 'cds.LargeBinary': 'BINARY LARGE OBJECT', // BLOB would require a length!
289
+ 'cds.DecimalFloat': 'DECFLOAT', // Decimal and Decimal(p) is mapped to cds.DecimalFloat
290
+ 'cds.DateTime': 'TIMESTAMP(0)',
291
+ 'cds.Timestamp': 'TIMESTAMP(7)',
292
+ },
282
293
  postgres: {
283
- // TODO: Type mapping for binary types is not correct, yet.
284
- // We can't use text types for binary on PostgreSQL due to NUL!
294
+ // See <https://www.postgresql.org/docs/current/datatype.html>
285
295
  'cds.String': 'VARCHAR',
286
- 'cds.LargeString': 'text',
287
- 'cds.hana.CLOB': 'text',
288
- 'cds.LargeBinary': 'bytea',
289
- 'cds.Binary': 'bytea',
290
- 'cds.hana.BINARY': 'bytea',
291
- 'cds.Double': 'double precision',
292
- 'cds.hana.TINYINT': 'INTEGER',
296
+ 'cds.LargeString': 'TEXT',
297
+ 'cds.LargeBinary': 'BYTEA',
298
+ 'cds.Binary': 'BYTEA',
299
+ 'cds.Double': 'FLOAT8',
300
+ 'cds.UInt8': 'INTEGER', // Not equivalent
293
301
  },
294
302
  };
295
303
 
304
+ // Type mapping from cds type names to HDBCDS type names:
305
+ // Only those types, that need mapping, are listed.
306
+ const cdsToHdbcdsTypes = {
307
+ 'cds.UInt8': 'cds.hana.TINYINT',
308
+ 'cds.Int16': 'cds.hana.SMALLINT',
309
+ 'cds.Int32': 'cds.Integer',
310
+ 'cds.Int64': 'cds.Integer64',
311
+ };
312
+
296
313
  /**
297
314
  * Get the element matching the column
298
315
  *
@@ -521,6 +538,7 @@ module.exports = {
521
538
  addIntermediateContexts,
522
539
  addContextMarkers,
523
540
  cdsToSqlTypes,
541
+ cdsToHdbcdsTypes,
524
542
  hasHanaComment,
525
543
  getHanaComment,
526
544
  findElement,
@@ -43,8 +43,7 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
43
43
  options.src === 'hdi' ||
44
44
  (options.manageConstraints && options.manageConstraints.src === 'hdi');
45
45
 
46
- const { sqlMapping } = options;
47
- const forSqlite = options.sqlDialect === 'sqlite';
46
+ const { sqlMapping, sqlDialect } = options;
48
47
  let result = '';
49
48
  result += `${indent}CONSTRAINT ${quoteId(constraint.identifier)}\n`;
50
49
  if (renderAsHdbconstraint)
@@ -55,7 +54,7 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
55
54
  const onDeleteRemark = constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : '';
56
55
 
57
56
  // omit 'RESTRICT' action for ON UPDATE / ON DELETE, because it interferes with deferred constraint check
58
- if (forSqlite) {
57
+ if (sqlDialect === 'sqlite') {
59
58
  if (constraint.onDelete === 'CASCADE' )
60
59
  result += `${indent}ON DELETE ${constraint.onDelete}${onDeleteRemark}\n`;
61
60
  }
@@ -65,13 +64,14 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
65
64
  }
66
65
  }
67
66
  // constraint enforcement / validation must be switched off using sqlite pragma statement
67
+ // constraint enforcement / validation not supported by postgres
68
68
  // Does not include HDBCDS.
69
- if (options.toSql && !forSqlite) {
69
+ if (options.toSql && sqlDialect !== 'sqlite' && sqlDialect !== 'postgres') {
70
70
  result += `${indent}${!constraint.validated ? 'NOT ' : ''}VALIDATED\n`;
71
71
  result += `${indent}${!constraint.enforced ? 'NOT ' : ''}ENFORCED\n`;
72
72
  }
73
73
  // for sqlite, the DEFERRABLE keyword is required
74
- result += `${indent}${forSqlite ? 'DEFERRABLE ' : ''}INITIALLY DEFERRED`;
74
+ result += `${indent}${sqlDialect === 'sqlite' ? 'DEFERRABLE ' : ''}INITIALLY DEFERRED`;
75
75
  return result;
76
76
  }
77
77
 
@@ -39,6 +39,13 @@ const keywords = require( './base/keywords' );
39
39
 
40
40
  const sqlDialects = {
41
41
  plain: {},
42
+ h2: {
43
+ // See http://www.h2database.com/html/grammar.html#name
44
+ regularRegex: /^[A-Za-z_][A-Za-z_0-9]*$/,
45
+ reservedWords: keywords.h2,
46
+ effectiveName: name => name.toUpperCase(),
47
+ asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
48
+ },
42
49
  sqlite: {
43
50
  regularRegex: /^[A-Za-z_][A-Za-z_$0-9]*$/,
44
51
  reservedWords: keywords.sqlite,
@@ -35,10 +35,16 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
35
35
  };
36
36
 
37
37
  const csnPath = [ ...path ];
38
- if (prop === 'definitions')
38
+ if (prop === 'definitions') {
39
39
  definitions( parent, 'definitions', parent.definitions );
40
- else
40
+ }
41
+ else if (options.directDict) {
42
+ for (const name of Object.getOwnPropertyNames( parent ))
43
+ standard( parent, name, parent[name] );
44
+ }
45
+ else {
41
46
  standard(parent, prop, parent[prop]);
47
+ }
42
48
  return parent;
43
49
 
44
50
  /**
@@ -84,7 +90,7 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
84
90
  * @param {object} dict The value of node[_prop]
85
91
  */
86
92
  function dictionary( node, _prop, dict ) {
87
- // Allow skipping dicts like actions in forHanaNew
93
+ // Allow skipping dicts like actions in forRelationalDB
88
94
  if (options.skipDict && options.skipDict[_prop] || dict === null || dict === undefined) // with universal CSN, dicts might be null
89
95
  return;
90
96
  csnPath.push( _prop );
@@ -194,9 +200,31 @@ function applyTransformationsOnNonDictionary(parent, prop, customTransformers =
194
200
  return applyTransformationsInternal(parent, prop, customTransformers, [], options, path)[prop];
195
201
  }
196
202
 
203
+ /**
204
+ * Instead of looping through the whole model, start at a given thing (like .elements),
205
+ * as long as it is a dictionary.
206
+ *
207
+ * Each transformer gets:
208
+ * - the parent having the property
209
+ * - the name of the property
210
+ * - the value of the property
211
+ * - the path to the property
212
+ *
213
+ *
214
+ * @param {object} dictionary Dictionary to enrich in-place
215
+ * @param {object} customTransformers Map of prop to transform and function to apply
216
+ * @param {applyTransformationsOptions} [options={}]
217
+ * @param {CSN.Path} path Path pointing to parent
218
+ * @returns {object} dictionary with transformations applied
219
+ */
220
+ function applyTransformationsOnDictionary(dictionary, customTransformers = {}, options = {}, path = []) {
221
+ return applyTransformationsInternal(dictionary, null, customTransformers, [], { directDict: true, ...options }, path);
222
+ }
223
+
197
224
  module.exports = {
198
225
  applyTransformations,
199
226
  applyTransformationsOnNonDictionary,
227
+ applyTransformationsOnDictionary,
200
228
  };
201
229
 
202
230
 
@@ -209,4 +237,5 @@ module.exports = {
209
237
  * @property {object} [skipStandard] stop drill-down on certain "standard" props
210
238
  * @property {object} [skipDict] stop drill-down on certain "dictionary" props
211
239
  * @property {boolean} [skipIgnore=true] Whether to skip _ignore elements or not
240
+ * @property {boolean} [directDict=false] Implicitly set via applyTransformationsOnDictionary
212
241
  */
@@ -3,6 +3,7 @@
3
3
  const { forEachDefinition, hasAnnotationValue } = require('../../model/csnUtils');
4
4
  const { getTransformers } = require('../transformUtilsNew');
5
5
  const { setProp } = require('../../base/model');
6
+ const { pathName } = require('../../compiler/utils');
6
7
 
7
8
 
8
9
  /**
@@ -38,13 +39,14 @@ function processAssertUnique(csn, options, error, info) {
38
39
  // filter unique constraints from annotations
39
40
  for (const propName in artifact) {
40
41
  if (propName.startsWith('@assert.unique') && artifact[propName] !== null) {
42
+ const anno = propName;
41
43
  // Constraint Name check
42
44
  const constraintName = propName.split('.').splice(2);
43
45
  if (constraintName.length === 0)
44
- err(propName, 'Table constraint cannot be anonymous');
46
+ error(null, [ 'definitions', artifactName ], { anno }, '$(ANNO): Table constraint can\'t be anonymous');
45
47
  if (constraintName.length > 1)
46
48
  // Neither HANA CDS nor HANA SQL allow dots in index names
47
- err(propName, "Illegal character '.' in constraint name");
49
+ error(null, [ 'definitions', artifactName ], { anno }, '$(ANNO): Illegal character \'.\' in constraint name');
48
50
 
49
51
  const propValue = artifact[propName];
50
52
  // Constraint value check, returns array of path values
@@ -79,10 +81,12 @@ function processAssertUnique(csn, options, error, info) {
79
81
  pathxrefs[pstr]++;
80
82
  });
81
83
  Object.keys(pathxrefs).forEach((k) => {
82
- if (pathxrefs[k] > 1)
83
- err(propName, `Final path "${k}" can only be specified once`);
84
+ if (pathxrefs[k] > 1) {
85
+ error(null, [ 'definitions', artifactName ], { anno, id: k },
86
+ '$(ANNO): Final path $(ID) can only be specified once');
87
+ }
84
88
  });
85
- // 9) Add into constraint cross reference
89
+ // 9) Add into constraint cross-reference
86
90
  if (constraintKey.length) {
87
91
  if (constraintXrefs[constraintKey])
88
92
  constraintXrefs[constraintKey].push(propName);
@@ -99,7 +103,7 @@ function processAssertUnique(csn, options, error, info) {
99
103
  for (const key in constraintXrefs) {
100
104
  const val = constraintXrefs[key];
101
105
  if (val.length > 1)
102
- err(val.join(', '), 'Constraint can only be specified once');
106
+ error(null, [ 'definitions', artifactName ], { annos: val }, '$(ANNOS): Constraint can only be specified once');
103
107
  }
104
108
  // preserve dictionary in '$tableConstraints' on the artifact for path rewriting and rendering
105
109
  if (Object.keys(constraintDict).length) {
@@ -119,17 +123,18 @@ function processAssertUnique(csn, options, error, info) {
119
123
  */
120
124
  function checkVal(val, propName) {
121
125
  const paths = [];
126
+ const loc = [ 'definitions', artifactName ];
122
127
  if (!Array.isArray(val)) {
123
- err(propName, `Value '${JSON.stringify(unref(val))}' is not an array`);
128
+ error(null, loc, { anno: propName, value: JSON.stringify(unref(val)) }, '$(ANNO): Value $(VALUE) is not an array');
124
129
  }
125
130
  else {
126
131
  if (val.length === 0)
127
- inf(propName, 'Empty annotation is ignored');
132
+ info(null, loc, { anno: propName }, '$(ANNO): Empty annotation is ignored');
128
133
 
129
134
  val.forEach((v) => {
130
135
  const p = v['='];
131
136
  if (!p)
132
- err(propName, `Value '${JSON.stringify(unref(v))}' is not a path`);
137
+ error(null, loc, { anno: propName, value: JSON.stringify(unref(v)) }, '$(ANNO): Value $(VALUE) is not a path');
133
138
  else
134
139
  paths.push(p);
135
140
  });
@@ -185,61 +190,45 @@ function processAssertUnique(csn, options, error, info) {
185
190
  return;
186
191
  path.isChecked = true;
187
192
  let foundErr = false;
193
+ const name = pathName(path.ref);
194
+ const loc = [ 'definitions', artifactName ];
188
195
  for (let i = 0; i < path.ref.length && !foundErr; i++) {
189
196
  const art = path.ref[i]._art;
197
+ const elemref = path.ref[i].id;
190
198
  if (art) {
191
199
  if (art.items) {
192
- msg(`'Array of/many' element "${path.ref[i].id}" is not allowed`);
200
+ error(null, loc, { elemref, name, anno: constraintName },
201
+ '$(ANNO): \'Array of/many\' element $(ELEMREF) is not allowed in $(NAME)');
193
202
  delete path._art;
194
203
  foundErr = true;
195
204
  }
196
205
  if (art.target) {
197
206
  if (art.on) {
198
- msg(`Unmanaged association "${path.ref[i].id}" is not allowed`);
207
+ error(null, loc, { elemref, name, anno: constraintName },
208
+ '$(ANNO): Unmanaged association $(ELEMREF) is not allowed in $(NAME)');
199
209
  delete path._art;
200
210
  foundErr = true;
201
211
  }
202
212
  if (art.keys && i < path.ref.length - 1) {
203
- msg(`Element access via managed association "${path.ref[i].id}" is not allowed`);
213
+ error(null, loc, { elemref, name, anno: constraintName },
214
+ '$(ANNO): Element access via managed association $(ELEMREF) is not allowed in $(NAME)');
204
215
  delete path._art;
205
216
  foundErr = true;
206
217
  }
207
218
  }
208
219
  }
209
220
  else {
210
- err(constraintName, `"${path.ref[i].id}" has not been found`);
221
+ error(null, loc, { elemref, anno: constraintName }, '$(ANNO): $(ELEMREF) has not been found');
211
222
  foundErr = true;
212
223
  }
213
224
  }
214
225
 
215
226
  if (!foundErr && path._art && [ 'cds.LargeBinary', 'cds.LargeString',
216
- 'cds.hana.CLOB', 'cds.hana.ST_POINT', 'cds.hana.ST_GEOMETRY' ].includes(path._art.type))
217
- msg(`"Type ${path._art.type}" not allowed`);
218
-
219
-
220
- /**
221
- * @param {string} message
222
- */
223
- function msg(message) {
224
- err(constraintName, `${message} in "${path.ref.map(p => p.id).join('.')}"`);
227
+ 'cds.hana.CLOB', 'cds.hana.ST_POINT', 'cds.hana.ST_GEOMETRY' ].includes(path._art.type)) {
228
+ error(null, loc, { type: path._art.type, name, anno: constraintName },
229
+ '$(ANNO): Type $(TYPE) not allowed in $(NAME)');
225
230
  }
226
231
  }
227
-
228
- // message macros for unified messaging
229
- /**
230
- * @param {string} propName
231
- * @param {string} message
232
- */
233
- function err(propName, message) {
234
- error(null, [ 'definitions', artifactName ], `${propName}: ${message}`);
235
- }
236
- /**
237
- * @param {string} propName
238
- * @param {string} message
239
- */
240
- function inf(propName, message) {
241
- info(null, [ 'definitions', artifactName ], `${propName}: ${message}`);
242
- }
243
232
  }
244
233
  }
245
234
 
@@ -7,7 +7,7 @@ const {
7
7
  walkCsnPath,
8
8
  } = require('../../model/csnUtils');
9
9
  const { csnRefs, implicitAs } = require('../../model/csnRefs');
10
- const { setProp, isBetaEnabled } = require('../../base/model');
10
+ const { setProp } = require('../../base/model');
11
11
  const { forEach } = require('../../utils/objectUtils');
12
12
 
13
13
  /**
@@ -26,12 +26,11 @@ const { forEach } = require('../../utils/objectUtils');
26
26
  function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithAnyError }, iterateOptions = {}) {
27
27
  const csnUtils = getUtils(csn);
28
28
  const {
29
- isStructured, get$combined, getFinalBaseTypeWithProps, getServiceName,
29
+ isStructured, get$combined, getFinalBaseTypeWithProps,
30
30
  } = csnUtils;
31
31
  let { effectiveType, inspectRef } = csnUtils;
32
32
 
33
- if (isBetaEnabled(options, 'nestedProjections'))
34
- rewriteExpandInline();
33
+ rewriteExpandInline();
35
34
 
36
35
 
37
36
  applyTransformations(csn, {
@@ -63,7 +62,8 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
63
62
  * For such skipped things, error for usage of assoc pointing to them and and ignore publishing of assoc pointing to them.
64
63
  */
65
64
  function rewriteExpandInline() {
66
- const { cleanup, _dependents } = setDependencies(csn);
65
+ let cleanup;
66
+ let _dependents;
67
67
 
68
68
  const entity = findAnEntity();
69
69
  const toDummyfy = [];
@@ -77,14 +77,12 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
77
77
  const root = get$combined({ SELECT: parent });
78
78
  if (!hasAnnotationValue(artifact, '@cds.persistence.table')) {
79
79
  const rewritten = rewrite(root, parent.columns, parent.excluding);
80
- parent.columns = rewritten.columns;
81
80
  /*
82
81
  * Do not remove unexpandable many columns in OData
83
82
  */
84
83
  if (rewritten.toMany.length > 0 && !options.toOdata) {
85
84
  markAsToDummyfy(artifact, path[1]);
86
- if (getServiceName(path[1]) === null)
87
- error( null, [ 'definitions', path[1] ], { name: path[1] }, 'Unexpected .expand with to-many association in entity $(NAME), which is outside any service');
85
+ error( null, [ 'definitions', path[1] ], { name: path[1] }, 'Unexpected .expand with to-many association in entity $(NAME)');
88
86
  }
89
87
  else {
90
88
  parent.columns = rewritten.columns;
@@ -98,7 +96,8 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
98
96
  if (!options.toOdata)
99
97
  dummyfy();
100
98
 
101
- cleanup.forEach(fn => fn());
99
+ if (cleanup)
100
+ cleanup.forEach(fn => fn());
102
101
 
103
102
  ({ effectiveType, inspectRef } = csnRefs(csn));
104
103
 
@@ -106,7 +105,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
106
105
  const publishing = [];
107
106
  // OData must allow navigations to @cds.persistence.skip targets
108
107
  // as valid navigations in the API
109
- if (!options.toOdata) {
108
+ if (options.transformation !== 'odata') {
110
109
  applyTransformations(csn, {
111
110
  target: (parent, name, target, path) => {
112
111
  if (toDummyfy.indexOf(target) !== -1) {
@@ -209,6 +208,9 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
209
208
  * @param {string} name
210
209
  */
211
210
  function markAsToDummyfy(artifact, name) {
211
+ if (!_dependents && !cleanup)
212
+ ({ cleanup, _dependents } = setDependencies(csn, csnUtils));
213
+
212
214
  const stack = [ [ artifact, name ] ];
213
215
  while (stack.length > 0) {
214
216
  const [ a, n ] = stack.pop();
@@ -347,14 +349,21 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
347
349
  }
348
350
  else if (current.xpr || current.args) {
349
351
  // We need to re-write refs in the .xpr/.args so they stay resolvable - we need to prepend the currentRef
350
- rewriteXprArgs(current, currentRef);
352
+ rewriteExpressionArrays(current, currentRef);
351
353
  expanded.push(Object.assign({}, current, { as: currentAlias.join(pathDelimiter) } ));
352
354
  }
355
+ else if (current.on || current.cast?.on) {
356
+ rewriteOn(current, [ currentAlias.slice(0, -1).join(pathDelimiter) ]);
357
+ expanded.push(Object.assign({}, current, { ref: currentRef, as: currentAlias.join(pathDelimiter) } ));
358
+ }
353
359
  else if (current.val !== undefined || current.func !== undefined) {
354
360
  expanded.push(Object.assign(current, { as: currentAlias.join(pathDelimiter) }));
355
361
  }
356
- else {
357
- expanded.push({ ref: currentRef, as: currentAlias.join(pathDelimiter) });
362
+ else if (current.$scope === '$magic' || current.$scope === '$self') {
363
+ expanded.push(Object.assign({}, current, { as: currentAlias.join(pathDelimiter) } ));
364
+ }
365
+ else { // preserve stuff like .cast for redirection
366
+ expanded.push(Object.assign({}, current, { ref: currentRef, as: currentAlias.join(pathDelimiter) } ));
358
367
  }
359
368
  }
360
369
 
@@ -367,37 +376,79 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
367
376
  * @param {object} parent Thing that has an .xpr/.args
368
377
  * @param {string[]} ref Ref so far
369
378
  */
370
- function rewriteXprArgs(parent, ref) {
379
+ function rewriteExpressionArrays(parent, ref) {
371
380
  const stack = [ [ parent, ref ] ];
372
381
  while (stack.length > 0) {
373
382
  const [ current, currentRef ] = stack.pop();
374
- if (current.xpr) {
375
- for (let i = 0; i < current.xpr.length; i++) {
376
- const part = current.xpr[i];
377
- if (part.ref) {
378
- part.ref = currentRef.concat(part.ref);
379
- // part.as = currentAlias.concat(part.as || part.ref[ref.length - 1]).join(pathDelimiter);
380
- current.xpr[i] = part;
381
- stack.push([ part, part.ref ]);
382
- }
383
- else {
384
- stack.push([ part, currentRef ]);
385
- }
386
- }
383
+ if (current.xpr)
384
+ rewriteSingleExpressionArray(current.xpr, currentRef, stack);
385
+ if (current.args)
386
+ rewriteSingleExpressionArray(current.args, currentRef, stack);
387
+ }
388
+ }
389
+
390
+ /**
391
+ * With a .cast.on or .on in a .expand/.inline, we need to change the references,
392
+ * since we change the overall scope of things (by "heaving" them up into "normal refs").
393
+ *
394
+ * So anything that does not have a $self/$projection infron get's the so-far-traveled alias,
395
+ * since after the transformation it will basically be in "top-level".
396
+ *
397
+ * @param {object} parent
398
+ * @param {Array} ref The so-far effective name (basically the will-be alias), as an array to treat like a ref
399
+ */
400
+ function rewriteOn(parent, ref) {
401
+ const stack = [ [ parent, ref ] ];
402
+ while (stack.length > 0) {
403
+ const [ current, currentRef ] = stack.pop();
404
+ if (current.on)
405
+ rewriteOnCondition(current.on, currentRef, stack);
406
+ if (current.cast?.on)
407
+ rewriteOnCondition(current.cast.on, currentRef, stack);
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Actually rewrite the given oncondition. Once we find something to rewrite,
413
+ * we preprend the currentRef.
414
+ *
415
+ * All stuff is pushed to the stack.
416
+ *
417
+ * @param {Array} on
418
+ * @param {Array} currentRef
419
+ * @param {Array} stack
420
+ */
421
+ function rewriteOnCondition(on, currentRef, stack) {
422
+ for (let i = 0; i < on.length; i++) {
423
+ const part = on[i];
424
+ if (part.ref && part.ref[0] !== '$self' && part.ref[0] !== '$projection') {
425
+ part.ref = currentRef[0] ? [ currentRef[0], ...part.ref ] : part.ref;
426
+ on[i] = part;
427
+ stack.push([ part, part.ref ]);
387
428
  }
388
- if (current.args) {
389
- for (let i = 0; i < current.args.length; i++) {
390
- const part = current.args[i];
391
- if (part.ref) {
392
- part.ref = currentRef.concat(part.ref);
393
- // part.as = currentAlias.concat(part.as || part.ref[ref.length - 1]).join(pathDelimiter);
394
- current.args[i] = part;
395
- stack.push([ part, part.ref ]);
396
- }
397
- else {
398
- stack.push([ part, currentRef ]);
399
- }
400
- }
429
+ else {
430
+ stack.push([ part, currentRef ]);
431
+ }
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Rewrite the given expressionArray, prefixing currentRef to all refs
437
+ *
438
+ * @param {Array} expressionArray
439
+ * @param {Array} currentRef
440
+ * @param {Array} stack
441
+ */
442
+ function rewriteSingleExpressionArray(expressionArray, currentRef, stack) {
443
+ for (let i = 0; i < expressionArray.length; i++) {
444
+ const part = expressionArray[i];
445
+ if (part.ref) {
446
+ part.ref = currentRef.concat(part.ref);
447
+ expressionArray[i] = part;
448
+ stack.push([ part, part.ref ]);
449
+ }
450
+ else {
451
+ stack.push([ part, currentRef ]);
401
452
  }
402
453
  }
403
454
  }
@@ -409,7 +460,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
409
460
  */
410
461
  function findAnEntity() {
411
462
  for (const name in csn.definitions) {
412
- if (Object.hasOwnProperty.call(csn.definitions, name) && csn.definitions[name].kind === 'entity' && !csn.definitions[name].query)
463
+ if (Object.prototype.hasOwnProperty.call(csn.definitions, name) && csn.definitions[name].kind === 'entity' && !csn.definitions[name].query)
413
464
  return name;
414
465
  }
415
466
  return null;
@@ -124,7 +124,7 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
124
124
  }
125
125
  },
126
126
  }, [ (definitions, artifactName, artifact) => {
127
- // Replace events, actions and functions with simple dummies - they don't have effect on forHanaNew stuff
127
+ // Replace events, actions and functions with simple dummies - they don't have effect on forRelationalDB stuff
128
128
  // and that way they contain no references and don't hurt.
129
129
 
130
130
  // Do not do for OData
@@ -81,7 +81,9 @@ function getViewDecorator(csn, messageFunctions) {
81
81
  }
82
82
  }
83
83
  else {
84
- info(null, [ 'definitions', artifactName ], `No temporal WHERE clause added as "${from[0].error_parent}"."${from[0].name}" and "${to[0].error_parent}"."${to[0].name}" are not of same origin`);
84
+ info(null, [ 'definitions', artifactName ],
85
+ { source: `${from[0].errorParent}.${from[0].name}`, target: `${to[0].errorParent}.${to[0].name}` },
86
+ 'No temporal WHERE clause added as $(SOURCE) and $(TARGET) are not of same origin');
85
87
  }
86
88
  }
87
89
  else if (from.length > 0 || to.length > 0) {
@@ -428,6 +428,11 @@ function handleExists(csn, options, error) {
428
428
  * @returns {object[]} The stuff to add to the where
429
429
  */
430
430
  function translateManagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current) {
431
+ if (current.$scope === '$self') {
432
+ error('ref-unexpected-exists-self', current.$path, { id: current.ref[0], name: 'exists' }, 'With $(NAME), path steps must not start with $(ID)');
433
+ return [];
434
+ }
435
+
431
436
  const whereExtension = [];
432
437
  for (let j = 0; j < root.keys.length; j++) {
433
438
  const lop = { ref: [ target, ...root.keys[j].ref ] }; // target side
@@ -799,8 +804,9 @@ function handleExists(csn, options, error) {
799
804
  where.push({ ref: [ base, ...part.ref.slice(1) ] });
800
805
  }
801
806
  else if (part.$scope === '$self') { // source side - "absolute" scope
802
- // Same message as in forHanaNew/transformDollarSelfComparisonWithUnmanagedAssoc
803
- error(null, part.$path, 'An association that uses "$self" in its ON-condition can\'t be compared to "$self"');
807
+ // Same message as in forRelationalDB/transformDollarSelfComparisonWithUnmanagedAssoc
808
+ error(null, part.$path, { name: '$self' },
809
+ 'An association that uses $(NAME) in its ON-condition can\'t be compared to "$self"');
804
810
  }
805
811
  else if (partInspect.art) { // source side - with local scope
806
812
  where.push({ ref: [ target, ...assoc.ref.slice(1, -1), ...part.ref ] });