@sap/cds-compiler 4.8.0 → 4.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 (92) hide show
  1. package/CHANGELOG.md +29 -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 +14 -1
  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 +32 -19
  12. package/lib/base/messages.js +50 -19
  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 +8 -2
  20. package/lib/compiler/checks.js +44 -18
  21. package/lib/compiler/define.js +34 -22
  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/propagator.js +21 -18
  26. package/lib/compiler/resolve.js +44 -28
  27. package/lib/compiler/shared.js +60 -20
  28. package/lib/compiler/tweak-assocs.js +13 -88
  29. package/lib/compiler/xpr-rewrite.js +689 -0
  30. package/lib/edm/annotations/genericTranslation.js +80 -60
  31. package/lib/edm/edm.js +4 -4
  32. package/lib/edm/edmInboundChecks.js +33 -0
  33. package/lib/edm/edmPreprocessor.js +9 -6
  34. package/lib/gen/Dictionary.json +129 -14
  35. package/lib/gen/language.checksum +1 -1
  36. package/lib/gen/language.interp +1 -1
  37. package/lib/gen/languageParser.js +1523 -1518
  38. package/lib/json/from-csn.js +13 -4
  39. package/lib/json/to-csn.js +10 -11
  40. package/lib/language/genericAntlrParser.js +14 -6
  41. package/lib/main.d.ts +67 -14
  42. package/lib/main.js +1 -0
  43. package/lib/model/cloneCsn.js +6 -3
  44. package/lib/model/csnRefs.js +12 -7
  45. package/lib/model/csnUtils.js +13 -7
  46. package/lib/model/enrichCsn.js +3 -1
  47. package/lib/model/revealInternalProperties.js +2 -1
  48. package/lib/model/sortViews.js +14 -6
  49. package/lib/modelCompare/compare.js +33 -34
  50. package/lib/optionProcessor.js +27 -2
  51. package/lib/render/DuplicateChecker.js +6 -6
  52. package/lib/render/manageConstraints.js +1 -0
  53. package/lib/render/toCdl.js +3 -1
  54. package/lib/transform/db/applyTransformations.js +33 -0
  55. package/lib/transform/db/constraints.js +1 -1
  56. package/lib/transform/db/expansion.js +8 -3
  57. package/lib/transform/db/groupByOrderBy.js +2 -2
  58. package/lib/transform/db/temporal.js +6 -3
  59. package/lib/transform/db/transformExists.js +2 -2
  60. package/lib/transform/effective/annotations.js +194 -0
  61. package/lib/transform/effective/main.js +6 -8
  62. package/lib/transform/effective/misc.js +31 -10
  63. package/lib/transform/forOdata.js +23 -7
  64. package/lib/transform/forRelationalDB.js +1 -1
  65. package/lib/transform/localized.js +7 -6
  66. package/lib/transform/odata/flattening.js +189 -106
  67. package/lib/transform/odata/toFinalBaseType.js +1 -1
  68. package/lib/transform/odata/typesExposure.js +15 -12
  69. package/lib/transform/parseExpr.js +4 -4
  70. package/lib/transform/transformUtils.js +40 -37
  71. package/lib/transform/translateAssocsToJoins.js +47 -47
  72. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -16
  73. package/package.json +1 -1
  74. package/share/messages/anno-missing-rewrite.md +45 -0
  75. package/share/messages/message-explanations.json +1 -0
  76. package/bin/.eslintrc.json +0 -17
  77. package/lib/api/.eslintrc.json +0 -37
  78. package/lib/checks/.eslintrc.json +0 -31
  79. package/lib/compiler/.eslintrc.json +0 -8
  80. package/lib/edm/.eslintrc.json +0 -46
  81. package/lib/inspect/.eslintrc.json +0 -4
  82. package/lib/json/.eslintrc.json +0 -4
  83. package/lib/language/.eslintrc.json +0 -4
  84. package/lib/model/.eslintrc.json +0 -13
  85. package/lib/modelCompare/utils/.eslintrc.json +0 -22
  86. package/lib/render/.eslintrc.json +0 -22
  87. package/lib/transform/.eslintrc.json +0 -13
  88. package/lib/transform/db/.eslintrc.json +0 -41
  89. package/lib/transform/draft/.eslintrc.json +0 -4
  90. package/lib/transform/effective/.eslintrc.json +0 -4
  91. package/lib/transform/universalCsn/.eslintrc.json +0 -37
  92. package/lib/utils/.eslintrc.json +0 -7
@@ -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) {
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
 
@@ -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
 
@@ -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());
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- forEachDefinition, forEachMemberRecursively, getArtifactDatabaseNameOf, getElementDatabaseNameOf, applyTransformations,
4
+ getArtifactDatabaseNameOf, getElementDatabaseNameOf,
5
5
  } = require('../../model/csnUtils');
6
6
  /**
7
7
  * Attach @cds.persistence.name to all artifacts and "things".
@@ -9,17 +9,34 @@ const {
9
9
  * @param {CSN.Model} csn
10
10
  * @param {CSN.Options} options
11
11
  * @param {object} csnUtils
12
+ * @returns {object}
12
13
  */
13
14
  function attachPersistenceName( csn, options, csnUtils ) {
14
15
  const { addStringAnnotationTo } = csnUtils;
15
16
 
16
- forEachDefinition(csn, (artifact, artifactName) => {
17
- if (artifact.kind === 'entity') {
18
- addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
19
-
20
- forEachMemberRecursively(artifact, (member, memberName) => addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), member), [ 'definitions', artifactName ]);
17
+ /**
18
+ *
19
+ * @param {object} parent
20
+ * @param {string} prop
21
+ * @param {object} dict
22
+ * @param {CSN.Path} path
23
+ */
24
+ function addToEachMember( parent, prop, dict, path ) {
25
+ const artifact = csn.definitions[path[1]];
26
+ if (artifact?.kind === 'entity') {
27
+ for (const memberName in dict)
28
+ addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), dict[memberName]);
21
29
  }
22
- });
30
+ }
31
+
32
+ return {
33
+ kind: (parent, prop, kind, path) => {
34
+ if (kind === 'entity')
35
+ addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(path[1], options.sqlMapping, csn, options.sqlDialect), parent);
36
+ },
37
+ elements: addToEachMember,
38
+ params: addToEachMember,
39
+ };
23
40
  }
24
41
 
25
42
  /**
@@ -42,10 +59,11 @@ function killProp( parent, prop ) {
42
59
  * - localized
43
60
  * @param {CSN.Model} csn
44
61
  * @param {CSN.Options} options
62
+ * @returns {object}
45
63
  * @todo Callback-like architecture and merge with persistence name?
46
64
  */
47
65
  function _removeDefinitionsAndProperties( csn, options ) {
48
- const killers = {
66
+ const transformers = {
49
67
  $ignore: (a, b, c, path, parentParent) => {
50
68
  const tail = path[path.length - 1];
51
69
  delete parentParent[tail];
@@ -74,13 +92,16 @@ function _removeDefinitionsAndProperties( csn, options ) {
74
92
  // Set when we remove .key from temporal things, used in localized.js
75
93
  $key: killProp,
76
94
  includes: killProp,
77
- localized: killProp,
78
95
  enum: killProp,
79
96
  keys: killProp,
80
97
  excluding: killProp, // * is resolved, so has no effect anymore
98
+ targetAspect: killProp,
81
99
  };
82
100
 
83
- applyTransformations(csn, killers, [], { skipIgnore: false });
101
+ if (!options.keepLocalized)
102
+ transformers.localized = killProp;
103
+
104
+ return transformers;
84
105
  }
85
106
 
86
107
 
@@ -73,7 +73,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
73
73
  timetrace.start('OData transformation');
74
74
 
75
75
  // copy the model as we don't want to change the input model
76
- let csn = cloneFullCsn(inputModel, options);
76
+ const csn = cloneFullCsn(inputModel, options);
77
77
  messageFunctions.setModel(csn);
78
78
 
79
79
  const { message, error, warning, info, throwWithAnyError } = messageFunctions;
@@ -184,14 +184,19 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
184
184
  expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
185
185
 
186
186
  if (!structuredOData) {
187
- expansion.expandStructureReferences(csn, options, '_', { error, info, throwWithAnyError }, csnUtils, { skipArtifact: isExternalServiceMember });
187
+ expansion.expandStructureReferences(csn, options, '_',
188
+ { error, info, throwWithAnyError }, csnUtils,
189
+ { skipArtifact: isExternalServiceMember });
188
190
  const resolved = new WeakMap();
189
191
 
190
192
  const { inspectRef, effectiveType } = csnRefs(csn);
191
- const { adaptRefs, transformer: refFlattener } = flattening.getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, options, resolved, '_');
193
+ const { adaptRefs, transformer: refFlattener } =
194
+ flattening.getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, options, resolved, '_');
192
195
 
193
- flattening.allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternalServiceMember, error, csnUtils, options);
194
- flattening.flattenAllStructStepsInRefs(csn, refFlattener, adaptRefs, inspectRef, effectiveType, csnUtils, error, options,
196
+ flattening.allInOneFlattening(csn, refFlattener, adaptRefs,
197
+ inspectRef, isExternalServiceMember, error, csnUtils, options);
198
+ flattening.flattenAllStructStepsInRefs(csn, refFlattener, adaptRefs,
199
+ inspectRef, effectiveType, csnUtils, error, options,
195
200
  { //skip: ['action', 'aspect', 'event', 'function', 'type'],
196
201
  skipArtifact: isExternalServiceMember,
197
202
  });
@@ -208,12 +213,22 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
208
213
  def[an] = av;
209
214
  })
210
215
  }
216
+ if(def.actions) {
217
+ Object.values(def.actions).forEach((action) => {
218
+ if(action.$flatAnnotations) {
219
+ Object.entries(action.$flatAnnotations).forEach(([an, av]) => {
220
+ action[an] = av;
221
+ });
222
+ }
223
+ });
224
+ }
211
225
  });
212
226
  }
213
227
 
214
228
  // TODO: add the generated foreign keys to the columns when we are in a view
215
229
  // see db/views.js::addForeignKeysToColumns
216
- flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, messageFunctions, '_', !structuredOData, csnUtils,{ skipArtifact: isExternalServiceMember });
230
+ flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, messageFunctions, '_',
231
+ !structuredOData, csnUtils,{ skipArtifact: isExternalServiceMember });
217
232
 
218
233
  // Allow using managed associations as steps in on-conditions to access their fks
219
234
  // To be done after handleManagedAssociationsAndCreateForeignKeys,
@@ -252,7 +267,8 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
252
267
  // Annotate artifacts with their DB names if requested.
253
268
  // Skip artifacts that have no DB equivalent anyway
254
269
  if (options.sqlMapping && !(def.kind in skipPersNameKinds))
255
- def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.sqlMapping, csn, 'hana'); // hana to allow naming mode "hdbcds"
270
+ // hana to allow naming mode "hdbcds"
271
+ def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.sqlMapping, csn, 'hana');
256
272
 
257
273
  forEachMemberRecursively(def, (member, memberName, propertyName) => {
258
274
  // Annotate elements, foreign keys, parameters, etc. with their DB names if requested
@@ -183,7 +183,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
183
183
  forEachDefinition(csn, [
184
184
  // (001) Add a temporal where condition to views where applicable before assoc2join
185
185
  // assoc2join eventually rewrites the table aliases
186
- temporal.getViewDecorator(csn, messageFunctions, csnUtils),
186
+ temporal.getViewDecorator(csn, messageFunctions, csnUtils, options),
187
187
  // check unique constraints - further processing is done in rewriteUniqueConstraints
188
188
  assertUnique.prepare(csn, options, messageFunctions)
189
189
  ]);
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { makeMessageFunction } = require('../base/messages');
4
- const { setProp, isDeprecatedEnabled} = require('../base/model');
4
+ const { setProp, isDeprecatedEnabled, isBetaEnabled} = require('../base/model');
5
5
  const { forEachKey } = require('../utils/objectUtils');
6
6
  const { cleanSymbols } = require('../base/cleanSymbols.js');
7
7
  const {
@@ -747,7 +747,7 @@ function copyLocation(target, source) {
747
747
  }
748
748
 
749
749
  /**
750
- * Copy @cds.persistence.exists/skip annotations from the source to
750
+ * Copy @cds.persistence.skip annotations from the source to
751
751
  * the target. Ignores existing annotations on the _target_.
752
752
  *
753
753
  * @param {CSN.Artifact} target
@@ -755,14 +755,15 @@ function copyLocation(target, source) {
755
755
  * @param {CSN.Options} options
756
756
  */
757
757
  function copyPersistenceAnnotations(target, source, options) {
758
- const doNotCopyExists = isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
758
+ const copyExists = !isBetaEnabled(options, 'v5preview') &&
759
+ !isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
759
760
  forEachKey(source, anno => {
760
761
  // Note:
761
- // Because `.exists` is copied to the convenience view, it could
762
+ // v3/v4: Because `.exists` is copied to the convenience view, it could
762
763
  // lead to some localization views referencing non-existing ones.
763
764
  // But that is the contract: User says that it already exists!
764
- // In v2, `.exists` was never copied.
765
- if (anno === annoPersistenceSkip || (!doNotCopyExists && anno === '@cds.persistence.exists'))
765
+ // v2/>=v5, `.exists` is never copied.
766
+ if (anno === annoPersistenceSkip || (copyExists && anno === '@cds.persistence.exists'))
766
767
  target[anno] = source[anno];
767
768
  });
768
769
  }