@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.
- package/CHANGELOG.md +76 -2
- package/bin/cdsc.js +4 -33
- package/doc/IncompatibleChanges_v7.md +639 -0
- package/lib/api/main.js +4 -56
- package/lib/api/options.js +5 -15
- package/lib/api/validate.js +1 -0
- package/lib/base/builtins.js +1 -2
- package/lib/base/csnRefs.js +2 -6
- package/lib/base/message-registry.js +82 -76
- package/lib/base/messages.js +8 -5
- package/lib/base/optionProcessor.js +2 -72
- package/lib/base/specialOptions.js +20 -17
- package/lib/checks/defaultValues.js +1 -39
- package/lib/checks/hasPersistedElements.js +19 -3
- package/lib/checks/parameters.js +0 -34
- package/lib/checks/selectItems.js +2 -38
- package/lib/checks/typeParameters.js +162 -0
- package/lib/checks/validator.js +5 -8
- package/lib/compiler/assert-consistency.js +19 -5
- package/lib/compiler/checks.js +47 -43
- package/lib/compiler/define.js +6 -6
- package/lib/compiler/extend.js +102 -111
- package/lib/compiler/generate.js +4 -8
- package/lib/compiler/populate.js +4 -7
- package/lib/compiler/propagator.js +9 -9
- package/lib/compiler/resolve.js +205 -7
- package/lib/compiler/shared.js +76 -82
- package/lib/compiler/tweak-assocs.js +102 -22
- package/lib/compiler/utils.js +57 -12
- package/lib/compiler/xpr-rewrite.js +2 -15
- package/lib/edm/annotations/edmJson.js +14 -10
- package/lib/edm/annotations/genericTranslation.js +3 -1
- package/lib/edm/annotations/preprocessAnnotations.js +9 -26
- package/lib/edm/csn2edm.js +27 -20
- package/lib/edm/edmUtils.js +25 -0
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +2237 -2241
- package/lib/gen/Dictionary.json +17 -2
- package/lib/json/from-csn.js +67 -52
- package/lib/json/to-csn.js +28 -25
- package/lib/language/textUtils.js +0 -13
- package/lib/main.d.ts +22 -59
- package/lib/main.js +1 -1
- package/lib/model/csnUtils.js +9 -8
- package/lib/parsers/AstBuildingParser.js +45 -55
- package/lib/parsers/Lexer.js +2 -0
- package/lib/parsers/identifiers.js +0 -9
- package/lib/render/toCdl.js +41 -40
- package/lib/render/toSql.js +8 -1
- package/lib/render/utils/common.js +1 -1
- package/lib/render/utils/sql.js +2 -3
- package/lib/tool-lib/enrichCsn.js +1 -2
- package/lib/transform/db/applyTransformations.js +7 -5
- package/lib/transform/db/assertUnique.js +8 -51
- package/lib/transform/db/associations.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -15
- package/lib/transform/db/expansion.js +9 -12
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/groupByOrderBy.js +0 -16
- package/lib/transform/db/views.js +57 -161
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdata.js +25 -14
- package/lib/transform/forRelationalDB.js +93 -301
- package/lib/transform/localized.js +33 -102
- package/lib/transform/odata/flattening.js +11 -2
- package/lib/transform/transformUtils.js +25 -3
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -2
- package/package.json +2 -2
- 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
|
|
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
|
|
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
|
|
42
|
-
* 2. for
|
|
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
|
|
52
|
-
*
|
|
53
|
-
*
|
|
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
|
-
|
|
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
|
|
122
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
436
|
-
|
|
406
|
+
function createConvenienceViewsForViews() {
|
|
407
|
+
const entities = [];
|
|
437
408
|
forEachDefinition( csn, collectLocalizedEntities );
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
470
|
-
// e.g. projections ; ignore if key is present (warning already issued)
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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)
|
|
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,
|
|
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', '
|
|
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'
|
|
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', '
|
|
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
|
|
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": "
|
|
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": ">=
|
|
28
|
+
"node": ">=22"
|
|
29
29
|
}
|
|
30
30
|
}
|