@sap/cds-compiler 4.8.0 → 4.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +38 -4
  2. package/bin/cds_remove_invalid_whitespace.js +135 -0
  3. package/bin/cds_update_annotations.js +180 -0
  4. package/bin/cds_update_identifiers.js +3 -4
  5. package/bin/cdsc.js +30 -17
  6. package/doc/CHANGELOG_BETA.md +19 -0
  7. package/lib/api/main.js +59 -24
  8. package/lib/api/options.js +12 -1
  9. package/lib/api/validate.js +1 -5
  10. package/lib/base/builtins.js +27 -0
  11. package/lib/base/message-registry.js +38 -21
  12. package/lib/base/messages.js +51 -20
  13. package/lib/base/model.js +4 -5
  14. package/lib/checks/actionsFunctions.js +2 -2
  15. package/lib/checks/annotationsOData.js +3 -0
  16. package/lib/checks/defaultValues.js +5 -2
  17. package/lib/checks/queryNoDbArtifacts.js +3 -2
  18. package/lib/checks/validator.js +2 -34
  19. package/lib/compiler/assert-consistency.js +10 -3
  20. package/lib/compiler/checks.js +44 -18
  21. package/lib/compiler/define.js +38 -30
  22. package/lib/compiler/extend.js +33 -10
  23. package/lib/compiler/index.js +0 -1
  24. package/lib/compiler/lsp-api.js +5 -0
  25. package/lib/compiler/populate.js +0 -2
  26. package/lib/compiler/propagator.js +23 -19
  27. package/lib/compiler/resolve.js +48 -29
  28. package/lib/compiler/shared.js +60 -20
  29. package/lib/compiler/tweak-assocs.js +72 -116
  30. package/lib/compiler/xpr-rewrite.js +762 -0
  31. package/lib/edm/annotations/edmJson.js +24 -7
  32. package/lib/edm/annotations/genericTranslation.js +81 -61
  33. package/lib/edm/edm.js +4 -4
  34. package/lib/edm/edmInboundChecks.js +33 -0
  35. package/lib/edm/edmPreprocessor.js +9 -6
  36. package/lib/gen/Dictionary.json +129 -14
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +1 -1
  39. package/lib/gen/languageParser.js +1523 -1518
  40. package/lib/json/from-csn.js +13 -4
  41. package/lib/json/to-csn.js +12 -12
  42. package/lib/language/genericAntlrParser.js +14 -6
  43. package/lib/main.d.ts +67 -14
  44. package/lib/main.js +1 -0
  45. package/lib/model/cloneCsn.js +6 -3
  46. package/lib/model/csnRefs.js +23 -11
  47. package/lib/model/csnUtils.js +13 -7
  48. package/lib/model/enrichCsn.js +3 -1
  49. package/lib/model/revealInternalProperties.js +2 -1
  50. package/lib/model/sortViews.js +14 -6
  51. package/lib/modelCompare/compare.js +33 -34
  52. package/lib/optionProcessor.js +27 -2
  53. package/lib/render/DuplicateChecker.js +6 -6
  54. package/lib/render/manageConstraints.js +1 -0
  55. package/lib/render/toCdl.js +3 -1
  56. package/lib/transform/db/applyTransformations.js +33 -0
  57. package/lib/transform/db/constraints.js +75 -28
  58. package/lib/transform/db/expansion.js +8 -3
  59. package/lib/transform/db/flattening.js +2 -2
  60. package/lib/transform/db/groupByOrderBy.js +2 -2
  61. package/lib/transform/db/temporal.js +6 -3
  62. package/lib/transform/db/transformExists.js +2 -2
  63. package/lib/transform/effective/annotations.js +194 -0
  64. package/lib/transform/effective/main.js +6 -8
  65. package/lib/transform/effective/misc.js +31 -10
  66. package/lib/transform/forOdata.js +23 -7
  67. package/lib/transform/forRelationalDB.js +3 -3
  68. package/lib/transform/localized.js +7 -6
  69. package/lib/transform/odata/flattening.js +221 -124
  70. package/lib/transform/odata/toFinalBaseType.js +1 -1
  71. package/lib/transform/odata/typesExposure.js +15 -12
  72. package/lib/transform/parseExpr.js +4 -4
  73. package/lib/transform/transformUtils.js +47 -42
  74. package/lib/transform/translateAssocsToJoins.js +47 -47
  75. package/lib/transform/universalCsn/universalCsnEnricher.js +16 -19
  76. package/package.json +1 -1
  77. package/share/messages/anno-missing-rewrite.md +45 -0
  78. package/share/messages/message-explanations.json +1 -0
  79. package/bin/.eslintrc.json +0 -17
  80. package/lib/api/.eslintrc.json +0 -37
  81. package/lib/checks/.eslintrc.json +0 -31
  82. package/lib/compiler/.eslintrc.json +0 -8
  83. package/lib/edm/.eslintrc.json +0 -46
  84. package/lib/inspect/.eslintrc.json +0 -4
  85. package/lib/json/.eslintrc.json +0 -4
  86. package/lib/language/.eslintrc.json +0 -4
  87. package/lib/model/.eslintrc.json +0 -13
  88. package/lib/modelCompare/utils/.eslintrc.json +0 -22
  89. package/lib/render/.eslintrc.json +0 -22
  90. package/lib/transform/.eslintrc.json +0 -13
  91. package/lib/transform/db/.eslintrc.json +0 -41
  92. package/lib/transform/draft/.eslintrc.json +0 -4
  93. package/lib/transform/effective/.eslintrc.json +0 -4
  94. package/lib/transform/universalCsn/.eslintrc.json +0 -37
  95. package/lib/utils/.eslintrc.json +0 -7
@@ -155,7 +155,8 @@ optionProcessor
155
155
  manageConstraints [options] <files...> (internal) Generate ALTER TABLE statements to
156
156
  add / modify referential constraints.
157
157
  inspect [options] <files...> (internal) Inspect the given CDS files.
158
- toEffectiveCsn [options] <files...> (internal) Get an effective CSN for SEAL; requires beta mode
158
+ toEffectiveCsn [options] <files...> (internal) Get an effective CSN; requires beta mode
159
+ forSeal [options] <files...> (internal) Get a SEAL CSN
159
160
 
160
161
  Environment variables
161
162
  NO_COLOR If set, compiler messages (/output) will not be colored.
@@ -546,6 +547,8 @@ optionProcessor.command('toEffectiveCsn')
546
547
  .option('-h, --help')
547
548
  .option('--resolve-simple-types <val>', { valid: ['true', 'false'] } )
548
549
  .option('--resolve-projections <val>', { valid: ['true', 'false'] } )
550
+ .option('--remap-odata-annotations <val>', { valid: ['true', 'false'] } )
551
+ .option('--keep-localized <val>', { valid: ['true', 'false'] } )
549
552
  .positionalArgument('<files...>')
550
553
  .help(`
551
554
  Usage: cdsc toEffectiveCsn [options] <files...>
@@ -560,8 +563,30 @@ optionProcessor.command('toEffectiveCsn')
560
563
  true: (default) resolve simple type references to their simple base type
561
564
  false: do not resolve simple type references
562
565
  --resolve-projections <val> Resolve projections:
563
- true: (default) resolve projections to ordinary views with SELECT
566
+ true: (default) transform projections into ordinary views with SELECT
564
567
  false: leave them as real projections
568
+ --remap-odata-annotations <val> Remap OData annotations to ABAP annotations:
569
+ true: remap annotations
570
+ false:(default) leave them as is
571
+ --keep-localized <val> Keep '.localized' property in the CSN:
572
+ true: property is kept
573
+ false:(default) property is deleted
574
+ `);
575
+
576
+ optionProcessor.command('forSeal')
577
+ .option('-h, --help')
578
+ .option('--remap-odata-annotations <val>', { valid: ['true', 'false'] } )
579
+ .positionalArgument('<files...>')
580
+ .help(`
581
+ Usage: cdsc forSeal [options] <files...>
582
+
583
+ (internal): Get the SEAL CSN model compiled from the provided CDS files.
584
+
585
+ Options
586
+ -h, --help Show this help text
587
+ --remap-odata-annotations <val> Remap OData annotations to ABAP annotations:
588
+ true: (default) remap annotations
589
+ false: leave them as is
565
590
  `);
566
591
 
567
592
  module.exports = {
@@ -97,12 +97,12 @@ class DuplicateChecker {
97
97
  else
98
98
  namingMode = 'plain';
99
99
 
100
- error(null, [ 'definitions', artifact.modelName ],
101
- { name: collidesWith.modelName, prop: namingMode, '#': artifact.modelName.includes('.') ? 'dots' : 'std' },
102
- {
103
- std: 'Artifact name can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
104
- dots: 'Artifact name containing dots can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
105
- });
100
+ error(null, [ 'definitions', artifact.modelName ], {
101
+ name: collidesWith.modelName, prop: namingMode, '#': artifact.modelName.includes('.') ? 'dots' : 'std',
102
+ }, {
103
+ std: 'Artifact name can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
104
+ dots: 'Artifact name containing dots can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
105
+ });
106
106
  });
107
107
  }
108
108
  artifacts.forEach((artifact) => {
@@ -32,6 +32,7 @@ function alterConstraintsWithCsn( csn, options, messageFunctions ) {
32
32
  warning(null, null, { prop: sqlDialect || 'plain' }, 'Referential Constraints are not available for sql dialect $(PROP)');
33
33
 
34
34
  if (drop && alter)
35
+ // eslint-disable-next-line cds-compiler/message-no-quotes
35
36
  error(null, null, 'Option “--drop” can\'t be combined with “--alter”');
36
37
 
37
38
  // Of course, we want the database constraints
@@ -614,10 +614,12 @@ function csnToCdl( csn, options, msg ) {
614
614
  else if (element['#'] !== undefined) { // enum symbol reference
615
615
  result += ` = #${element['#']}`;
616
616
  }
617
- else if (!isCalcElement || !isDirectAssocOrComp(element.type)) {
617
+ else if (!isCalcElement || !isDirectAssocOrComp(element.type) && !element.$filtered && !element.$enclosed) {
618
618
  // If the element is a calculated element _and_ a direct association or
619
619
  // composition, we'd render `Association to F on (cond) = calcValue;` which
620
620
  // would alter the ON-condition.
621
+ // If it is a calculated element _and_ an indirect association (via type chain),
622
+ // we'd get a cast to an association.
621
623
  const props = renderTypeReferenceAndProps(element, env);
622
624
  if (props !== '')
623
625
  result += ` : ${props}`;
@@ -344,7 +344,40 @@ function transformExpression( parent, propName, transformers, path = [] ) {
344
344
  return parent;
345
345
  }
346
346
 
347
+ /**
348
+ * Merge an array of transformer-objects into a single one, set the this-value of every subfunction to "that"
349
+ *
350
+ * @param {object[]} transformers transformers
351
+ * @param {object} that Value for this
352
+ * @returns {object} Remapped transformers.
353
+ */
354
+ function mergeTransformers( transformers, that ) {
355
+ const remapped = {};
356
+ for (const transformer of transformers) {
357
+ for (const [ n, fns ] of Object.entries(transformer)) {
358
+ if (!remapped[n])
359
+ remapped[n] = [];
360
+
361
+ if (Array.isArray(fns)) {
362
+ remapped[n].push((parent, name, prop, path, parentParent) => fns.forEach(
363
+ fn => fn.bind(that)(parent, name, prop, path, parentParent)
364
+ ));
365
+ }
366
+ else {
367
+ remapped[n].push((parent, name, prop, path, parentParent) => fns.bind(that)(parent, name, prop, path, parentParent));
368
+ }
369
+ }
370
+ }
371
+
372
+ for (const [ n, fns ] of Object.entries(remapped))
373
+ remapped[n] = (parent, name, prop, path, parentParent) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path, parentParent));
374
+
375
+
376
+ return remapped;
377
+ }
378
+
347
379
  module.exports = {
380
+ mergeTransformers,
348
381
  transformExpression,
349
382
  applyTransformations,
350
383
  applyTransformationsOnNonDictionary,
@@ -116,7 +116,7 @@ function createReferentialConstraints( csn, options ) {
116
116
  foreignKeyConstraintForAssociation(up_, dependent, [ 'definitions', composition.target, 'elements', upLinkName ], path[path.length - 1] );
117
117
  }
118
118
  else if (!onCondition && composition.keys.length > 0) {
119
- throw new CompilerAssertion('Please debug me, an on-condition was expected here, but only found keys');
119
+ throw new CompilerAssertion('Debug me, an on-condition was expected here, but only found keys');
120
120
  }
121
121
  }
122
122
 
@@ -187,28 +187,46 @@ function createReferentialConstraints( csn, options ) {
187
187
  * @param {CSN.PathSegment | object} upLinkFor the name of the composition which used this association in a `$self = <comp>.<up_>` comparison
188
188
  * it is used for a comment in the constraint, which is only printed out in test-mode
189
189
  */
190
- function attachConstraintsToDependentKeys( dependentKeys, parentKeys, parentTable, sourceAssociation, upLinkFor = null ) {
190
+ function attachConstraintsToDependentKeys(
191
+ dependentKeys,
192
+ parentKeys,
193
+ parentTable,
194
+ sourceAssociation,
195
+ upLinkFor = null
196
+ ) {
191
197
  while (dependentKeys.length > 0) {
192
198
  const dependentKeyValuePair = dependentKeys.pop();
193
199
  const dependentKey = dependentKeyValuePair[1];
194
200
  // if it already has a dependent key assigned, do not overwrite it.
195
201
  // this is the case for <up_> associations in on-conditions of compositions
196
- if (Object.prototype.hasOwnProperty.call(dependentKey, '$foreignKeyConstraint'))
202
+ const { $foreignKeyConstraint } = dependentKey;
203
+ // in contrast to foreign keys which stem from managed associations,
204
+ // a tenant foreign key column may have multiple parent keys as partners
205
+ const tenantForeignKey = isTenant && dependentKeyValuePair[0] === 'tenant';
206
+ if ($foreignKeyConstraint && (!tenantForeignKey || $foreignKeyConstraint.upLinkFor)) {
197
207
  return;
198
-
199
- const parentKeyValuePair = parentKeys.pop();
200
- const parentKeyName = parentKeyValuePair[0];
201
-
202
- const constraint = {
203
- parentKey: parentKeyName,
204
- parentTable,
205
- upLinkFor,
206
- sourceAssociation,
207
- onDelete: upLinkFor ? 'CASCADE' : 'RESTRICT',
208
- validated,
209
- enforced,
210
- };
211
- dependentKey.$foreignKeyConstraint = constraint;
208
+ }
209
+ else if ($foreignKeyConstraint && tenantForeignKey) {
210
+ parentKeys.pop();
211
+ $foreignKeyConstraint.sourceAssociation.push(sourceAssociation);
212
+ }
213
+ else {
214
+ const parentKeyValuePair = parentKeys.pop();
215
+ const parentKeyName = parentKeyValuePair[0];
216
+
217
+ const constraint = {
218
+ parentKey: parentKeyName,
219
+ parentTable,
220
+ upLinkFor,
221
+ sourceAssociation: tenantForeignKey
222
+ ? [ sourceAssociation ]
223
+ : sourceAssociation,
224
+ onDelete: upLinkFor ? 'CASCADE' : 'RESTRICT',
225
+ validated,
226
+ enforced,
227
+ };
228
+ dependentKey.$foreignKeyConstraint = constraint;
229
+ }
212
230
  }
213
231
  }
214
232
 
@@ -473,11 +491,11 @@ function createReferentialConstraints( csn, options ) {
473
491
  }
474
492
 
475
493
  /**
476
- * Creates the final referential constraints from all dependent key <-> parent key pairs stemming from the same $sourceAssociation
494
+ * Creates the final referential constraints from all dependent key <-> parent key pairs stemming from the same sourceAssociation
477
495
  * and attaches it to the given artifact.
478
496
  *
479
497
  * Go over all elements with $foreignKeyConstraint property:
480
- * - Find all other elements in artifact with the same $sourceAssociation
498
+ * - Find all other elements in artifact with the same sourceAssociation
481
499
  * - Create constraints with the information supplied by $parentKey, $parentTable and $onDelete
482
500
  *
483
501
  * @param {CSN.Artifact} artifact
@@ -485,6 +503,23 @@ function createReferentialConstraints( csn, options ) {
485
503
  */
486
504
  function collectAndAttachReferentialConstraints( artifact, artifactName ) {
487
505
  const referentialConstraints = Object.create(null);
506
+
507
+ // tenant foreign keys may have multiple parent keys
508
+ // process tenant foreign key first
509
+ if (isTenant && artifact.elements?.tenant) {
510
+ const element = artifact.elements.tenant;
511
+ if (element.$foreignKeyConstraint) {
512
+ const $foreignKeyConstraint = Object.assign({}, element.$foreignKeyConstraint);
513
+ delete element.$foreignKeyConstraint;
514
+ // create a foreign key constraint for the tenant column with each association in the dependent entity
515
+ $foreignKeyConstraint.sourceAssociation.forEach((sourceAssociation) => {
516
+ const copy = Object.assign({}, $foreignKeyConstraint);
517
+ copy.sourceAssociation = sourceAssociation;
518
+ createReferentialConstraints(copy, 'tenant');
519
+ });
520
+ }
521
+ }
522
+
488
523
  for (const elementName in artifact.elements) {
489
524
  const element = artifact.elements[elementName];
490
525
  if (!element.$foreignKeyConstraint)
@@ -492,16 +527,34 @@ function createReferentialConstraints( csn, options ) {
492
527
  // copy constraint property, and delete it from the element
493
528
  const $foreignKeyConstraint = Object.assign({}, element.$foreignKeyConstraint);
494
529
  delete element.$foreignKeyConstraint;
530
+ createReferentialConstraints($foreignKeyConstraint, elementName);
531
+ }
532
+ if (Object.keys(referentialConstraints).length) {
533
+ if (!('$tableConstraints' in artifact))
534
+ artifact.$tableConstraints = Object.create(null);
535
+
536
+ artifact.$tableConstraints.referential = referentialConstraints;
537
+ }
538
+
539
+ /**
540
+ * Creates referential constraints for database relationships. This function constructs constraints based on foreign key information and element names,
541
+ * and determines deletion rules based on the existing constraints and options. It manages dependencies and names for constraints dynamically during
542
+ * execution.
543
+ *
544
+ * @param {object} $foreignKeyConstraint - An object encapsulating details about the foreign key constraint
545
+ * @param {string} elementName - The name of the dependent element or table that is linked by the foreign key.
546
+ */
547
+ function createReferentialConstraints($foreignKeyConstraint, elementName) {
495
548
  const { parentTable } = $foreignKeyConstraint;
496
549
  const parentKey = [ $foreignKeyConstraint.parentKey ];
497
550
  const dependentKey = [ elementName ];
498
551
  const onDeleteRules = new Set();
499
552
  onDeleteRules.add($foreignKeyConstraint.onDelete);
500
553
  forEach(artifact.elements, (foreignKeyName, foreignKey) => {
501
- // find all other `$foreignKeyConstraint`s with same `$sourceAssociation` and same `parentTable`
554
+ // find all other `$foreignKeyConstraint`s with same `sourceAssociation` and same `parentTable`
502
555
  const matchingForeignKeyFound = foreignKey.$foreignKeyConstraint &&
503
- foreignKey.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
504
- foreignKey.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable;
556
+ foreignKey.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
557
+ foreignKey.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable;
505
558
  if (!matchingForeignKeyFound)
506
559
  return;
507
560
 
@@ -536,12 +589,6 @@ function createReferentialConstraints( csn, options ) {
536
589
  enforced: $foreignKeyConstraint.enforced,
537
590
  };
538
591
  }
539
- if (Object.keys(referentialConstraints).length) {
540
- if (!('$tableConstraints' in artifact))
541
- artifact.$tableConstraints = Object.create(null);
542
-
543
- artifact.$tableConstraints.referential = referentialConstraints;
544
- }
545
592
  }
546
593
  }
547
594
 
@@ -98,7 +98,9 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
98
98
  */
99
99
  if (rewritten.toMany.length > 0 && !options.toOdata) {
100
100
  markAsToDummify(artifact, path[1]);
101
- error( null, [ 'definitions', path[1] ], { name: path[1] }, 'Unexpected .expand with to-many association in entity $(NAME)');
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)');
103
+ });
102
104
  }
103
105
  else {
104
106
  parent.columns = rewritten.columns;
@@ -175,8 +177,11 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
175
177
  const target = art.target ? art.target : pathStep;
176
178
  if (toDummify.indexOf(target) !== -1) {
177
179
  error( null, obj.$path, {
178
- id: pathStep, elemref: obj, name,
179
- }, 'Unexpected “@cds.persistence.skip” annotation on Association target $(NAME) of $(ID) in path $(ELEMREF) was skipped because of .expand in conjunction with to-many');
180
+ id: pathStep,
181
+ elemref: obj,
182
+ name,
183
+ anno: '@cds.persistence.skip',
184
+ }, 'Unexpected $(ANNO) annotation on Association target $(NAME) of $(ID) in path $(ELEMREF) was skipped because of .expand in conjunction with to-many');
180
185
  }
181
186
  }
182
187
 
@@ -242,7 +242,7 @@ function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved,
242
242
  // full path into target, uncomment this line and
243
243
  // comment/remove setProp in expansion.js
244
244
  // setProp(parent, '$structRef', parent.ref);
245
- parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes);
245
+ [ parent.ref ] = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes);
246
246
  resolved.set(parent, { links, art, scope });
247
247
  // Explicitly set implicit alias for things that are now flattened - but only in columns
248
248
  // TODO: Can this be done elegantly during expand phase already?
@@ -543,7 +543,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
543
543
  if (clone.ref) {
544
544
  clone.ref[clone.ref.length - 1] = flatElemName;
545
545
  // Now we need to properly flatten the whole ref
546
- clone.ref = flattenStructStepsInRef(clone.ref, pathToKey);
546
+ [ clone.ref ] = flattenStructStepsInRef(clone.ref, pathToKey);
547
547
  }
548
548
  if (!clone.as) {
549
549
  clone.as = flatElemName;
@@ -28,8 +28,8 @@ function replaceAssociationsInGroupByOrderBy( inputQuery, options, inspectRef, e
28
28
  // (230 c) If we keep associations as they are (hdbcds naming convention), we can't have associations in GROUP BY
29
29
  if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
30
30
  error(null, groupByPath,
31
- { $reviewed: true },
32
- 'Unexpected managed association in GROUP BY for naming mode “hdbcds”');
31
+ { $reviewed: true, keyword: 'GROUP BY', value: 'hdbcds' },
32
+ 'Unexpected managed association in $(KEYWORD) for naming mode $(VALUE)');
33
33
  continue;
34
34
  }
35
35
  const pathPrefix = query.groupBy[i].ref.slice(0, -1);
@@ -4,7 +4,7 @@ const {
4
4
  getNormalizedQuery, hasAnnotationValue, forEachMember,
5
5
  } = require('../../model/csnUtils');
6
6
  const { implicitAs } = require('../../model/csnRefs');
7
- const { setProp } = require('../../base/model');
7
+ const { setProp, isBetaEnabled } = require('../../base/model');
8
8
  const { getTransformers } = require('../transformUtils');
9
9
 
10
10
  const validToString = '@cds.valid.to';
@@ -21,9 +21,10 @@ const validFromString = '@cds.valid.from';
21
21
  * @param {object} messageFunctions
22
22
  * @param {Function} messageFunctions.info
23
23
  * @param {object} csnUtils
24
+ * @param {object} options
24
25
  * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition applying the where-condition to views.
25
26
  */
26
- function getViewDecorator( csn, messageFunctions, csnUtils ) {
27
+ function getViewDecorator( csn, messageFunctions, csnUtils, options ) {
27
28
  const { info } = messageFunctions;
28
29
  const { get$combined } = csnUtils;
29
30
  return addTemporalWhereConditionToView;
@@ -52,7 +53,9 @@ function getViewDecorator( csn, messageFunctions, csnUtils ) {
52
53
  if (from.length === 1 && to.length === 1) {
53
54
  // and both are from the same origin
54
55
  if (from[0].source === to[0].source && from[0].parent === to[0].parent) {
55
- if (!hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0])) {
56
+ const omitWhereClause = isBetaEnabled(options, 'temporalRawProjection') &&
57
+ hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0]);
58
+ if (!omitWhereClause) {
56
59
  const fromPath = {
57
60
  ref: [
58
61
  from[0].parent,
@@ -616,10 +616,10 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
616
616
  else if (typeof xpr.$env === 'number') {
617
617
  if (xpr.$scope === 'mixin')
618
618
  return '';
619
- return error(null, xpr.$path, '$env with number is not handled yet - please report this error!');
619
+ return error(null, xpr.$path, '$env with number is not handled yet - report this error!');
620
620
  }
621
621
 
622
- return error(null, xpr.$path, 'Boolean $env is not handled yet - please report this error!');
622
+ return error(null, xpr.$path, 'Boolean $env is not handled yet - report this error!');
623
623
  }
624
624
  else if (xpr.ref) {
625
625
  throw new ModelError('Missing $env and missing leading artifact ref - throwing to trigger recompilation!');
@@ -0,0 +1,194 @@
1
+ 'use strict';
2
+
3
+ const { CompilerAssertion } = require('../../base/error');
4
+
5
+ const directMappings = {
6
+ '@Common.IsDayOfCalendarMonth': replace('@Semantics.calendar.dayOfMonth'),
7
+ '@Common.IsDayOfCalendarYear': replace('@Semantics.calendar.dayOfYear'),
8
+ '@Common.IsCalendarWeek': replace('@Semantics.calendar.week'),
9
+ '@Common.IsCalendarMonth': replace('@Semantics.calendar.month'),
10
+ '@Common.IsCalendarQuarter': replace('@Semantics.calendar.quarter'),
11
+ '@Common.IsCalendarHalfyear': replace('@Semantics.calendar.halfyear'),
12
+ '@Common.IsCalendarYear': replace('@Semantics.calendar.year'),
13
+ '@Common.IsCalendarYearWeek': replace('@Semantics.calendar.yearWeek'),
14
+ '@Common.IsCalendarYearMonth': replace('@Semantics.calendar.yearMonth'),
15
+ '@Common.IsCalendarYearQuarter': replace('@Semantics.calendar.yearQuarter'),
16
+ '@Common.IsCalendarYearHalfyear': replace('@Semantics.calendar.yearHalfyear'),
17
+ '@Common.IsCalendarDate': replace('@Semantics.date'),
18
+ '@Common.IsFiscalYearVariant': replace('@Semantics.yearVariant'),
19
+ '@Common.IsFiscalPeriod': replace('@Semantics.period'),
20
+ '@Common.IsFiscalYear': replace('@Semantics.year'),
21
+ '@Common.IsFiscalYearPeriod': replace('@Semantics.yearPeriod'),
22
+ '@Common.IsFiscalQuarter': replace('@Semantics.quarter'),
23
+ '@Common.IsFiscalYearQuarter': replace('@Semantics.yearQuarter'),
24
+ '@Common.IsFiscalWeek': replace('@Semantics.week'),
25
+ '@Common.IsFiscalYearWeek': replace('@Semantics.yearWeek'),
26
+ '@Common.IsDayOfFiscalYear': replace('@Semantics.dayOfYear'),
27
+ '@Measures.ISOCurrency': (csn, artifact, element, oldAnno) => {
28
+ const { targetElement } = getAnnoRefTarget(csn, artifact, element[oldAnno]);
29
+ if (refPointsToThisArtifact(csn, artifact, element, oldAnno)) {
30
+ replace('@Semantics.amount.currencyCode')(csn, artifact, element, oldAnno);
31
+ if (targetElement && targetElement['@Semantics.currencyCode'] === undefined)
32
+ targetElement['@Semantics.currencyCode'] = true;
33
+ }
34
+ },
35
+ '@Measures.Unit': (csn, artifact, element, oldAnno) => {
36
+ const { targetElement } = getAnnoRefTarget(csn, artifact, element[oldAnno]);
37
+ if (refPointsToThisArtifact(csn, artifact, element, oldAnno)) {
38
+ replace('@Semantics.quantity.unitOfMeasure')(csn, artifact, element, oldAnno);
39
+ if (targetElement && targetElement['@Semantics.unitOfMeasure'] === undefined)
40
+ targetElement['@Semantics.unitOfMeasure'] = true;
41
+ }
42
+ },
43
+ '@UI.IsImageURL': replace('@Semantics.imageUrl'),
44
+ '@Common.ValueList.CollectionPath': (csn, artifact, element) => {
45
+ if (!element.target && element['@Consumption.valueHelpDefinition'] === undefined) {
46
+ if (element['@Common.ValueList.Parameters'] && Array.isArray(element['@Common.ValueList.Parameters'])) {
47
+ const InOutParameters = element['@Common.ValueList.Parameters'].filter(param => param.$Type === 'Common.ValueListParameterInOut');
48
+
49
+ if (InOutParameters.length === 1) {
50
+ element['@Consumption.valueHelpDefinition'] = [ {
51
+ name: element['@Common.ValueList.CollectionPath'],
52
+ } ];
53
+
54
+ delete element['@Common.ValueList.CollectionPath'];
55
+ delete element['@Common.ValueList.Label'];
56
+
57
+ element['@Consumption.valueHelpDefinition'][0].element = element['@Common.ValueList.Parameters'][0].ValueListProperty;
58
+ delete element['@Common.ValueList.Parameters'];
59
+ }
60
+ }
61
+ }
62
+ },
63
+ '@Common.TextFor': replace('@Semantics.text', true),
64
+ '@Common.IsLanguageIdentifier': replaceIf('@Semantics.language', true, (csn, artifact, element, anno) => !!element[anno]),
65
+ // We need to set two different annos here, depending on the value -> need a custom replacer
66
+ '@Common.Text': (csn, artifact, element, oldAnno) => {
67
+ const { targetArtifact, targetElement } = getAnnoRefTarget(csn, artifact, element[oldAnno]);
68
+ if (targetArtifact === artifact && !element['@ObjectModel.text.element'] && !targetElement['@Semantics.text']) {
69
+ element['@ObjectModel.text.element'] = element[oldAnno];
70
+ if (targetElement['@Semantics.text'] === undefined)
71
+ targetElement['@Semantics.text'] = true;
72
+ delete element['@Common.Text'];
73
+ }
74
+ else if (targetArtifact && targetElement && !element['@ObjectModel.text.association'] && !targetElement['@Semantics.text']) {
75
+ element['@ObjectModel.text.association'] = element[oldAnno];
76
+ if (targetElement['@Semantics.text'] === undefined)
77
+ targetElement['@Semantics.text'] = true;
78
+ delete element['@Common.Text'];
79
+ }
80
+ },
81
+ };
82
+
83
+ /**
84
+ *
85
+ * @param {CSN.Model} csn
86
+ * @param {CSN.Artifact} artifact
87
+ * @param {CSN.Element} element
88
+ * @param {object} anno
89
+ * @returns {boolean}
90
+ */
91
+ function refPointsToThisArtifact( csn, artifact, element, anno ) {
92
+ const { targetArtifact } = getAnnoRefTarget(csn, artifact, element[anno]);
93
+ return targetArtifact && targetArtifact === artifact;
94
+ }
95
+
96
+ /**
97
+ * Walk the possible annotation ref and return the artifact and element it points to
98
+ *
99
+ * @param {CSN.Model} csn
100
+ * @param {CSN.Artifact} startArtifact
101
+ * @param {object} annoValue
102
+ * @returns {object}
103
+ */
104
+ function getAnnoRefTarget( csn, startArtifact, annoValue ) {
105
+ if (!annoValue || !annoValue['='])
106
+ return { targetArtifact: undefined, targetElement: undefined };
107
+
108
+ const steps = annoValue['='].split('.');
109
+ let base = startArtifact;
110
+ let element;
111
+ for (const step of steps) {
112
+ if (!base.elements)
113
+ return { targetArtifact: undefined, targetElement: undefined };
114
+ element = base.elements[step];
115
+ if (!element)
116
+ return { targetArtifact: undefined, targetElement: undefined };
117
+ if (element.target)
118
+ base = csn.definitions[element.target];
119
+ }
120
+
121
+ return { targetArtifact: base, targetElement: element };
122
+ }
123
+
124
+ /**
125
+ * Get the function to replace oldAnno with newAnno on carrier.
126
+ *
127
+ * - If available, use "replacement" as value.
128
+ * - Only do replacement if "condition" returns true
129
+ * - Possibly set additional annotations via "additional"
130
+ * @param {string} newAnno
131
+ * @param {any} replacement
132
+ * @param {Function} [condition]
133
+ * @param {Function} [additional]
134
+ * @returns {Function}
135
+ */
136
+ function replace( newAnno, replacement, condition = () => true, additional = () => true ) {
137
+ return function replaceAnnotationPrefix(csn, artifact, carrier, oldAnno) {
138
+ if (carrier[newAnno] === undefined && condition(csn, artifact, carrier, oldAnno, newAnno)) {
139
+ carrier[newAnno] = replacement || carrier[oldAnno];
140
+ additional(carrier, oldAnno, newAnno);
141
+ delete carrier[oldAnno];
142
+ }
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Get the function to replace oldAnno with newAnno on carrier.
148
+ *
149
+ * - If available, use "replacement" as value.
150
+ * - Only do replacement if "condition" returns true
151
+ *
152
+ * @param {string} newAnno
153
+ * @param {any} replacement
154
+ * @param {Function} condition
155
+ * @returns {Function}
156
+ */
157
+ function replaceIf( newAnno, replacement, condition ) {
158
+ return replace( newAnno, replacement, condition );
159
+ }
160
+
161
+ /**
162
+ *
163
+ * @param {CSN.Model} csn
164
+ * @returns {object} Transfomer object for applyTransformations
165
+ */
166
+ function remapODataAnnotations( csn ) {
167
+ /**
168
+ *
169
+ * @param {CSN.Artifact} artifact
170
+ * @param {CSN.Element} element Element to process
171
+ */
172
+ function remapAnnotationsOnElement( artifact, element ) {
173
+ if (element.elements && !element.$ignore) // We expect to only be called on flattened CSN - error if we encounter .elements!
174
+ throw new CompilerAssertion(`Expected a flat model. Found element with subelements: ${JSON.stringify(element)}`);
175
+ for (const prop in element) {
176
+ if (directMappings[prop])
177
+ directMappings[prop](csn, artifact, element, prop);
178
+ }
179
+ }
180
+
181
+ return {
182
+ elements: (parent, prop, elements, path) => {
183
+ const artifact = csn.definitions[path[1]];
184
+ if (artifact?.kind === 'entity') {
185
+ for (const elementName in elements)
186
+ remapAnnotationsOnElement(artifact, elements[elementName]);
187
+ }
188
+ },
189
+ };
190
+ }
191
+
192
+ module.exports = {
193
+ remapODataAnnotations,
194
+ };
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const { isBetaEnabled } = require('../../base/model');
4
- const { CompilerAssertion } = require('../../base/error');
5
- const { getUtils, isAspect } = require('../../model/csnUtils');
3
+ const {
4
+ getUtils, isAspect, mergeTransformers, applyTransformations,
5
+ } = require('../../model/csnUtils');
6
6
  const transformUtils = require('../transformUtils');
7
7
  const flattening = require('../db/flattening');
8
8
  const types = require('./types');
@@ -14,6 +14,7 @@ const associations = require('./associations');
14
14
  const generateDrafts = require('../draft/db');
15
15
  const handleExists = require('../db/transformExists');
16
16
  const misc = require('./misc');
17
+ const annotations = require('./annotations');
17
18
  const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities } = require('../db/rewriteCalculatedElements');
18
19
  const { cloneFullCsn } = require('../../model/cloneCsn');
19
20
 
@@ -30,9 +31,6 @@ const { cloneFullCsn } = require('../../model/cloneCsn');
30
31
  * @returns {CSN.Model}
31
32
  */
32
33
  function effectiveCsn( model, options, messageFunctions ) {
33
- if (!isBetaEnabled(options, 'effectiveCsn'))
34
- throw new CompilerAssertion('effective CSN is only supported with beta flag `effectiveCsn`!');
35
-
36
34
  const csn = cloneFullCsn(model, options);
37
35
  delete csn.vocabularies; // must not be set for effective CSN
38
36
  messageFunctions.setModel(csn);
@@ -80,8 +78,8 @@ function effectiveCsn( model, options, messageFunctions ) {
80
78
  associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
81
79
  associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
82
80
  generateDrafts(csn, options, '_', messageFunctions);
83
- misc.attachPersistenceName(csn, options, csnUtils);
84
- misc.removeDefinitionsAndProperties(csn, options);
81
+ const transformers = mergeTransformers([ misc.attachPersistenceName(csn, options, csnUtils), options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {}, misc.removeDefinitionsAndProperties(csn, options) ], null);
82
+ applyTransformations(csn, transformers, [], { skipIgnore: false });
85
83
 
86
84
  if (!options.resolveProjections)
87
85
  redoProjections.forEach(fn => fn());