@sap/cds-compiler 6.9.3 → 7.0.1

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 +76 -2
  2. package/bin/cdsc.js +4 -33
  3. package/doc/IncompatibleChanges_v7.md +639 -0
  4. package/lib/api/main.js +4 -56
  5. package/lib/api/options.js +5 -15
  6. package/lib/api/validate.js +1 -0
  7. package/lib/base/builtins.js +1 -2
  8. package/lib/base/csnRefs.js +2 -6
  9. package/lib/base/message-registry.js +82 -76
  10. package/lib/base/messages.js +8 -5
  11. package/lib/base/optionProcessor.js +2 -72
  12. package/lib/base/specialOptions.js +20 -17
  13. package/lib/checks/defaultValues.js +1 -39
  14. package/lib/checks/hasPersistedElements.js +19 -3
  15. package/lib/checks/parameters.js +0 -34
  16. package/lib/checks/selectItems.js +2 -38
  17. package/lib/checks/typeParameters.js +162 -0
  18. package/lib/checks/validator.js +5 -8
  19. package/lib/compiler/assert-consistency.js +19 -5
  20. package/lib/compiler/checks.js +47 -43
  21. package/lib/compiler/define.js +6 -6
  22. package/lib/compiler/extend.js +102 -111
  23. package/lib/compiler/generate.js +4 -8
  24. package/lib/compiler/populate.js +4 -7
  25. package/lib/compiler/propagator.js +9 -9
  26. package/lib/compiler/resolve.js +205 -7
  27. package/lib/compiler/shared.js +76 -82
  28. package/lib/compiler/tweak-assocs.js +102 -22
  29. package/lib/compiler/utils.js +57 -12
  30. package/lib/compiler/xpr-rewrite.js +2 -15
  31. package/lib/edm/annotations/edmJson.js +14 -10
  32. package/lib/edm/annotations/genericTranslation.js +3 -1
  33. package/lib/edm/annotations/preprocessAnnotations.js +9 -26
  34. package/lib/edm/csn2edm.js +27 -20
  35. package/lib/edm/edmUtils.js +25 -0
  36. package/lib/gen/CdlGrammar.checksum +1 -1
  37. package/lib/gen/CdlParser.js +2237 -2241
  38. package/lib/gen/Dictionary.json +17 -2
  39. package/lib/json/from-csn.js +67 -52
  40. package/lib/json/to-csn.js +28 -25
  41. package/lib/language/textUtils.js +0 -13
  42. package/lib/main.d.ts +22 -59
  43. package/lib/main.js +1 -1
  44. package/lib/model/csnUtils.js +9 -8
  45. package/lib/parsers/AstBuildingParser.js +45 -55
  46. package/lib/parsers/Lexer.js +2 -0
  47. package/lib/parsers/identifiers.js +0 -9
  48. package/lib/render/toCdl.js +41 -40
  49. package/lib/render/toSql.js +8 -1
  50. package/lib/render/utils/common.js +1 -1
  51. package/lib/render/utils/sql.js +2 -3
  52. package/lib/tool-lib/enrichCsn.js +1 -2
  53. package/lib/transform/db/applyTransformations.js +7 -5
  54. package/lib/transform/db/assertUnique.js +8 -51
  55. package/lib/transform/db/associations.js +1 -1
  56. package/lib/transform/db/cdsPersistence.js +1 -15
  57. package/lib/transform/db/expansion.js +9 -12
  58. package/lib/transform/db/flattening.js +1 -1
  59. package/lib/transform/db/groupByOrderBy.js +0 -16
  60. package/lib/transform/db/views.js +57 -161
  61. package/lib/transform/draft/db.js +2 -2
  62. package/lib/transform/forOdata.js +25 -14
  63. package/lib/transform/forRelationalDB.js +93 -301
  64. package/lib/transform/localized.js +33 -102
  65. package/lib/transform/odata/flattening.js +11 -2
  66. package/lib/transform/transformUtils.js +25 -3
  67. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -2
  68. package/package.json +2 -2
  69. package/lib/render/toHdbcds.js +0 -1810
@@ -29,31 +29,22 @@ const annoPersistenceSkip = '@cds.persistence.skip';
29
29
  /**
30
30
  * Create transitive localized convenience views.
31
31
  *
32
- * A convenience view is created if (a) the entity/view has a localized element[^1]
33
- * or (b) if it exposes an association leading to a localized-tagged target.
34
- * The second part (b) is only performed if option `fewerLocalizedViews` is
35
- * disabled.
32
+ * A convenience view is created if the entity/view has a localized element[^1].
36
33
  *
37
34
  * INTERNALS:
38
- * We have three kinds of localized convenience views:
35
+ * We have two kinds of localized convenience views:
39
36
  *
40
37
  * 1. "direct ones" using coalesce() for the table entities with localized
41
- * elements[^1]: as projection on the original and '.texts' entity (created in extend.js)
42
- * 2. for _table_ entities with associations to entities which have a localized
43
- * convenience: as projection on the original
44
- * 3. for _view_ entities with either localized elements[^1] or associations
45
- * to entities which have a localized convenience view:
38
+ * elements[^1]: as projection on the original and '.texts' entity
39
+ * 2. for _view_ entities with localized elements[^1]:
46
40
  * as view using a copy of the original query, but replacing all sources by
47
41
  * their localized convenience view variant if present
48
42
  *
49
43
  * [^1]: That is, the element has `localized: true`.
50
44
  *
51
- * First, all "direct ones" are built (1). Then we build all 2 and 3
52
- * transitively (i.e. as long as an entity has an association which directly or
53
- * indirectly leads to an entity with localized elements, we create a localized
54
- * variant for it), and finally make sure via redirection that associations in
55
- * localized convenience views have as target the localized convenience view
56
- * variant if present.
45
+ * First, all "direct ones" are built (1). Then we build 2 transitively and
46
+ * finally make sure via redirection that associations in localized convenience
47
+ * views have as target the localized convenience view variant if present.
57
48
  *
58
49
  * @param {CSN.Model} csn
59
50
  * Input CSN model. Should not have existing convenience views.
@@ -82,8 +73,6 @@ function _addLocalizationViews(csn, options, config) {
82
73
  const { useJoins, acceptLocalizedView, ignoreUnknownExtensions } = config;
83
74
  const noCoalesce = (options.localizedLanguageFallback === 'none' ||
84
75
  options.localizedWithoutCoalesce);
85
- // default is true, hence only check for explicitly disabled option
86
- const ignoreAssocToLocalized = options.fewerLocalizedViews !== false;
87
76
 
88
77
  /**
89
78
  * Indicator that a definition is localized and has a convenience view.
@@ -103,29 +92,22 @@ function _addLocalizationViews(csn, options, config) {
103
92
  * @type {Record<string, boolean>}
104
93
  */
105
94
  const createdForEntity = Object.create(null); // $inferred = 'LOCALIZED-HORIZONTAL'
106
- /**
107
- * List of artifacts for which the view/entity is a target.
108
- * Used to transitively create convenience views.
109
- * @type {Record<string, string[]>}
110
- */
111
- const targetFor = Object.create(null);
112
95
 
113
96
  createDirectConvenienceViews(); // 1
114
- createTransitiveConvenienceViews(); // 2 + 3
97
+ createConvenienceViewsForViews(); // 2
115
98
  applyAnnotationsForLocalizedViews();
116
99
  sortLocalizedForTests(csn, options);
117
100
  messageFunctions.throwWithError();
118
101
  return csn;
119
102
 
120
103
  /**
121
- * Create direct convenience localization views for entities that have localized elements.
122
- * Only entities that have `localized` elements are used. `localized` in types or sub-elements
123
- * are not respected.
104
+ * Create direct convenience localization views for table entities (not views or projections)
105
+ * that have localized elements. `localized` in types or sub-elements are not respected.
124
106
  */
125
107
  function createDirectConvenienceViews() {
126
108
  forEachDefinition(csn, (art, artName) => {
127
109
  if (art.kind !== 'entity' || art.query || art.projection)
128
- // Ignore non-entities and views. The latter are handled at a later point (step 2+3).
110
+ // Ignore non-entities and views. The latter are handled at a later point (step 2).
129
111
  return;
130
112
 
131
113
  if (isInLocalizedNamespace(artName))
@@ -412,42 +394,30 @@ function _addLocalizationViews(csn, options, config) {
412
394
  }
413
395
 
414
396
  /**
415
- * Transitively create convenience views for entities/views that have
416
- * associations to localized entities, views that themselves have such
417
- * a dependency or views that contain projections on localized elements.
397
+ * Create convenience views for views/projections that contain localized elements.
418
398
  *
419
399
  * The algorithm is as follows:
420
400
  *
421
401
  * 1. For each view with elements that have `localized: true` markers:
422
402
  * => add view to array `entities`
423
- * For each view/entity with associations (if fewerLocalizedViews is false):
424
- * - If target is NOT localized => add view/entity to target's `_targetFor` property
425
- * - If target is localized => add view/entity to array `entities`
426
- * 2. As long as `entities` has entries:
427
- * a. For each entry in `entities`
428
- * - Create a convenience view
429
- * - If the entry has a `_targetFor` property, add its entries to `nextEntities`
430
- * because they now have a transitive dependency on a localized view.
431
- * b. Copy all entries from `nextEntities` to `entities`.
432
- * c. Clear `nextEntities`.
403
+ * 2. For each entry in `entities`: create a convenience view.
433
404
  * 3. Rewrite all references to the localized variants.
434
405
  */
435
- function createTransitiveConvenienceViews() {
436
- let entities = [];
406
+ function createConvenienceViewsForViews() {
407
+ const entities = [];
437
408
  forEachDefinition( csn, collectLocalizedEntities );
438
-
439
- let nextEntities = [];
440
- while (entities.length) {
441
- entities.forEach( createViewAndCollectSources );
442
- entities = [ ...nextEntities ];
443
- nextEntities = [];
409
+ for (const artName of entities) {
410
+ if (!localizedViewFor[artName])
411
+ addLocalizedView(artName);
444
412
  }
445
413
  forEachDefinition( csn, rewriteToLocalized );
446
- return;
447
414
 
448
415
  function collectLocalizedEntities( art, artName ) {
449
416
  if (art.kind !== 'entity')
450
- // Ignore non-entities but also process entities because of associations.
417
+ // Ignore non-entities.
418
+ return;
419
+ if (!art.query && !art.projection)
420
+ // Table entities are already handled in step 1.
451
421
  return;
452
422
  if (isInLocalizedNamespace(artName))
453
423
  // Ignore existing `localized.` views.
@@ -458,63 +428,24 @@ function _addLocalizationViews(csn, options, config) {
458
428
 
459
429
  _collectFromElements(art.elements);
460
430
 
431
+ // helper function, return value only used internally to stop recursion if localized element is found
461
432
  function _collectFromElements(elements) {
462
- if (!elements)
463
- return;
464
-
465
- // Element may be localized or has an association to localized entity.
433
+ // Element may be localized.
466
434
  for (const elemName in elements) {
467
435
  const elem = elements[elemName];
468
436
 
469
- if ((art.query || art.projection) && elem.localized && !elem.key && !elem.$key) {
470
- // e.g. projections ; ignore if key is present (warning already issued) or
471
- // if the artifact is an entity (already processed in (1))
437
+ if (elem.localized && !elem.key && !elem.$key) {
438
+ // e.g. projections ; ignore if key is present (warning already issued)
472
439
  entities.push(artName);
440
+ return true; // one push per artifact is enough
473
441
  }
474
- else if (!ignoreAssocToLocalized && elem.target) {
475
- // If the target has a localized view then we are localized as well.
476
- // Only necessary if "fewerLocalizedView" is disabled.
477
- const def = csn.definitions[elem.target];
478
- if (!def)
479
- continue;
480
-
481
- if (localizedViewFor[elem.target]) {
482
- // The target may already be localized and if so, then add the artifact
483
- // to the to-be-processed entities.
484
- entities.push(artName);
485
- }
486
- else {
487
- // Otherwise the target view may become localized at a later point so
488
- // we should add it to a reverse-dependency list.
489
- targetFor[elem.target] ??= [];
490
- targetFor[elem.target].push(artName);
491
- }
492
- }
493
- else {
494
- // recursive check
495
- _collectFromElements(elem.elements);
496
- }
497
- }
498
- }
499
- }
500
442
 
501
- /**
502
- * Create a localization view for `artName` and add views/entities that depend
503
- * on `artName` to `nextEntities`
504
- *
505
- * @param {string} artName
506
- */
507
- function createViewAndCollectSources( artName ) {
508
- if (localizedViewFor[artName]) {
509
- // view/entity was already processed
510
- return;
443
+ // recursive check
444
+ if (elem.elements && _collectFromElements(elem.elements))
445
+ return true; // found localized element
446
+ }
447
+ return false;
511
448
  }
512
-
513
- addLocalizedView(artName);
514
-
515
- if (!ignoreAssocToLocalized && targetFor[artName])
516
- nextEntities.push(...targetFor[artName]);
517
- delete targetFor[artName];
518
449
  }
519
450
  }
520
451
 
@@ -761,7 +692,7 @@ function checkExistingLocalizationViews(csn, options, messageFunctions) {
761
692
  let hasNonViews = false;
762
693
 
763
694
  forEachDefinition(csn, (def, name) => {
764
- if (isInLocalizedNamespace(name) || name === 'localized') {
695
+ if (isInLocalizedNamespace(name)) {
765
696
  if (!def.query && !def.projection) {
766
697
  if (!name.endsWith('.texts')) {
767
698
  hasNonViews = true;
@@ -17,7 +17,8 @@ const { cloneCsnNonDict } = require('../../base/cloneCsn');
17
17
  const { forEach } = require('../../utils/objectUtils');
18
18
  const { assignAnnotation } = require('../../edm/edmUtils');
19
19
 
20
- function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTypeInfo, isExternalServiceMember, error, csnUtils, options) {
20
+ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTypeInfo, isExternalServiceMember, messageFunctions, csnUtils, options) {
21
+ const { error, warning } = messageFunctions;
21
22
  const allMgdAssocDefs = [];
22
23
  forEachDefinition(csn, (def, defName) => {
23
24
  if (def.kind === 'entity' && !isExternalServiceMember(def, defName)) {
@@ -53,6 +54,14 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
53
54
  adaptManagedAssociationSpecialFields(flatElt, flatEltName, flattenedSubTree);
54
55
  orderedElementList.push([ flatEltName, flatElt ]);
55
56
  });
57
+ // Also, propagate 'default' to flattened leaf element, but only for single-element structures;
58
+ // warn and ignore for multi-element structures
59
+ if (child.default?.val !== undefined) {
60
+ if (flattenedSubTree.length === 1)
61
+ flattenedSubTree[0][1].default = { ...child.default };
62
+ else if (flattenedSubTree.length > 1)
63
+ warning('type-default-ignored-for-struct', location, { '#': 'std', name: childName });
64
+ }
56
65
  }
57
66
  else {
58
67
  // TODO: run adaptManagedAssociationSpecialFields here as well
@@ -246,7 +255,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
246
255
  const exprAnnoNames = copyAnnotations(elt, flatElt, false, excludes).filter(pn => pn[0] === '@');
247
256
  flattenAndPrefixExprPaths(flatElt, exprAnnoNames, elt.$path, rootPrefix, typeIdx);
248
257
  // Copy selected type properties
249
- [ 'key', 'virtual', 'masked', 'viaAll', 'localized' ].forEach((p) => {
258
+ [ 'key', 'virtual', 'viaAll', 'localized' ].forEach((p) => {
250
259
  if (elt[p] != null)
251
260
  flatElt[p] = elt[p];
252
261
  });
@@ -105,7 +105,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
105
105
 
106
106
  // For a structured element 'elem', return a dictionary of flattened elements to
107
107
  // replace it, flattening names with pathDelimiter's value and propagating all annotations and the
108
- // type properties 'key', 'notNull', 'virtual', 'masked' to the flattened elements.
108
+ // type properties 'key', 'notNull', 'virtual' to the flattened elements.
109
109
  // example input:
110
110
  // { elem: {
111
111
  // key: true,
@@ -200,7 +200,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
200
200
  copyAnnotations(elem, flatElem, false, excludes);
201
201
 
202
202
  // Copy selected type properties
203
- const props = [ 'key', 'virtual', 'masked', 'viaAll' ];
203
+ const props = [ 'key', 'virtual', 'viaAll' ];
204
204
  // 'localized' is needed for OData
205
205
  if (options.toOdata)
206
206
  props.push('localized');
@@ -209,6 +209,28 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
209
209
  flatElem[p] = elem[p];
210
210
  }
211
211
  });
212
+
213
+ // Propagate 'default' to flattened leaf element (SQL only)
214
+ // Only apply for single-element structures; warn and ignore for multi-element structures
215
+ if (elem.default?.val !== undefined && (options.transformation === 'sql' || options.transformation === 'effective')) {
216
+ const flattenedNames = Object.keys(result);
217
+
218
+ if (flattenedNames.length === 1) {
219
+ // Single element: propagate default if leaf doesn't have one
220
+ const singleElem = result[flattenedNames[0]];
221
+ if (singleElem.default?.val === undefined) {
222
+ singleElem.default = elem.default; // Copy entire default object {val, location}
223
+ }
224
+ }
225
+ else if (flattenedNames.length > 1) {
226
+ // Multi-element structure: warn that default is ignored
227
+ warning('type-default-ignored-for-struct', pathInCsn, {
228
+ '#': 'std',
229
+ name: elemName,
230
+ });
231
+ }
232
+ }
233
+
212
234
  return result;
213
235
  }
214
236
 
@@ -319,7 +341,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
319
341
  // Copy elements/items and we're finished. No need to look up actual base type,
320
342
  // since it must also be structured and must contain at least as many elements,
321
343
  // if not more (in client style CSN).
322
- if (typeRef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds'))
344
+ if (typeRef.elements)
323
345
  nodeWithType.elements = cloneCsnDict(typeRef.elements, options);
324
346
  else if (typeRef.items)
325
347
  nodeWithType.items = cloneCsnNonDict(typeRef.items, options);
@@ -54,7 +54,7 @@ module.exports = (csn, options) => {
54
54
 
55
55
  const ruleToFunction = {
56
56
  __proto__: null,
57
- never: skip,
57
+ never: skip, // TODO: should work like corrected `onlyViaParent`, see #13794
58
58
  onlyViaParent: skip, // TODO: not correct
59
59
  onlyViaArtifact,
60
60
  notWithPersistenceTable,
@@ -67,7 +67,6 @@ module.exports = (csn, options) => {
67
67
  const memberPropagationRules = {
68
68
  key: skip,
69
69
  enum: notWithTypeOrigin,
70
- masked: skip,
71
70
  virtual: notWithTypeRef,
72
71
  items: specialItemsRules,
73
72
  elements: (prop, target, source) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "6.9.3",
3
+ "version": "7.0.1",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",
@@ -25,6 +25,6 @@
25
25
  "CHANGELOG.md"
26
26
  ],
27
27
  "engines": {
28
- "node": ">=20"
28
+ "node": ">=22"
29
29
  }
30
30
  }