@sap/cds-compiler 4.6.2 → 4.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/cds_update_identifiers.js +6 -2
  3. package/bin/cdsc.js +1 -1
  4. package/doc/CHANGELOG_ARCHIVE.md +9 -9
  5. package/doc/CHANGELOG_BETA.md +6 -0
  6. package/lib/api/main.js +56 -9
  7. package/lib/api/options.js +6 -3
  8. package/lib/api/validate.js +20 -29
  9. package/lib/base/message-registry.js +27 -3
  10. package/lib/base/messages.js +8 -3
  11. package/lib/base/model.js +2 -0
  12. package/lib/checks/dbFeatureFlags.js +28 -0
  13. package/lib/checks/elements.js +81 -13
  14. package/lib/checks/enricher.js +3 -2
  15. package/lib/checks/validator.js +38 -4
  16. package/lib/compiler/assert-consistency.js +4 -4
  17. package/lib/compiler/checks.js +5 -4
  18. package/lib/compiler/define.js +2 -2
  19. package/lib/compiler/generate.js +2 -1
  20. package/lib/compiler/propagator.js +3 -11
  21. package/lib/compiler/shared.js +2 -1
  22. package/lib/compiler/tweak-assocs.js +43 -24
  23. package/lib/edm/annotations/edmJson.js +3 -0
  24. package/lib/edm/annotations/genericTranslation.js +156 -106
  25. package/lib/edm/annotations/preprocessAnnotations.js +11 -14
  26. package/lib/edm/csn2edm.js +27 -24
  27. package/lib/edm/edm.js +8 -8
  28. package/lib/edm/edmPreprocessor.js +135 -37
  29. package/lib/edm/edmUtils.js +20 -7
  30. package/lib/gen/Dictionary.json +1 -0
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +9 -11
  33. package/lib/gen/languageParser.js +5942 -5446
  34. package/lib/json/to-csn.js +7 -114
  35. package/lib/language/genericAntlrParser.js +106 -48
  36. package/lib/model/cloneCsn.js +203 -0
  37. package/lib/model/csnRefs.js +11 -3
  38. package/lib/model/csnUtils.js +42 -85
  39. package/lib/optionProcessor.js +2 -2
  40. package/lib/render/manageConstraints.js +1 -1
  41. package/lib/render/toCdl.js +133 -88
  42. package/lib/render/toHdbcds.js +1 -5
  43. package/lib/render/toSql.js +7 -9
  44. package/lib/render/utils/common.js +9 -16
  45. package/lib/transform/addTenantFields.js +277 -102
  46. package/lib/transform/db/applyTransformations.js +14 -9
  47. package/lib/transform/db/backlinks.js +2 -1
  48. package/lib/transform/db/constraints.js +60 -82
  49. package/lib/transform/db/expansion.js +6 -6
  50. package/lib/transform/db/featureFlags.js +5 -0
  51. package/lib/transform/db/flattening.js +4 -4
  52. package/lib/transform/db/killAnnotations.js +1 -0
  53. package/lib/transform/db/rewriteCalculatedElements.js +2 -2
  54. package/lib/transform/db/transformExists.js +12 -0
  55. package/lib/transform/db/views.js +5 -2
  56. package/lib/transform/draft/odata.js +7 -6
  57. package/lib/transform/effective/associations.js +2 -1
  58. package/lib/transform/effective/main.js +3 -2
  59. package/lib/transform/effective/types.js +6 -3
  60. package/lib/transform/forOdata.js +39 -24
  61. package/lib/transform/forRelationalDB.js +34 -27
  62. package/lib/transform/localized.js +29 -9
  63. package/lib/transform/odata/flattening.js +419 -0
  64. package/lib/transform/odata/toFinalBaseType.js +95 -15
  65. package/lib/transform/odata/typesExposure.js +9 -7
  66. package/lib/transform/transformUtils.js +7 -6
  67. package/lib/transform/translateAssocsToJoins.js +3 -3
  68. package/lib/utils/objectUtils.js +14 -0
  69. package/package.json +1 -1
@@ -2,8 +2,7 @@
2
2
 
3
3
  const { isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
4
4
  const transformUtils = require('./transformUtils');
5
- const { cloneCsnNonDict,
6
- forEachDefinition,
5
+ const { forEachDefinition,
7
6
  forEachMemberRecursively,
8
7
  applyTransformationsOnNonDictionary,
9
8
  getArtifactDatabaseNameOf,
@@ -18,13 +17,15 @@ const { isArtifactInSomeService, isLocalizedArtifactInService } = require('./oda
18
17
  const expandToFinalBaseType = require('./odata/toFinalBaseType');
19
18
  const { timetrace } = require('../utils/timetrace');
20
19
  const enrichUniversalCsn = require('./universalCsn/universalCsnEnricher');
21
- const flattening = require('./db/flattening');
20
+ const flattening = require('./odata/flattening');
22
21
  const associations = require('./db/associations')
23
22
  const expansion = require('./db/expansion');
24
23
  const generateDrafts = require('./draft/odata');
25
24
 
26
25
  const { addTenantFields } = require('./addTenantFields');
27
26
  const { addLocalizationViews } = require('./localized');
27
+ const { cloneFullCsn } = require('../model/cloneCsn');
28
+ const { csnRefs } = require('../model/csnRefs');
28
29
 
29
30
  // Transformation for ODATA. Expects a CSN 'inputModel', processes it for ODATA.
30
31
  // The result should be suitable for consumption by EDMX processors (annotations and metadata)
@@ -72,7 +73,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
72
73
  timetrace.start('OData transformation');
73
74
 
74
75
  // copy the model as we don't want to change the input model
75
- let csn = cloneCsnNonDict(inputModel, options);
76
+ let csn = cloneFullCsn(inputModel, options);
76
77
  messageFunctions.setModel(csn);
77
78
 
78
79
  const { message, error, warning, info, throwWithAnyError } = messageFunctions;
@@ -118,7 +119,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
118
119
  if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
119
120
  enrichUniversalCsn(csn, options);
120
121
 
121
- if (options.tenantAsColumn)
122
+ if (options.tenantDiscriminator)
122
123
  addTenantFields(csn, options);
123
124
 
124
125
  const keepLocalizedViews = isDeprecatedEnabled(options, '_createLocalizedViews');
@@ -133,6 +134,9 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
133
134
  // replace all type refs to builtin types with direct type
134
135
  transformUtils.rewriteBuiltinTypeRef(csn);
135
136
 
137
+ // Rewrite paths in annotations only if beta modes are set
138
+
139
+ options.enrichAnnotations = true;
136
140
  const cleanup = validate.forOdata(csn, {
137
141
  message, error, warning, info, inspectRef, effectiveType, getFinalTypeInfo, artifactRef,
138
142
  options, csnUtils, services, isAspect, isExternalServiceMember, recurseElements,
@@ -165,11 +169,11 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
165
169
  // If in the future 'other' APIs that might support type refs are imported, these refs must be
166
170
  // resolved here, as this is the OData transformation and sets the foundation for subsequent EDM
167
171
  // rendering which may has to publish external definitions
168
- expandToFinalBaseType(csn, transformers, csnUtils, services, options);
172
+ expandToFinalBaseType(csn, transformers, csnUtils, services, options, error);
169
173
 
170
174
  // - Generate artificial draft fields on a structured CSN if requested, flattening and struct
171
175
  // expansion do their magic including foreign key generation and annotation propagation.
172
- generateDrafts(csn, options, services);
176
+ generateDrafts(csn, options, services, messageFunctions);
173
177
 
174
178
  // Check if structured elements and managed associations are compared in an expression
175
179
  // and expand these structured elements. This tuple expansion allows all other
@@ -180,21 +184,30 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
180
184
  if (!structuredOData) {
181
185
  expansion.expandStructureReferences(csn, options, '_', { error, info, throwWithAnyError }, csnUtils, { skipArtifact: isExternalServiceMember });
182
186
  const resolved = new WeakMap();
183
- // No refs with struct-steps exist anymore
184
-
185
- flattening.flattenAllStructStepsInRefs(csn, options, messageFunctions, resolved, '_', { skipArtifact: isExternalServiceMember });
186
- // No type references exist anymore
187
- // Needs to happen exactly between flattenAllStructStepsInRefs and flattenElements to keep model resolvable.
188
- // OData doesn't resolve type chains after the first 'items'
189
- flattening.resolveTypeReferences(csn, options, messageFunctions, resolved, '_',
190
- { skip: [ 'action', 'aspect', 'event', 'function', 'type'],
191
- skipArtifact: isExternalServiceMember, skipStandard: { items: true } });
192
- // No structured elements exists anymore
193
- flattening.flattenElements(csn, options, messageFunctions, '_',
194
- { skip: ['action', 'aspect', 'event', 'function', 'type'],
187
+
188
+ const { inspectRef, effectiveType } = csnRefs(csn);
189
+ const { adaptRefs, transformer: refFlattener } = flattening.getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, options, resolved, '_');
190
+
191
+ flattening.allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternalServiceMember, error, csnUtils, options);
192
+
193
+ flattening.flattenAllStructStepsInRefs(csn, refFlattener, adaptRefs,
194
+ { //skip: ['action', 'aspect', 'event', 'function', 'type'],
195
195
  skipArtifact: isExternalServiceMember,
196
- skipStandard: { items: true }, // don't drill further into .items
197
- skipDict: { actions: true } }); // don't drill further into .actions -> bound actions, action-artifacts are handled by skip
196
+ });
197
+
198
+ // replace structured with flat dictionaries that contain
199
+ // rewritten path expressions
200
+ forEachDefinition(csn, (def) => {
201
+ ['elements', 'params'].forEach(dictName => {
202
+ if(def[`$${dictName}`])
203
+ def[dictName] = def[`$${dictName}`];
204
+ })
205
+ if(def.$flatAnnotations) {
206
+ Object.entries(def.$flatAnnotations).forEach(([an, av]) => {
207
+ def[an] = av;
208
+ })
209
+ }
210
+ });
198
211
  }
199
212
 
200
213
  // TODO: add the generated foreign keys to the columns when we are in a view
@@ -249,7 +262,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
249
262
  !(propertyName === 'enum' || propertyName === 'returns') &&
250
263
  (!member.virtual || def.query)) {
251
264
  // If we have a 'preserved dotted name' (i.e. we are a result of flattening), use that for the @cds.persistence.name annotation
252
- member['@cds.persistence.name'] = getElementDatabaseNameOf(member._flatElementNameWithDots || memberName, options.sqlMapping, 'hana'); // hana to allow "hdbcds"
265
+ member['@cds.persistence.name'] = getElementDatabaseNameOf(member.$defPath?.slice(1).join('.') || memberName, options.sqlMapping, 'hana'); // hana to allow "hdbcds"
253
266
  }
254
267
 
255
268
  // Mark fields with @odata.on.insert/update as @Core.Computed
@@ -282,13 +295,15 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
282
295
  forEachGeneric(csn, 'vocabularies', renameShorthandAnnotations);
283
296
  }
284
297
 
298
+ cleanup();
285
299
  // Throw exception in case of errors
286
300
  throwWithAnyError();
287
- cleanup();
288
- if (options.testMode) csn = cloneCsnNonDict(csn, options); // sort, keep hidden properties
289
301
  timetrace.stop('OData transformation');
290
302
  return csn;
291
303
 
304
+ //--------------------------------------------------------------------
305
+ // HELPER SECTION STARTS HERE
306
+
292
307
  // Mark elements that are annotated with @odata.on.insert/update with the annotation @Core.Computed.
293
308
  function annotateCoreComputed(node) {
294
309
  // If @Core.Computed is explicitly set, don't overwrite it!
@@ -1,8 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { setProp, isBetaEnabled } = require('../base/model');
4
- const { cloneCsnNonDict,
5
- forEachMemberRecursively, forAllQueries, applyTransformationsOnNonDictionary,
4
+ const { forEachMemberRecursively, forAllQueries, applyTransformationsOnNonDictionary,
6
5
  getArtifactDatabaseNameOf, getElementDatabaseNameOf, isBuiltinType, applyTransformations,
7
6
  isAspect, walkCsnPath, isPersistedOnDatabase
8
7
  } = require('../model/csnUtils');
@@ -32,6 +31,8 @@ const temporal = require('./db/temporal');
32
31
  const associations = require('./db/associations');
33
32
  const backlinks = require('./db/backlinks');
34
33
  const { getDefaultTypeLengths } = require('../render/utils/common');
34
+ const { featureFlags } = require('./db/featureFlags');
35
+ const { cloneCsnNonDict, cloneFullCsn } = require('../model/cloneCsn');
35
36
 
36
37
  // By default: Do not process non-entities/views
37
38
  function forEachDefinition(csn, cb) {
@@ -108,10 +109,10 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
108
109
 
109
110
  timetrace.start('Clone CSN');
110
111
  /** @type {CSN.Model} */
111
- csn = cloneCsnNonDict(csn, options);
112
+ csn = cloneFullCsn(csn, options);
112
113
  timetrace.stop('Clone CSN');
113
114
 
114
- if (options.tenantAsColumn)
115
+ if (options.tenantDiscriminator)
115
116
  addTenantFields(csn, options);
116
117
 
117
118
  checkCSNVersion(csn, options);
@@ -161,7 +162,8 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
161
162
  // exit if validators found errors
162
163
  throwWithAnyError();
163
164
 
164
- rewriteCalculatedElementsInViews(csn, options, csnUtils, pathDelimiter, messageFunctions);
165
+ if(csn.meta?.[featureFlags]?.$calculatedElements)
166
+ rewriteCalculatedElementsInViews(csn, options, csnUtils, pathDelimiter, messageFunctions);
165
167
 
166
168
  // Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
167
169
  handleExists(csn, options, error, csnUtils.inspectRef, csnUtils.initDefinition, csnUtils.dropDefinitionCache);
@@ -197,7 +199,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
197
199
 
198
200
  bindCsnReferenceOnly();
199
201
 
200
-
201
202
  // TODO: Instead of 3 separate applyTransformations, we could have each of them just return the "listeners", merge them into
202
203
  // one big listener that then gets passed into one single applyTransformations. Each listener would then have to return an array of callbacks to call.
203
204
  // With that, we could still ensure the processing order (assuming we don't run into problems with scoping).
@@ -395,18 +396,20 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
395
396
  delete parent[prop];
396
397
  }
397
398
 
399
+ function killParent(parent, a, b, path){
400
+ if(path.length > 2) {
401
+ const tail = path[path.length-1];
402
+ const parentPath = path.slice(0, -1)
403
+ const parentParent = walkCsnPath(csn, parentPath);
404
+ delete parentParent[tail];
405
+ } else {
406
+ delete parent.$ignore;
407
+ }
408
+ }
409
+
398
410
  const killers = {
399
411
  // Used to ignore actions etc from processing and remove associations/elements
400
- '$ignore': function (parent, a, b, path){
401
- if(path.length > 2) {
402
- const tail = path[path.length-1];
403
- const parentPath = path.slice(0, -1)
404
- const parentParent = walkCsnPath(csn, parentPath);
405
- delete parentParent[tail];
406
- } else {
407
- delete parent.$ignore;
408
- }
409
- },
412
+ '$ignore': killParent,
410
413
  // Still used in flattenStructuredElements - in db/flattening.js
411
414
  '_flatElementNameWithDots': killProp,
412
415
  // Set when setting default string/binary length - used in copyTypeProperties and fixBorkedElementsOfLocalized
@@ -442,13 +445,16 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
442
445
  }
443
446
  }
444
447
 
445
- applyTransformations(csn, killers, [], { skipIgnore: false});
448
+ if(options.sqlDialect === 'hana' && options.withHanaAssociations === false && doA2J) {
449
+ killers.target = killParent;
450
+ }
451
+
452
+ applyTransformations(csn, killers, [], { skipIgnore: false });
446
453
 
447
454
  redoProjections.forEach(fn => fn());
448
455
 
449
456
  timetrace.stop('Transform CSN');
450
457
  timetrace.stop('HANA transformation');
451
-
452
458
  return csn;
453
459
 
454
460
  /* ----------------------------------- Functions start here -----------------------------------------------*/
@@ -548,15 +554,16 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
548
554
  // First pass: Set alias name for SELECTs without table alias. Required for setting proper table aliases
549
555
  // for HDBCDS in naming mode HDBCDS. We use the same schema as the core-compiler, so duplicates should
550
556
  // have already been reported.
551
- let selectDepth = 0;
552
- traverseQuery(artifact.query, null, null, (query, fromSelect) => {
553
- if (!query.ref && !query.as && fromSelect) {
554
- // Use +1; for UNION, it's the next select, for SELECT, it's increased later.
555
- query.as = `$_select_${selectDepth + 1}__`;
556
- }
557
- if (query.SELECT)
558
- ++selectDepth;
559
- });
557
+ if(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
558
+ let selectDepth = 0;
559
+ traverseQuery(artifact.query, null, null, (query, fromSelect) => {
560
+ if (!query.ref && !query.as && fromSelect) {
561
+ // Use +1; for UNION, it's the next select, for SELECT, it's increased later.
562
+ query.as = `$_select_${selectDepth + 1}__`;
563
+ }
564
+ if (query.SELECT) ++selectDepth;
565
+ });
566
+ }
560
567
 
561
568
  const process = (parent, prop, query, path) => {
562
569
  transformEntityOrViewPass2(parent, artifact, artifactName, path.concat(prop))
@@ -5,15 +5,18 @@ const { setProp, isDeprecatedEnabled} = require('../base/model');
5
5
  const { forEachKey } = require('../utils/objectUtils');
6
6
  const { cleanSymbols } = require('../base/cleanSymbols.js');
7
7
  const {
8
- cloneCsnDictionary,
9
- cloneCsnNonDict,
10
8
  applyAnnotationsFromExtensions,
11
9
  forEachDefinition,
12
10
  forEachGeneric,
13
11
  forAllQueries,
14
- sortCsnDefinitionsForTests,
15
12
  } = require('../model/csnUtils');
16
- const {CompilerAssertion} = require('../base/error');
13
+ const { CompilerAssertion } = require('../base/error');
14
+ const {
15
+ cloneCsnDict,
16
+ cloneCsnNonDict,
17
+ sortCsnDefinitionsForTests,
18
+ sortCsn
19
+ } = require('../model/cloneCsn');
17
20
 
18
21
  /**
19
22
  * Indicator that a definition is localized and has a convenience view.
@@ -104,7 +107,7 @@ const annoPersistenceSkip = '@cds.persistence.skip';
104
107
  * If true, do not emit a warning for annotations on unknown `localized.*` views.
105
108
  */
106
109
  function _addLocalizationViews(csn, options, config) {
107
- const messageFunctions = makeMessageFunction(csn, options);
110
+ const messageFunctions = makeMessageFunction(csn, options, 'localized');
108
111
  if (checkExistingLocalizationViews(csn, options, messageFunctions))
109
112
  return csn;
110
113
 
@@ -117,7 +120,7 @@ function _addLocalizationViews(csn, options, config) {
117
120
  createTransitiveConvenienceViews(); // 2 + 3
118
121
  cleanDefinitionSymbols();
119
122
  applyAnnotationsForLocalizedViews();
120
- sortCsnDefinitionsForTests(csn, options);
123
+ sortLocalizedForTests(csn, options);
121
124
  messageFunctions.throwWithError();
122
125
  return csn;
123
126
 
@@ -201,7 +204,7 @@ function _addLocalizationViews(csn, options, config) {
201
204
  columns,
202
205
  },
203
206
  },
204
- elements: cloneCsnDictionary(entity.elements, options),
207
+ elements: cloneCsnDict(entity.elements, options),
205
208
  [_isViewForEntity]: true,
206
209
  };
207
210
  copyLocation(convenienceView, entity);
@@ -280,7 +283,7 @@ function _addLocalizationViews(csn, options, config) {
280
283
  else if (view.projection)
281
284
  convenienceView.projection = cloneCsnNonDict(view.projection, options);
282
285
 
283
- convenienceView.elements = cloneCsnDictionary(view.elements, options);
286
+ convenienceView.elements = cloneCsnDict(view.elements, options);
284
287
  convenienceView[_isViewForView] = true;
285
288
  copyLocation(convenienceView, view);
286
289
 
@@ -289,7 +292,7 @@ function _addLocalizationViews(csn, options, config) {
289
292
  });
290
293
 
291
294
  if (view.params)
292
- convenienceView.params = cloneCsnDictionary(view.params, options);
295
+ convenienceView.params = cloneCsnDict(view.params, options);
293
296
 
294
297
  return convenienceView;
295
298
  }
@@ -842,6 +845,23 @@ function hasLocalizedConvenienceView(csn, artifactName) {
842
845
  return !isInLocalizedNamespace(artifactName) && !!csn.definitions[`localized.${ artifactName }`];
843
846
  }
844
847
 
848
+ /**
849
+ * For tests (testMode), sort the generated localized definitions, i.e. their props,
850
+ * but also sort 'csn.definitions' if requested.
851
+ *
852
+ * @param {CSN.Model} csn
853
+ * @param {CSN.Options} options
854
+ */
855
+ function sortLocalizedForTests(csn, options) {
856
+ if (options.testMode) {
857
+ for (const defName in csn.definitions) {
858
+ if (defName.startsWith('localized.'))
859
+ csn.definitions[defName] = sortCsn(csn.definitions[defName], options);
860
+ }
861
+ }
862
+ sortCsnDefinitionsForTests(csn, options);
863
+ }
864
+
845
865
  module.exports = {
846
866
  addLocalizationViews,
847
867
  addLocalizationViewsWithJoins,