@sap/cds-compiler 5.8.2 → 5.9.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 (87) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/bin/cds_remove_invalid_whitespace.js +5 -3
  3. package/bin/cds_update_identifiers.js +9 -6
  4. package/bin/cdsc.js +79 -59
  5. package/bin/cdsse.js +14 -10
  6. package/bin/cdsv2m.js +3 -1
  7. package/lib/api/options.js +28 -6
  8. package/lib/base/message-registry.js +15 -4
  9. package/lib/checks/validator.js +3 -0
  10. package/lib/compiler/base.js +1 -1
  11. package/lib/compiler/checks.js +70 -50
  12. package/lib/compiler/extend.js +1 -1
  13. package/lib/compiler/generate.js +8 -2
  14. package/lib/compiler/index.js +1 -1
  15. package/lib/compiler/lsp-api.js +1 -1
  16. package/lib/compiler/propagator.js +2 -2
  17. package/lib/compiler/resolve.js +78 -31
  18. package/lib/compiler/shared.js +3 -3
  19. package/lib/compiler/tweak-assocs.js +1 -1
  20. package/lib/compiler/utils.js +10 -0
  21. package/lib/compiler/xpr-rewrite.js +1 -1
  22. package/lib/edm/annotations/edmJson.js +42 -39
  23. package/lib/edm/annotations/genericTranslation.js +55 -55
  24. package/lib/edm/annotations/preprocessAnnotations.js +5 -5
  25. package/lib/edm/csn2edm.js +21 -16
  26. package/lib/edm/edm.js +62 -62
  27. package/lib/edm/edmAnnoPreprocessor.js +2 -2
  28. package/lib/edm/edmInboundChecks.js +1 -1
  29. package/lib/edm/edmPreprocessor.js +32 -32
  30. package/lib/edm/edmUtils.js +8 -8
  31. package/lib/gen/CdlGrammar.checksum +1 -1
  32. package/lib/gen/CdlParser.js +77 -81
  33. package/lib/gen/Dictionary.json +3062 -3072
  34. package/lib/gen/language.checksum +1 -1
  35. package/lib/gen/language.interp +1 -1
  36. package/lib/gen/languageParser.js +1238 -1236
  37. package/lib/json/from-csn.js +1 -1
  38. package/lib/json/to-csn.js +30 -3
  39. package/lib/language/genericAntlrParser.js +16 -0
  40. package/lib/main.d.ts +79 -1
  41. package/lib/model/csnRefs.js +12 -5
  42. package/lib/model/xprAsTree.js +71 -0
  43. package/lib/modelCompare/utils/filter.js +1 -1
  44. package/lib/optionProcessor.js +46 -32
  45. package/lib/parsers/CdlGrammar.g4 +33 -28
  46. package/lib/parsers/Lexer.js +1 -1
  47. package/lib/parsers/XprTree.js +25 -16
  48. package/lib/render/toCdl.js +902 -414
  49. package/lib/render/toHdbcds.js +1 -1
  50. package/lib/render/toSql.js +8 -0
  51. package/lib/render/utils/common.js +2 -2
  52. package/lib/render/utils/operators.js +160 -0
  53. package/lib/render/utils/pretty.js +337 -0
  54. package/lib/sql-identifier.js +7 -9
  55. package/lib/transform/addTenantFields.js +39 -41
  56. package/lib/transform/db/applyTransformations.js +4 -4
  57. package/lib/transform/db/assertUnique.js +6 -5
  58. package/lib/transform/db/associations.js +3 -3
  59. package/lib/transform/db/assocsToQueries/transformExists.js +13 -13
  60. package/lib/transform/db/assocsToQueries/utils.js +8 -0
  61. package/lib/transform/db/backlinks.js +19 -14
  62. package/lib/transform/db/constraints.js +6 -6
  63. package/lib/transform/db/expansion.js +1 -1
  64. package/lib/transform/db/flattening.js +2 -2
  65. package/lib/transform/db/groupByOrderBy.js +1 -1
  66. package/lib/transform/db/processSqlServices.js +3 -3
  67. package/lib/transform/db/rewriteCalculatedElements.js +2 -2
  68. package/lib/transform/db/temporal.js +7 -9
  69. package/lib/transform/db/views.js +6 -6
  70. package/lib/transform/draft/odata.js +2 -0
  71. package/lib/transform/effective/annotations.js +1 -1
  72. package/lib/transform/effective/associations.js +1 -1
  73. package/lib/transform/effective/main.js +1 -0
  74. package/lib/transform/effective/service.js +2 -2
  75. package/lib/transform/forRelationalDB.js +11 -5
  76. package/lib/transform/localized.js +2 -0
  77. package/lib/transform/odata/adaptAnnotationRefs.js +10 -9
  78. package/lib/transform/parseExpr.js +2 -2
  79. package/lib/transform/transformUtils.js +9 -7
  80. package/lib/transform/translateAssocsToJoins.js +0 -2
  81. package/lib/transform/universalCsn/coreComputed.js +2 -2
  82. package/lib/utils/moduleResolve.js +7 -5
  83. package/package.json +1 -1
  84. package/share/messages/def-upcoming-virtual-change.md +55 -0
  85. package/share/messages/file-unexpected-case-mismatch.md +61 -0
  86. package/share/messages/message-explanations.json +2 -0
  87. package/lib/transform/braceExpression.js +0 -77
@@ -57,6 +57,8 @@ function addTenantFields( csn, options, messageFunctions ) {
57
57
  initDefinition,
58
58
  artifactRef,
59
59
  effectiveType,
60
+ queryForElements,
61
+ $getQueries,
60
62
  msgLocations,
61
63
  } = csnRefs( csn, true );
62
64
 
@@ -100,7 +102,10 @@ function addTenantFields( csn, options, messageFunctions ) {
100
102
  // the cache of csnRefs):
101
103
  for (const name in definitions) {
102
104
  const art = definitions[name];
103
- addTenantFieldToArt(art, options);
105
+ if (addTenantFieldToArt( art, options ) && (art.query || art.projection)) {
106
+ for (const qcache of $getQueries( art ).slice( 1 ))
107
+ addTenantFieldToArt( qcache._select, options, true );
108
+ }
104
109
  }
105
110
 
106
111
  (csn.extensions || []).forEach( ( ext, idx ) => {
@@ -152,25 +157,36 @@ function addTenantFields( csn, options, messageFunctions ) {
152
157
 
153
158
  // Queries --------------------------------------------------------------------
154
159
 
155
- function handleQuery( query ) {
156
- // TODO: errors are temporary: start with simple projections only = no better
157
- // message $location necessary yet - better: set `name` in csnRefs
158
- if (!projection || query.ref && handleQuerySource( query ))
160
+ function handleQuery( query, fromSelect, parentQuery ) {
161
+ const select = query.SELECT || query.projection;
162
+ if (select)
163
+ csnPath[0] = query;
164
+ if (!projection || query.ref && handleQuerySource( query, fromSelect ))
159
165
  return;
160
166
 
161
- if (query !== projection && !independent) {
162
- error( 'tenant-unsupported-query', msgLocations( csnPath ), {},
163
- 'Can\'t yet have tenant-dependent non-simple query entities' );
164
- projection = null;
167
+ if (query !== projection && !independent &&
168
+ (fromSelect && !fromSelect.from.ref || !parentQuery?.SET)) {
169
+ // If a sub query would be supported in ORDER BY or LIMIT, the test above
170
+ // would not be enough
171
+ error( 'tenant-unsupported-query', msgLocations( csnPath ),
172
+ { '#': (fromSelect?.from?.join ? 'join' : 'subquery') },
173
+ {
174
+ std: 'Can\'t have tenant-dependent non-simple query entities',
175
+ join: 'Can\'t use a join in a tenant-dependent entity',
176
+ subquery: 'Can\'t use a sub query in a tenant-dependent entity',
177
+ } );
178
+ projection = null; // no further error
165
179
  return;
166
180
  }
167
181
 
168
- if (!query.projection && !query.SELECT)
182
+ if (!select)
169
183
  return; // query.SET or query.join
170
- csnPath[0] = query;
171
184
  csnPath.push( query.SELECT ? 'SELECT' : 'projection' );
172
185
 
173
- const select = query.SELECT || query.projection;
186
+ const qcache = queryForElements( query );
187
+ if (qcache.$queryNumber > 1)
188
+ handleElements( qcache );
189
+
174
190
  if (select.mixin)
175
191
  checkMixins( select.mixin );
176
192
  if (!independent) {
@@ -178,19 +194,12 @@ function addTenantFields( csn, options, messageFunctions ) {
178
194
  checkExcluding( select.excluding );
179
195
  if (select.columns)
180
196
  handleColumns( select.columns );
181
- // TODO: when we allow subqueries, we must also check for published in redirected assocs
182
- // TODO: for subqueries, we might need to adapt the inferred elements
183
- // TODO: where exists ref -
184
- // TODO: select and query clauses, especially with aggregation functions
185
197
  handleGroupBy( select );
186
198
  }
187
- else if (query !== projection && select.columns) {
188
- checkColumnCasts( select.columns );
189
- }
190
199
  csnPath.length = 1;
191
200
  }
192
201
 
193
- function handleQuerySource( query ) {
202
+ function handleQuerySource( query, fromSelect ) {
194
203
  if (independent) {
195
204
  const art = pathId(query.ref[0]); // yes, the base
196
205
  if (csn.definitions[art][annoTenantIndep])
@@ -202,8 +211,8 @@ function addTenantFields( csn, options, messageFunctions ) {
202
211
  } );
203
212
  return true;
204
213
  }
205
- if (query !== (projection.SELECT || projection.projection)?.from) // with `join`
206
- return false;
214
+ if (fromSelect && fromSelect.from !== query) // with JOIN
215
+ return false; // issue better error later
207
216
  if ((query.as || implicitAs( query.ref )) === tenantName) {
208
217
  error( 'tenant-invalid-alias-name', msgLocations( csnPath ),
209
218
  { name: tenantName, '#': (query.as ? 'std' : 'implicit') } );
@@ -265,20 +274,6 @@ function addTenantFields( csn, options, messageFunctions ) {
265
274
  : { ref: [ tenantName ] } );
266
275
  }
267
276
 
268
- function checkColumnCasts( columns, prop = 'columns' ) {
269
- csnPath.push( prop, -1 );
270
- for (const col of columns) {
271
- ++csnPath[csnPath.length - 1];
272
- if (col.cast?.target)
273
- handleAssociations( col.cast, null );
274
- else if (col.expand)
275
- checkColumnCasts( col.expand, 'expand' );
276
- else if (col.inline)
277
- checkColumnCasts( col.inline, 'inline' );
278
- }
279
- csnPath.length -= 2;
280
- }
281
-
282
277
  // Associations ---------------------------------------------------------------
283
278
 
284
279
  function handleAssociations( elem, afterRecursion ) {
@@ -419,12 +414,15 @@ function isTenantDepEntity( art ) {
419
414
  return art?.kind === 'entity' && !art[annoTenantIndep];
420
415
  }
421
416
 
422
- function addTenantFieldToArt( art, options ) {
417
+ function addTenantFieldToArt( art, options, isQuery = false ) {
418
+ if (!isQuery && !isTenantDepEntity( art ))
419
+ return false;
423
420
  const tenantName = options.tenantDiscriminator === true ? 'tenant' : options.tenantDiscriminator;
424
-
425
- if (isTenantDepEntity( art ))
426
- art.elements = { [tenantName]: { ...tenantDef }, ...art.elements };
427
- // consider non-enumerable `elements` of subqueries if that is supported
421
+ const elements = Object.getOwnPropertyDescriptor( art, 'elements' );
422
+ // `query.elements` is usually non-enumerable
423
+ elements.value = { [tenantName]: { ...tenantDef }, ...elements.value };
424
+ Object.defineProperty( art, 'elements', elements );
425
+ return true;
428
426
  }
429
427
 
430
428
  module.exports = {
@@ -131,14 +131,14 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
131
131
  if (options.skipDict?.[_prop] || dict == null) // with universal CSN, dictionaries might be null
132
132
  return;
133
133
  csnPath.push( _prop );
134
- context[`$in_${_prop}`] = true;
134
+ context[`$in_${ _prop }`] = true;
135
135
  for (const name of Object.getOwnPropertyNames( dict ))
136
136
  dictEntry( dict, name, dict[name] );
137
137
 
138
138
  if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
139
- setProp(node, `$${_prop}`, dict);
139
+ setProp(node, `$${ _prop }`, dict);
140
140
  csnPath.pop();
141
- context[`$in_${_prop}`] = undefined;
141
+ context[`$in_${ _prop }`] = undefined;
142
142
  }
143
143
 
144
144
  /**
@@ -188,7 +188,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
188
188
  }
189
189
  }
190
190
  if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
191
- setProp(node, `$${_prop}`, dict);
191
+ setProp(node, `$${ _prop }`, dict);
192
192
  csnPath.pop();
193
193
  }
194
194
 
@@ -322,16 +322,17 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
322
322
 
323
323
  // now add the index for HANA CDS
324
324
  if (options.transformation === 'hdbcds') {
325
- const index = [ 'unique', 'index', { ref: [ uniqueConstraint ] }, 'on', '(' ];
325
+ const cond = [];
326
326
  let i = 0;
327
327
  for (const constraint of rewrittenPaths) {
328
328
  if (i > 0)
329
- index.push(',');
330
- index.push(constraint);
329
+ cond.push(',');
330
+ cond.push(constraint);
331
331
  i++;
332
332
  }
333
- index.push(')');
334
- artifact.technicalConfig.hana.indexes[uniqueConstraint] = index;
333
+ artifact.technicalConfig.hana.indexes[uniqueConstraint] = [
334
+ 'unique', 'index', { ref: [ uniqueConstraint ] }, 'on', { xpr: cond },
335
+ ];
335
336
  }
336
337
  }
337
338
  artifact.$tableConstraints.unique = uniqueConstraints;
@@ -62,7 +62,7 @@ function attachOnConditions( csn, csnUtils, pathDelimiter, iterateOptions = {},
62
62
  ...foreignKey.ref,
63
63
  ],
64
64
  };
65
- const fkName = `${elemName}${pathDelimiter}${foreignKey.as || implicitAs(foreignKey.ref)}`;
65
+ const fkName = `${ elemName }${ pathDelimiter }${ foreignKey.as || implicitAs(foreignKey.ref) }`;
66
66
  const fKeyArg = {
67
67
  ref: [
68
68
  ...fkName.startsWith('$') ? [ '$self' ] : [],
@@ -189,14 +189,14 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
189
189
  // Doesn't work when ref-target (filter condition) or similar is used
190
190
  !ref.slice(i).some(refElement => typeof refElement !== 'string')) {
191
191
  const fkRef = ref[i + 1];
192
- const fkName = (!fkAlias ? fkRef : `${fkRef}${pathDelimiter}${fkAlias}`);
192
+ const fkName = (!fkAlias ? fkRef : `${ fkRef }${ pathDelimiter }${ fkAlias }`);
193
193
  const fks = link.art.keys.filter(key => key.ref[0] === fkName);
194
194
 
195
195
  if (fks.length >= 1) { // after flattening, at most one FK will remain.
196
196
  // `.as` is set for SQL, but not for OData -> fall back to implicit alias
197
197
  fkAlias = fks[0].as || fks[0].ref[fks[0].ref.length - 1];
198
198
  const managedAssocStepName = ref[i];
199
- const newFkName = `${managedAssocStepName}${pathDelimiter}${fkAlias}`;
199
+ const newFkName = `${ managedAssocStepName }${ pathDelimiter }${ fkAlias }`;
200
200
  if (isColumns) {
201
201
  refOwner.ref = [ ...ref.slice(0, i), newFkName ];
202
202
  if (!refOwner.as)
@@ -283,23 +283,23 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
283
283
  }
284
284
  }
285
285
 
286
- // TODO: parentheses around sub expressions are to be represented by inner `xpr`
287
- if (extension.length > 3)
288
- subselect.SELECT.where.push('('); // add braces around the on-condition part to ensure precedence is kept
289
-
290
286
  // TODO: add tenant comparison here ?
291
- subselect.SELECT.where.push(...extension);
292
-
293
- if (extension.length > 3)
294
- subselect.SELECT.where.push(')');
287
+ if (extension.length > 3) {
288
+ // make on-condition part sub-xpr to ensure precedence is kept
289
+ subselect.SELECT.where.push({ xpr: extension });
290
+ }
291
+ else {
292
+ subselect.SELECT.where.push(...extension);
293
+ }
295
294
 
296
295
  newExpr.push('exists');
297
- if (ref && ref.where) {
296
+ if (ref?.where) {
298
297
  const remappedWhere = remapExistingWhere(target, ref.where, exprPath, current);
298
+ subselect.SELECT.where.push('and');
299
299
  if (remappedWhere.length > 3)
300
- subselect.SELECT.where.push(...[ 'and', '(', ...remappedWhere, ')' ]);
300
+ subselect.SELECT.where.push( { xpr: remappedWhere } );
301
301
  else
302
- subselect.SELECT.where.push(...[ 'and', ...remappedWhere ]);
302
+ subselect.SELECT.where.push( ...remappedWhere );
303
303
  }
304
304
 
305
305
  newExpr.push(subselect);
@@ -332,10 +332,10 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
332
332
  * @returns {CSN.Query}
333
333
  */
334
334
  function getSubselect( target, assocRef, _sources ) {
335
- let subselectAlias = `_${assocRef.id ? assocRef.id : assocRef}_exists`;
335
+ let subselectAlias = `_${ assocRef.id ? assocRef.id : assocRef }_exists`;
336
336
 
337
337
  while (_sources[subselectAlias])
338
- subselectAlias = `_${subselectAlias}`;
338
+ subselectAlias = `_${ subselectAlias }`;
339
339
 
340
340
  const subselect = {
341
341
  SELECT: {
@@ -445,3 +445,11 @@ function getHelpers( csn, inspectRef, error ) {
445
445
 
446
446
 
447
447
  module.exports = { getHelpers };
448
+
449
+ /**
450
+ * @typedef {Token[]} TokenStream Array of tokens.
451
+ */
452
+
453
+ /**
454
+ * @typedef {string|object} Token Could be an object or a string - strings are usually operators.
455
+ */
@@ -69,7 +69,8 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
69
69
  */
70
70
  function processBacklinkAssoc( elem, elemName, art, artName, pathToOn ) {
71
71
  // Don't add braces if it is a single expression (ignoring superfluous braces)
72
- const multipleExprs = elem.on.filter(x => x !== '(' && x !== ')' ).length > 3;
72
+ // TODO: This check is too simplistic and probably adds superfluous parentheses.
73
+ const multipleExprs = elem.on.length > 3;
73
74
  elem.on = processExpressionArgs(elem.on, pathToOn);
74
75
  const column = csnUtils.getColumn(elem);
75
76
  if (column?.cast?.on) // avoid difference between column and element
@@ -92,26 +93,30 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
92
93
  // (if so, replace all three tokens with the condition generated from the other side, in parentheses)
93
94
  if (isDollarSelfOrProjectionOperand(xprArgs[i]) && isAssociationOperand(xprArgs[i + 2], path.concat([ i + 2 ]), csnUtils.inspectRef)) {
94
95
  const assoc = csnUtils.inspectRef(path.concat([ i + 2 ])).art;
95
- if (multipleExprs)
96
- result.push('(');
97
96
  const backlinkName = xprArgs[i + 2].ref[xprArgs[i + 2].ref.length - 1];
98
- result.push(...transformDollarSelfComparison(xprArgs[i + 2],
99
- assoc,
100
- backlinkName,
101
- elem, elemName, art, artName, path.concat([ i ])));
97
+ const comparison = transformDollarSelfComparison(
98
+ xprArgs[i + 2], assoc, backlinkName,
99
+ elem, elemName, art, artName, path.concat([ i ])
100
+ );
101
+
102
102
  if (multipleExprs)
103
- result.push(')');
103
+ result.push({ xpr: comparison });
104
+ else
105
+ result.push(...comparison);
106
+
104
107
  i += 3;
105
108
  attachBacklinkInformation(backlinkName);
106
109
  }
107
110
  else if (isDollarSelfOrProjectionOperand(xprArgs[i + 2]) && isAssociationOperand(xprArgs[i], path.concat([ i ]), csnUtils.inspectRef)) {
108
111
  const assoc = csnUtils.inspectRef(path.concat([ i ])).art;
109
- if (multipleExprs)
110
- result.push('(');
111
112
  const backlinkName = xprArgs[i].ref[xprArgs[i].ref.length - 1];
112
- result.push(...transformDollarSelfComparison(xprArgs[i], assoc, backlinkName, elem, elemName, art, artName, path.concat([ i + 2 ])));
113
+ const comparison = transformDollarSelfComparison(xprArgs[i], assoc, backlinkName, elem, elemName, art, artName, path.concat([ i + 2 ]));
114
+
113
115
  if (multipleExprs)
114
- result.push(')');
116
+ result.push({ xpr: comparison });
117
+ else
118
+ result.push(...comparison);
119
+
115
120
  i += 3;
116
121
  attachBacklinkInformation(backlinkName);
117
122
  }
@@ -205,7 +210,7 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
205
210
  return transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, assocName, elemName, art, path);
206
211
  }
207
212
 
208
- throw new ModelError(`Expected either managed or unmanaged association in $self-comparison: ${JSON.stringify(elem.on)}`);
213
+ throw new ModelError(`Expected either managed or unmanaged association in $self-comparison: ${ JSON.stringify(elem.on) }`);
209
214
  }
210
215
 
211
216
 
@@ -235,7 +240,7 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
235
240
  // Depending on naming conventions, the foreign key may two path steps (hdbcds) or be a single path step with a flattened name (plain, quoted)
236
241
  // With to.hdbcds in conjunction with hdbcds naming, we need to NOT use the alias - else we get deployment errors
237
242
  const keyName = k.as && doA2J ? [ k.as ] : k.ref;
238
- const fKeyPath = !doA2J ? [ assocName, ...keyName ] : [ `${assocName}${pathDelimiter}${keyName[0]}` ];
243
+ const fKeyPath = !doA2J ? [ assocName, ...keyName ] : [ `${ assocName }${ pathDelimiter }${ keyName[0] }` ];
239
244
  // FIXME: _artifact to the args ???
240
245
  const a = [
241
246
  {
@@ -57,7 +57,7 @@ function createReferentialConstraints( csn, options ) {
57
57
  });
58
58
  }
59
59
  // for `texts` compositions, we may generate foreign key constraints even w/o `up_`
60
- else if (elementName === 'texts' && element.target === `${path[path.length - 1]}.texts`) {
60
+ else if (elementName === 'texts' && element.target === `${ path[path.length - 1] }.texts`) {
61
61
  const { on } = element;
62
62
  const textsEntity = csn.definitions[element.target];
63
63
  // `texts` entities have a key named "locale"
@@ -568,14 +568,14 @@ function createReferentialConstraints( csn, options ) {
568
568
  // comments in sqlite files are causing the JDBC driver to throw an error on deployment
569
569
  if (options.testMode && onDelete === 'CASCADE') {
570
570
  if ($foreignKeyConstraint.upLinkFor?.texts)
571
- onDeleteRemark = `Constraint originates from localized composition ”${$foreignKeyConstraint.parentTable}:texts“`;
571
+ onDeleteRemark = `Constraint originates from localized composition ”${ $foreignKeyConstraint.parentTable }:texts“`;
572
572
  else
573
- onDeleteRemark = `Up_ link for Composition "${$foreignKeyConstraint.upLinkFor}" implies existential dependency`;
573
+ onDeleteRemark = `Up_ link for Composition "${ $foreignKeyConstraint.upLinkFor }" implies existential dependency`;
574
574
  }
575
575
  // constraint identifier usually start with `c__` to avoid name clashes
576
576
  let identifier = options.pre2134ReferentialConstraintNames ? '' : 'c__';
577
- identifier += `${getResultingName(csn, options.sqlMapping, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`;
578
- referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.sourceAssociation}`] = {
577
+ identifier += `${ getResultingName(csn, options.sqlMapping, artifactName) }_${ $foreignKeyConstraint.sourceAssociation }`;
578
+ referentialConstraints[`${ getResultingName(csn, 'quoted', artifactName) }_${ $foreignKeyConstraint.sourceAssociation }`] = {
579
579
  identifier,
580
580
  foreignKey: dependentKey,
581
581
  parentKey,
@@ -605,7 +605,7 @@ function assertConstraintIdentifierUniqueness( artifact, artifactName, path, err
605
605
  return;
606
606
 
607
607
  forEachKey(artifact.$tableConstraints.unique, (uniqueConstraintKey) => {
608
- const uniqueConstraintIdentifier = `${artifactName}_${uniqueConstraintKey}`; // final unique constraint identifier will be generated in renderer likewise
608
+ const uniqueConstraintIdentifier = `${ artifactName }_${ uniqueConstraintKey }`; // final unique constraint identifier will be generated in renderer likewise
609
609
  if (artifact.$tableConstraints.referential[uniqueConstraintIdentifier]) {
610
610
  error(null, path,
611
611
  { name: uniqueConstraintIdentifier, art: artifactName },
@@ -99,7 +99,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
99
99
  if (rewritten.toMany.length > 0 && !options.toOdata) {
100
100
  markAsToDummify(artifact, path[1]);
101
101
  rewritten.toMany.forEach(({ art }) => {
102
- error( null, art.$path || [ 'definitions', path[1] ], { name: `${art.$env || path[1]}:${art.ref.map(r => r.id || r)}` }, 'Unexpected .expand with to-many association $(NAME)');
102
+ error( null, art.$path || [ 'definitions', path[1] ], { name: `${ art.$env || path[1] }:${ art.ref.map(r => r.id || r) }` }, 'Unexpected .expand with to-many association $(NAME)');
103
103
  });
104
104
  }
105
105
  else {
@@ -751,7 +751,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
751
751
  // we have reached a leaf element, create a foreign key
752
752
  else if (finalElement.type == null || isBuiltinType(finalElement.type)) {
753
753
  const newFk = Object.create(null);
754
- [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`) ].forEach((prop) => {
754
+ [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${ f }`) ].forEach((prop) => {
755
755
  // copy props from original element to preserve derived types!
756
756
  if (element[prop] !== undefined)
757
757
  newFk[prop] = element[prop];
@@ -761,7 +761,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
761
761
 
762
762
  fks.forEach((fk) => {
763
763
  // prepend current prefix
764
- fk[0] = `${prefix}${pathDelimiter}${fk[0]}`;
764
+ fk[0] = `${ prefix }${ pathDelimiter }${ fk[0] }`;
765
765
  // if this is the entry association, decorate the final foreign keys with the association props
766
766
  if (lvl === 0) {
767
767
  if (options.transformation !== 'effective')
@@ -91,7 +91,7 @@ function replaceAssociationsInGroupByOrderBy( inputQuery, options, inspectRef, e
91
91
  function getForeignKeyRefs( assoc ) {
92
92
  return assoc.keys.map((fk) => {
93
93
  if (!fk.$generatedFieldName)
94
- throw new ModelError(`Expecting generated field name for foreign key: ${JSON.stringify(fk)}`);
94
+ throw new ModelError(`Expecting generated field name for foreign key: ${ JSON.stringify(fk) }`);
95
95
 
96
96
  return { ref: [ fk.$generatedFieldName ] };
97
97
  });
@@ -101,10 +101,10 @@ function createServiceDummy(artifact, artifactName, csn, { error }) {
101
101
  delete dummy['@cds.persistence.exists'];
102
102
  delete dummy.$ignore;
103
103
 
104
- if (csn.definitions[`dummy.${artifactName}`])
105
- error(null, [ 'definitions', artifactName ], { name: `dummy.${artifactName}` }, 'Generated artifact name $(NAME) conflicts with existing entity');
104
+ if (csn.definitions[`dummy.${ artifactName }`])
105
+ error(null, [ 'definitions', artifactName ], { name: `dummy.${ artifactName }` }, 'Generated artifact name $(NAME) conflicts with existing entity');
106
106
  else
107
- csn.definitions[`dummy.${artifactName}`] = dummy;
107
+ csn.definitions[`dummy.${ artifactName }`] = dummy;
108
108
  }
109
109
 
110
110
  module.exports = {
@@ -384,12 +384,12 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
384
384
  }
385
385
  }
386
386
  else {
387
- throw new CompilerAssertion(`Unhandled arg type: ${JSON.stringify(arg, null, 2)}`);
387
+ throw new CompilerAssertion(`Unhandled arg type: ${ JSON.stringify(arg, null, 2) }`);
388
388
  }
389
389
  }
390
390
  return mergedElements;
391
391
  }
392
- throw new CompilerAssertion(`Unhandled query type: ${JSON.stringify(SELECT, null, 2)}`);
392
+ throw new CompilerAssertion(`Unhandled query type: ${ JSON.stringify(SELECT, null, 2) }`);
393
393
  }
394
394
 
395
395
  /**
@@ -73,19 +73,17 @@ function getViewDecorator( csn, messageFunctions, csnUtils, options ) {
73
73
  const validFrom = { ref: [ '$valid', 'from' ] };
74
74
  const validTo = { ref: [ '$valid', 'to' ] };
75
75
 
76
- const cond = [ '(', fromPath, '<', validTo, 'and', toPath, '>', validFrom, ')' ];
77
-
78
- if (normalizedQuery.query.SELECT.where) { // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
79
- normalizedQuery.query.SELECT.where = [ '(', ...normalizedQuery.query.SELECT.where, ')', 'and', ...cond ];
80
- }
81
- else {
82
- normalizedQuery.query.SELECT.where = cond;
83
- }
76
+ const cond = { xpr: [ fromPath, '<', validTo, 'and', toPath, '>', validFrom ] };
77
+
78
+ if (normalizedQuery.query.SELECT.where) // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
79
+ normalizedQuery.query.SELECT.where = [ { xpr: normalizedQuery.query.SELECT.where }, 'and', cond ];
80
+ else
81
+ normalizedQuery.query.SELECT.where = [ cond ];
84
82
  }
85
83
  }
86
84
  else {
87
85
  info(null, [ 'definitions', artifactName ],
88
- { source: `${from[0].errorParent}.${from[0].name}`, target: `${to[0].errorParent}.${to[0].name}` },
86
+ { source: `${ from[0].errorParent }.${ from[0].name }`, target: `${ to[0].errorParent }.${ to[0].name }` },
89
87
  'No temporal WHERE clause added as $(SOURCE) and $(TARGET) are not of same origin');
90
88
  }
91
89
  }
@@ -145,10 +145,10 @@ function getViewTransformer( csn, options, messageFunctions ) {
145
145
  const matchingCombined = $combined[elemName];
146
146
  // Internal errors - this should never happen!
147
147
  if (matchingCombined.length > 1) { // should already be caught by compiler
148
- throw new ModelError(`Ambiguous name - can't be resolved: ${elemName}. Found in: ${matchingCombined.map(o => o.parent)}`);
148
+ throw new ModelError(`Ambiguous name - can't be resolved: ${ elemName }. Found in: ${ matchingCombined.map(o => o.parent) }`);
149
149
  }
150
150
  else if (matchingCombined.length === 0) { // no clue how this could happen? Invalid CSN?
151
- throw new ModelError(`No matching entry found in UNION of all elements for: ${elemName}`);
151
+ throw new ModelError(`No matching entry found in UNION of all elements for: ${ elemName }`);
152
152
  }
153
153
  alias = matchingCombined[0].parent;
154
154
  }
@@ -177,7 +177,7 @@ function getViewTransformer( csn, options, messageFunctions ) {
177
177
  ref,
178
178
  };
179
179
  if (assocCol.as) {
180
- const columnName = `${assocCol.as}${pathDelimiter}${foreignKey.as}`;
180
+ const columnName = `${ assocCol.as }${ pathDelimiter }${ foreignKey.as }`;
181
181
  result.as = columnName;
182
182
  }
183
183
 
@@ -229,9 +229,9 @@ function getViewTransformer( csn, options, messageFunctions ) {
229
229
 
230
230
 
231
231
  // Create an unused alias name for the MIXIN - use 3 _ to avoid collision with usings
232
- let mixinElemName = `___${mixinName || elemName}`;
232
+ let mixinElemName = `___${ mixinName || elemName }`;
233
233
  while (elements[mixinElemName])
234
- mixinElemName = `_${mixinElemName}`;
234
+ mixinElemName = `_${ mixinElemName }`;
235
235
 
236
236
  // Copy the association element to the MIXIN clause under its alias name
237
237
  // Needs to be a deep copy, as we transform the on-condition
@@ -327,7 +327,7 @@ function getViewTransformer( csn, options, messageFunctions ) {
327
327
  function transformViewOrEntity( query, artifact, artName, path ) {
328
328
  const ignoreAssociations = options.sqlDialect === 'hana' && options.withHanaAssociations === false;
329
329
  csnUtils.initDefinition(artifact);
330
- const { elements } = queryOrMain(query, artifact);
330
+ const { elements } = queryOrMain(query, artifact); // TODO: use queryForElements
331
331
  // We use the elements from the leading query/main artifact - adapt the path
332
332
  const elementsPath = elements === artifact.elements ? path.slice(0, 2).concat('elements') : path.concat('elements');
333
333
  const queryPath = path;
@@ -188,6 +188,8 @@ function generateDrafts( csn, options, services, messageFunctions ) {
188
188
  setAnnotation(artifact, '@Common.SideEffects#alwaysFetchMessages.TargetProperties', ['DraftMessages'] );
189
189
  }
190
190
  setAnnotation(artifact, '@Common.Messages', { '=': 'DraftMessages', ref: ['DraftMessages'] });
191
+ const service = csn.definitions[getServiceOfArtifact(artifactName, services)];
192
+ setAnnotation(service, '@Common.AddressViaNavigationPath', true);
191
193
  }
192
194
 
193
195
  // Iterate elements
@@ -172,7 +172,7 @@ function remapODataAnnotations( csn ) {
172
172
  */
173
173
  function remapAnnotationsOnElement( artifact, element ) {
174
174
  if (element.elements && !element.$ignore) // We expect to only be called on flattened CSN - error if we encounter .elements!
175
- throw new CompilerAssertion(`Expected a flat model. Found element with subelements: ${JSON.stringify(element)}`);
175
+ throw new CompilerAssertion(`Expected a flat model. Found element with subelements: ${ JSON.stringify(element) }`);
176
176
  for (const prop in element) {
177
177
  if (directMappings[prop])
178
178
  directMappings[prop](csn, artifact, element, prop);
@@ -104,7 +104,7 @@ function addForeignKeyToColumns( foreignKey, columns, associationColumn, options
104
104
  ref,
105
105
  };
106
106
  if (associationColumn.as) {
107
- const columnName = `${associationColumn.as}_${foreignKey.as || implicitAs(foreignKey.ref)}`;
107
+ const columnName = `${ associationColumn.as }_${ foreignKey.as || implicitAs(foreignKey.ref) }`;
108
108
  result.as = columnName;
109
109
  }
110
110
 
@@ -34,6 +34,7 @@ const getServiceFilterFunction = require('./service');
34
34
  */
35
35
  function effectiveCsn( model, options, messageFunctions ) {
36
36
  const csn = cloneFullCsn(model, options);
37
+ delete csn.namespace; // must not be set for effective CSN
37
38
  delete csn.vocabularies; // must not be set for effective CSN
38
39
  messageFunctions.setModel(csn);
39
40
 
@@ -16,13 +16,13 @@ function getServiceFilterFunction(serviceName, collector) {
16
16
  if (artifactName === serviceName && artifact.kind === 'service') {
17
17
  collector.service = artifact;
18
18
  }
19
- else if (definitions[serviceName]?.kind === 'service' && artifactName.startsWith(`${serviceName }.`)) {
19
+ else if (definitions[serviceName]?.kind === 'service' && artifactName.startsWith(`${ serviceName }.`)) {
20
20
  collector.containedArtifacts[artifactName] = artifact;
21
21
  delete artifact.query;
22
22
  delete artifact.projection;
23
23
  if (artifact.elements) {
24
24
  forEach(artifact.elements, (elementName, element) => {
25
- if (element.target && !element.target.startsWith(`${serviceName }.`))
25
+ if (element.target && !element.target.startsWith(`${ serviceName }.`))
26
26
  delete artifact.elements[elementName];
27
27
  });
28
28
  }