@sap/cds-compiler 4.1.2 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +101 -1
- package/bin/cdsc.js +6 -3
- package/doc/CHANGELOG_BETA.md +5 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +2 -2
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +24 -24
- package/lib/base/message-registry.js +41 -6
- package/lib/base/messages.js +7 -0
- package/lib/base/model.js +37 -8
- package/lib/checks/elements.js +11 -10
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +5 -2
- package/lib/checks/queryNoDbArtifacts.js +2 -3
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/utils.js +3 -2
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +27 -24
- package/lib/compiler/base.js +6 -2
- package/lib/compiler/builtins.js +34 -34
- package/lib/compiler/checks.js +179 -208
- package/lib/compiler/classes.js +2 -2
- package/lib/compiler/cycle-detector.js +6 -6
- package/lib/compiler/define.js +66 -45
- package/lib/compiler/extend.js +81 -72
- package/lib/compiler/finalize-parse-cdl.js +26 -26
- package/lib/compiler/generate.js +61 -45
- package/lib/compiler/index.js +47 -49
- package/lib/compiler/kick-start.js +8 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +42 -35
- package/lib/compiler/propagator.js +6 -6
- package/lib/compiler/resolve.js +170 -126
- package/lib/compiler/shared.js +122 -45
- package/lib/compiler/tweak-assocs.js +93 -40
- package/lib/compiler/utils.js +15 -12
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +678 -772
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +686 -646
- package/lib/edm/edmUtils.js +277 -296
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1253 -1276
- package/lib/json/from-csn.js +34 -4
- package/lib/json/to-csn.js +4 -4
- package/lib/language/language.g4 +2 -5
- package/lib/main.d.ts +61 -1
- package/lib/model/csnUtils.js +31 -2
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/modelCompare/compare.js +37 -2
- package/lib/modelCompare/utils/filter.js +1 -1
- package/lib/optionProcessor.js +15 -3
- package/lib/render/toCdl.js +30 -4
- package/lib/render/toSql.js +5 -9
- package/lib/render/utils/common.js +8 -6
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +121 -47
- package/lib/transform/db/flattening.js +75 -7
- package/lib/transform/forOdata.js +4 -1
- package/lib/transform/forRelationalDB.js +80 -62
- package/lib/transform/localized.js +91 -54
- package/lib/transform/transformUtils.js +9 -10
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
|
@@ -9,7 +9,7 @@ const { cloneCsnNonDict,
|
|
|
9
9
|
const { makeMessageFunction } = require('../base/messages');
|
|
10
10
|
const transformUtils = require('./transformUtils');
|
|
11
11
|
const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');
|
|
12
|
-
const { csnRefs, pathId, traverseQuery } = require('../model/csnRefs');
|
|
12
|
+
const { csnRefs, pathId, traverseQuery, columnAlias} = require('../model/csnRefs');
|
|
13
13
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
14
14
|
const validate = require('../checks/validator');
|
|
15
15
|
const { rejectManagedAssociationsAndStructuresForHdbcdsNames } = require('../checks/selectItems');
|
|
@@ -163,6 +163,8 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
163
163
|
// Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
|
|
164
164
|
handleExists(csn, options, error, csnUtils.inspectRef, csnUtils.initDefinition, csnUtils.dropDefinitionCache);
|
|
165
165
|
|
|
166
|
+
doA2J && flattening.linkForeignKeyAnnotationExtensionsToAssociation(csn, options);
|
|
167
|
+
|
|
166
168
|
// Check if structured elements and managed associations are compared in an expression
|
|
167
169
|
// and expand these structured elements. This tuple expansion allows all other
|
|
168
170
|
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
@@ -463,70 +465,71 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
463
465
|
Object.assign(csnUtils, csnRefApi);
|
|
464
466
|
}
|
|
465
467
|
|
|
468
|
+
// For non-A2J only
|
|
466
469
|
function handleMixinOnConditions(artifact, artifactName) {
|
|
467
|
-
if (!artifact.query)
|
|
470
|
+
if (!artifact.query) // projections can't have mixins
|
|
468
471
|
return;
|
|
469
472
|
forAllQueries(artifact.query, (query, path) => {
|
|
470
|
-
const { mixin } = query.SELECT
|
|
471
|
-
if(mixin) {
|
|
473
|
+
const { mixin } = query.SELECT || {};
|
|
474
|
+
if (mixin) {
|
|
472
475
|
query.SELECT.columns
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
})
|
|
476
|
+
// filter for associations which are used in the SELECT
|
|
477
|
+
.filter((c) => {
|
|
478
|
+
return c.ref && c.ref.length > 1;
|
|
479
|
+
})
|
|
480
|
+
.forEach((usedAssoc) => {
|
|
481
|
+
const assocName = pathId(usedAssoc.ref[0]);
|
|
482
|
+
const mixinAssociation = mixin[assocName];
|
|
483
|
+
if (mixinAssociation)
|
|
484
|
+
mixinAssociation.on = getResolvedMixinOnCondition(mixinAssociation, query, assocName, path.concat(['mixin', assocName]));
|
|
485
|
+
})
|
|
484
486
|
}
|
|
485
|
-
}, [
|
|
486
|
-
|
|
487
|
-
function getResolvedMixinOnCondition(csn, mixinAssociation, query, assocName, path){
|
|
488
|
-
const { inspectRef } = csnRefs(csn);
|
|
489
|
-
const referencedThroughStar = query.SELECT.columns.some((column) => column === '*');
|
|
490
|
-
return mixinAssociation.on
|
|
491
|
-
.map((onConditionPart, i) => {
|
|
492
|
-
let columnToReplace;
|
|
493
|
-
if(onConditionPart.ref && (onConditionPart.ref[0] === '$projection' || onConditionPart.ref[0] === '$self')){
|
|
494
|
-
const { links } = inspectRef(path.concat(['on', i]));
|
|
495
|
-
if(links){
|
|
496
|
-
columnToReplace = onConditionPart.ref[links.length - 1];
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
if (!columnToReplace)
|
|
500
|
-
return onConditionPart;
|
|
501
|
-
|
|
502
|
-
const replaceWith = query.SELECT.columns.find((column) =>
|
|
503
|
-
column.as && column.as === columnToReplace ||
|
|
504
|
-
column.ref && column.ref[0] === columnToReplace
|
|
505
|
-
);
|
|
506
|
-
if (!replaceWith && referencedThroughStar) {
|
|
507
|
-
// not explicitly in column list, check query sources
|
|
508
|
-
// get$combined also includes elements which are part of "excluding {}"
|
|
509
|
-
// this shouldn't be an issue here, as such references get rejected
|
|
510
|
-
const elementsOfQuerySources = csnUtils.get$combined(query);
|
|
511
|
-
forEach(elementsOfQuerySources, (id, element) => {
|
|
512
|
-
// if the ref points to an element which is not explicitly exposed in the column list,
|
|
513
|
-
// but through the '*' operator -> replace the $projection / $self with the correct source entity
|
|
514
|
-
if(id === columnToReplace)
|
|
515
|
-
onConditionPart.ref[0] = element[0].parent;
|
|
516
|
-
});
|
|
517
|
-
}
|
|
487
|
+
}, ['definitions', artifactName, 'query']);
|
|
488
|
+
}
|
|
518
489
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
490
|
+
// For non-A2J only
|
|
491
|
+
function getResolvedMixinOnCondition(mixinAssociation, query, assocName, path) {
|
|
492
|
+
const referencedThroughStar = query.SELECT.columns.some((column) => column === '*');
|
|
493
|
+
return mixinAssociation.on.map(handeMixinOnConditionPart);
|
|
494
|
+
|
|
495
|
+
function handeMixinOnConditionPart(onConditionPart, i) {
|
|
496
|
+
let columnToReplace;
|
|
497
|
+
if (onConditionPart.ref && (onConditionPart.ref[0] === '$projection' || onConditionPart.ref[0] === '$self')){
|
|
498
|
+
const { links } = csnUtils.inspectRef(path.concat(['on', i]));
|
|
499
|
+
if (links) {
|
|
500
|
+
columnToReplace = onConditionPart.ref[links.length - 1];
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (!columnToReplace)
|
|
504
|
+
return onConditionPart;
|
|
505
|
+
|
|
506
|
+
const replaceWith = query.SELECT.columns.find(col => columnAlias(col) === columnToReplace);
|
|
507
|
+
if (!replaceWith && referencedThroughStar) {
|
|
508
|
+
// not explicitly in column list, check query sources
|
|
509
|
+
// get$combined also includes elements which are part of "excluding {}"
|
|
510
|
+
// this shouldn't be an issue here, as such references get rejected
|
|
511
|
+
const elementsOfQuerySources = csnUtils.get$combined(query);
|
|
512
|
+
forEach(elementsOfQuerySources, (id, element) => {
|
|
513
|
+
// if the ref points to an element which is not explicitly exposed in the column list,
|
|
514
|
+
// but through the '*' operator -> replace the $projection / $self with the correct source entity
|
|
515
|
+
if(id === columnToReplace)
|
|
516
|
+
onConditionPart.ref[0] = element[0].parent;
|
|
517
|
+
});
|
|
518
|
+
return onConditionPart;
|
|
519
|
+
}
|
|
520
|
+
else if (replaceWith) {
|
|
521
|
+
const clone = cloneCsnNonDict(replaceWith, options);
|
|
522
|
+
delete clone.cast; // No implicit CAST in on-condition
|
|
523
|
+
delete clone.as;
|
|
524
|
+
return clone;
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
return onConditionPart
|
|
528
|
+
}
|
|
527
529
|
}
|
|
528
530
|
}
|
|
529
531
|
|
|
532
|
+
|
|
530
533
|
/**
|
|
531
534
|
* @param {CSN.Artifact} artifact
|
|
532
535
|
* @param {string} artifactName
|
|
@@ -701,7 +704,9 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
701
704
|
throwWithAnyError();
|
|
702
705
|
// the augmentor isn't able to deal with technical configurations and since assoc2join can ignore it we
|
|
703
706
|
// simply make it invisible and copy it over to the result csn
|
|
704
|
-
forEachDefinition(csn,
|
|
707
|
+
forEachDefinition(csn,
|
|
708
|
+
art => art.technicalConfig && setProp(art, 'technicalConfig',
|
|
709
|
+
art.technicalConfig));
|
|
705
710
|
|
|
706
711
|
const newCsn = translateAssocsToJoinsCSN(csn, options);
|
|
707
712
|
|
|
@@ -714,6 +719,23 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
714
719
|
newCsn.definitions[artName].technicalConfig = art.technicalConfig;
|
|
715
720
|
|
|
716
721
|
});
|
|
722
|
+
// restore $fkExtensions and $structRef for foreign key annotations
|
|
723
|
+
if (isBetaEnabled(options, 'annotateForeignKeys')) {
|
|
724
|
+
forEachDefinition(csn, (oldDef, artName) => {
|
|
725
|
+
const newDef = newCsn.definitions[artName];
|
|
726
|
+
if(oldDef?.elements) {
|
|
727
|
+
Object.entries(oldDef.elements).forEach(([eltName, oldElt]) => {
|
|
728
|
+
const newElt = newDef.elements[eltName];
|
|
729
|
+
if(oldElt.$fkExtensions)
|
|
730
|
+
setProp(newElt, '$fkExtensions', oldElt.$fkExtensions);
|
|
731
|
+
oldElt.keys?.forEach((fk, i) => {
|
|
732
|
+
if(fk.$structRef && newElt.keys?.[i])
|
|
733
|
+
setProp(newElt.keys[i], '$structRef', fk.$structRef);
|
|
734
|
+
})
|
|
735
|
+
})
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
}
|
|
717
739
|
csn = newCsn;
|
|
718
740
|
}
|
|
719
741
|
|
|
@@ -748,11 +770,7 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
748
770
|
function renamePrimitiveTypesAndUuid(val, node, key) {
|
|
749
771
|
// assert key === 'type'
|
|
750
772
|
const hanaNamesMap = createDict({
|
|
751
|
-
'cds.
|
|
752
|
-
'cds.Timestamp': 'cds.UTCTimestamp',
|
|
753
|
-
'cds.Date': 'cds.LocalDate',
|
|
754
|
-
'cds.Time': 'cds.LocalTime',
|
|
755
|
-
'cds.UUID': 'cds.String',
|
|
773
|
+
'cds.UUID': 'cds.String'
|
|
756
774
|
});
|
|
757
775
|
node[key] = hanaNamesMap[val] || val;
|
|
758
776
|
if (val === 'cds.UUID' && !node.length) {
|
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
forAllQueries,
|
|
14
14
|
sortCsnDefinitionsForTests,
|
|
15
15
|
} = require('../model/csnUtils');
|
|
16
|
+
const {CompilerAssertion} = require('../base/error');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Indicator that a definition is localized and has a convenience view.
|
|
@@ -34,10 +35,11 @@ const _isViewForEntity = Symbol('_isViewForEntity'); // $inferred = 'LOCALIZED-H
|
|
|
34
35
|
* Used to transitively create convenience views.
|
|
35
36
|
*/
|
|
36
37
|
const _targetFor = Symbol('_targetFor');
|
|
38
|
+
const annoPersistenceSkip = '@cds.persistence.skip';
|
|
37
39
|
|
|
38
40
|
/**
|
|
39
41
|
* Callback function returning `true` if the localization view should be created.
|
|
40
|
-
* @callback
|
|
42
|
+
* @callback AcceptLocalizedViewCallback
|
|
41
43
|
* @param {string} viewName localization view name
|
|
42
44
|
* @param {string} originalName Artifact name of the original view
|
|
43
45
|
*/
|
|
@@ -45,20 +47,22 @@ const _targetFor = Symbol('_targetFor');
|
|
|
45
47
|
/**
|
|
46
48
|
* Create transitive localized convenience views.
|
|
47
49
|
*
|
|
48
|
-
* A convenience view is created if the entity/view has a localized element
|
|
50
|
+
* A convenience view is created if the entity/view has a localized element[^1]
|
|
49
51
|
* or if it exposes an association leading to a localized-tagged target.
|
|
50
52
|
*
|
|
51
53
|
* INTERNALS:
|
|
52
54
|
* We have three kinds of localized convenience views:
|
|
53
55
|
*
|
|
54
56
|
* 1. "direct ones" using coalesce() for the table entities with localized
|
|
55
|
-
* elements: as projection on the original (created in extend.js)
|
|
56
|
-
* 2. for
|
|
57
|
-
* convenience
|
|
58
|
-
* 3. for
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* convenience view variant if present
|
|
57
|
+
* elements[^1]: as projection on the original and '.texts' entity (created in extend.js)
|
|
58
|
+
* 2. for _table_ entities with associations to entities which have a localized
|
|
59
|
+
* convenience: as projection on the original
|
|
60
|
+
* 3. for _view_ entities with either localized elements[^1] or associations
|
|
61
|
+
* to entities which have a localized convenience view:
|
|
62
|
+
* as view using a copy of the original query, but replacing all sources by
|
|
63
|
+
* their localized convenience view variant if present
|
|
64
|
+
*
|
|
65
|
+
* [^1]: That is, the element has `localized: true`.
|
|
62
66
|
*
|
|
63
67
|
* First, all "direct ones" are built (1). Then we build all 2 and 3
|
|
64
68
|
* transitively (i.e. as long as an entity has an association which directly or
|
|
@@ -68,21 +72,46 @@ const _targetFor = Symbol('_targetFor');
|
|
|
68
72
|
* variant if present.
|
|
69
73
|
*
|
|
70
74
|
* @param {CSN.Model} csn
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
75
|
+
* Input CSN model. Should not have existing convenience views.
|
|
76
|
+
*
|
|
77
|
+
* @param {object} options
|
|
78
|
+
* CSN options. Only few options are used, see below for important ones.
|
|
79
|
+
* Options such as `testMode` or `testSortCsn` can also be set.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} [options.localizedLanguageFallback]
|
|
82
|
+
* Valid values (if set): 'none', 'coalesce' (default)
|
|
83
|
+
* Whether to use a `coalesce()` function when selecting from `.texts` entities.
|
|
84
|
+
* If not set, untranslated strings may not return any value. If 'coalesce'
|
|
85
|
+
* is used, it will fall back to the original string.
|
|
86
|
+
*
|
|
87
|
+
* @param {boolean} [options.localizedWithoutCoalesce]
|
|
88
|
+
* Deprecated version of localizedLanguageFallback. Do not use.
|
|
89
|
+
*
|
|
90
|
+
* @param {boolean} [options.fewerLocalizedViews]
|
|
91
|
+
*
|
|
74
92
|
* @param {object} config
|
|
93
|
+
* Configuration for creating convenience views. Non-user visible options.
|
|
94
|
+
*
|
|
95
|
+
* @param {boolean} [config.useJoins]
|
|
96
|
+
* If true, rewrite the "localized" association to a join in direct convenience views.
|
|
97
|
+
*
|
|
98
|
+
* @param {AcceptLocalizedViewCallback} [config.acceptLocalizedView]
|
|
99
|
+
* A callback that can be used to suppress the creation of localized convenience views
|
|
100
|
+
* if desired. For example, if you want to know which definitions get a convenience view
|
|
101
|
+
* but don't actually want to create them.
|
|
102
|
+
*
|
|
103
|
+
* @param {boolean} [config.ignoreUnknownExtensions]
|
|
104
|
+
* If true, do not emit a warning for annotations on unknown `localized.*` views.
|
|
75
105
|
*/
|
|
76
|
-
function _addLocalizationViews(csn, options,
|
|
106
|
+
function _addLocalizationViews(csn, options, config) {
|
|
77
107
|
const messageFunctions = makeMessageFunction(csn, options);
|
|
78
|
-
if (checkExistingLocalizationViews(csn, options, messageFunctions))
|
|
79
|
-
messageFunctions.throwWithError();
|
|
108
|
+
if (checkExistingLocalizationViews(csn, options, messageFunctions))
|
|
80
109
|
return csn;
|
|
81
|
-
}
|
|
82
110
|
|
|
83
|
-
const { acceptLocalizedView, ignoreUnknownExtensions } = config;
|
|
111
|
+
const { useJoins, acceptLocalizedView, ignoreUnknownExtensions } = config;
|
|
84
112
|
const noCoalesce = (options.localizedLanguageFallback === 'none' ||
|
|
85
113
|
options.localizedWithoutCoalesce);
|
|
114
|
+
const ignoreAssocToLocalized = !!options.fewerLocalizedViews;
|
|
86
115
|
|
|
87
116
|
createDirectConvenienceViews(); // 1
|
|
88
117
|
createTransitiveConvenienceViews(); // 2 + 3
|
|
@@ -179,10 +208,10 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
179
208
|
copyLocation(convenienceView.query, entity);
|
|
180
209
|
|
|
181
210
|
if (shouldUseJoin)
|
|
182
|
-
// Expand elements
|
|
211
|
+
// Expand elements; (variant 1)
|
|
183
212
|
columns.push( ...columnsForEntityWithExcludeList( entity, 'L_0', textElements ) )
|
|
184
213
|
else
|
|
185
|
-
columns.push( '*' );
|
|
214
|
+
columns.push( '*' ); // (variant 2)
|
|
186
215
|
|
|
187
216
|
for (const originalElement of textElements) {
|
|
188
217
|
const elem = entity.elements[originalElement];
|
|
@@ -330,7 +359,7 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
330
359
|
let keyCount = 0;
|
|
331
360
|
let textElements = [];
|
|
332
361
|
|
|
333
|
-
forEachGeneric(art, 'elements', (elem, elemName , _prop
|
|
362
|
+
forEachGeneric(art, 'elements', (elem, elemName , _prop) => {
|
|
334
363
|
if (elem.$ignore) // from SAP HANA backend
|
|
335
364
|
return;
|
|
336
365
|
|
|
@@ -339,11 +368,6 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
339
368
|
|
|
340
369
|
if (elem.key || elem.$key || elem.localized)
|
|
341
370
|
textElements.push( elemName );
|
|
342
|
-
|
|
343
|
-
if ((elem.key|| elem.$key) && elem.localized) {
|
|
344
|
-
messageFunctions.warning('def-ignoring-localized', path, { keyword: 'localized' },
|
|
345
|
-
'Keyword $(KEYWORD) is ignored for primary keys');
|
|
346
|
-
}
|
|
347
371
|
}, artPath);
|
|
348
372
|
|
|
349
373
|
if (textElements.length <= keyCount || keyCount <= 0)
|
|
@@ -364,12 +388,17 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
364
388
|
'Skipped creation of convenience view for $(NAME) because its texts entity could not be found' );
|
|
365
389
|
return null;
|
|
366
390
|
}
|
|
367
|
-
|
|
368
391
|
if (!isValidTextsEntity( textsEntity )) {
|
|
369
392
|
messageFunctions.info( null, [ 'definitions', textsName ], { name: artName },
|
|
370
393
|
'Skipped creation of convenience view for $(NAME) because its texts entity does not appear to be valid' );
|
|
371
394
|
return null;
|
|
372
395
|
}
|
|
396
|
+
if (!art[annoPersistenceSkip] && textsEntity[annoPersistenceSkip]) {
|
|
397
|
+
messageFunctions.message( 'anno-unexpected-localized-skip', artPath,
|
|
398
|
+
{ name: textsName, art: artName, anno: annoPersistenceSkip },
|
|
399
|
+
'Compiler generated entity $(NAME) must not be annotated with $(ANNO) if $(ART) is not skipped' );
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
373
402
|
|
|
374
403
|
// There may be keys in the original artifact that were added by the core compiler,
|
|
375
404
|
// for example elements that are marked @cds.valid.from.
|
|
@@ -391,22 +420,23 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
391
420
|
|
|
392
421
|
/**
|
|
393
422
|
* Transitively create convenience views for entities/views that have
|
|
394
|
-
* associations to localized entities
|
|
395
|
-
* a dependency.
|
|
423
|
+
* associations to localized entities, views that themselves have such
|
|
424
|
+
* a dependency or views that contain projections on localized elements.
|
|
396
425
|
*
|
|
397
426
|
* The algorithm is as follows:
|
|
398
427
|
*
|
|
399
|
-
* 1. For each view
|
|
400
|
-
*
|
|
401
|
-
*
|
|
428
|
+
* 1. For each view with elements that have `localized: true` markers:
|
|
429
|
+
* => add view to array `entities`
|
|
430
|
+
* For each view/entity with associations:
|
|
431
|
+
* - If target is NOT localized => add view/entity to target's `_targetFor` property
|
|
432
|
+
* - If target is localized => add view/entity to array `entities`
|
|
402
433
|
* 2. As long as `entities` has entries:
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
* c. Clear `nextEntities`.
|
|
434
|
+
* a. For each entry in `entities`
|
|
435
|
+
* - Create a convenience view
|
|
436
|
+
* - If the entry has a `_targetFor` property, add its entries to `nextEntities`
|
|
437
|
+
* because they now have a transitive dependency on a localized view.
|
|
438
|
+
* b. Copy all entries from `nextEntities` to `entities`.
|
|
439
|
+
* c. Clear `nextEntities`.
|
|
410
440
|
* 3. Rewrite all references to the localized variants.
|
|
411
441
|
*/
|
|
412
442
|
function createTransitiveConvenienceViews() {
|
|
@@ -448,10 +478,9 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
448
478
|
// if the artifact is an entity (already processed in (1))
|
|
449
479
|
entities.push(artName);
|
|
450
480
|
}
|
|
451
|
-
else if (elem.target) {
|
|
481
|
+
else if (!ignoreAssocToLocalized && elem.target) {
|
|
452
482
|
// If the target has a localized view then we are localized as well.
|
|
453
483
|
const def = csn.definitions[elem.target];
|
|
454
|
-
// TODO: What if elem.target cannot be found? Could this happen after flattening, ...?
|
|
455
484
|
if (!def)
|
|
456
485
|
continue;
|
|
457
486
|
|
|
@@ -490,7 +519,7 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
490
519
|
|
|
491
520
|
addLocalizedView(artName);
|
|
492
521
|
|
|
493
|
-
if (art[_targetFor])
|
|
522
|
+
if (!ignoreAssocToLocalized && art[_targetFor])
|
|
494
523
|
nextEntities.push(...art[_targetFor]);
|
|
495
524
|
delete art[_targetFor];
|
|
496
525
|
}
|
|
@@ -578,14 +607,22 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
578
607
|
if (!obj || !obj.ref)
|
|
579
608
|
return;
|
|
580
609
|
const ref = Array.isArray(obj.ref) ? obj.ref[0] : obj.ref;
|
|
581
|
-
if (typeof ref
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
else
|
|
610
|
+
if (typeof ref === 'string') {
|
|
611
|
+
const def = csn.definitions[ref];
|
|
612
|
+
if (def && def[_hasLocalizedView]) {
|
|
613
|
+
if (Array.isArray(obj.ref))
|
|
614
|
+
obj.ref[0] = def[_hasLocalizedView];
|
|
615
|
+
else
|
|
588
616
|
obj.ref = def[_hasLocalizedView];
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
} else if (ref.id) {
|
|
620
|
+
const def = csn.definitions[ref.id];
|
|
621
|
+
if (def && def[_hasLocalizedView])
|
|
622
|
+
obj.ref[0].id = def[_hasLocalizedView];
|
|
623
|
+
|
|
624
|
+
} else if (options.testMode) {
|
|
625
|
+
throw new CompilerAssertion('Debug me: Unhandled reference during localized-rewrite!');
|
|
589
626
|
}
|
|
590
627
|
}
|
|
591
628
|
|
|
@@ -593,7 +630,7 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
593
630
|
* @param {string} artName
|
|
594
631
|
*/
|
|
595
632
|
function textsEntityName(artName) {
|
|
596
|
-
// We can assume
|
|
633
|
+
// We can assume that the element exists. This is checked in isEntityPreprocessed().
|
|
597
634
|
return csn.definitions[artName].elements.texts.target;
|
|
598
635
|
}
|
|
599
636
|
|
|
@@ -626,23 +663,23 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
626
663
|
*
|
|
627
664
|
* @param {CSN.Model} csn
|
|
628
665
|
* @param {CSN.Options} options
|
|
629
|
-
* @param [config]
|
|
666
|
+
* @param [config]
|
|
630
667
|
*/
|
|
631
668
|
function addLocalizationViews(csn, options, config = {}) {
|
|
632
|
-
return _addLocalizationViews(csn, options,
|
|
669
|
+
return _addLocalizationViews(csn, options, { ...config, useJoins: false });
|
|
633
670
|
}
|
|
634
671
|
|
|
635
672
|
/**
|
|
636
673
|
* Create transitive localized convenience views to the given CSN but
|
|
637
674
|
* rewrite the "localized" association to joins in direct entity convenience
|
|
638
|
-
* views. This is needed
|
|
675
|
+
* views. This is needed e.g. by SQL for SQLite where A2J is used.
|
|
639
676
|
*
|
|
640
677
|
* @param {CSN.Model} csn
|
|
641
678
|
* @param {CSN.Options} options
|
|
642
|
-
* @param [config]
|
|
679
|
+
* @param [config]
|
|
643
680
|
*/
|
|
644
681
|
function addLocalizationViewsWithJoins(csn, options, config = {}) {
|
|
645
|
-
return _addLocalizationViews(csn, options,
|
|
682
|
+
return _addLocalizationViews(csn, options, { ...config, useJoins: true });
|
|
646
683
|
}
|
|
647
684
|
|
|
648
685
|
/**
|
|
@@ -265,7 +265,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
265
265
|
} else {
|
|
266
266
|
// Primitive child - clone it and restore its cross references
|
|
267
267
|
let flatElemName = elemName + pathDelimiter + childName;
|
|
268
|
-
let flatElem = cloneCsnNonDict(childElem, options);
|
|
268
|
+
let flatElem = cloneCsnNonDict(childElem, { ...options, hiddenPropertiesToClone: [ '$structRef', '$fkExtensions' ] } );
|
|
269
269
|
// Don't take over notNull from leaf elements
|
|
270
270
|
delete flatElem.notNull;
|
|
271
271
|
setProp(flatElem, '_flatElementNameWithDots', elementPath.concat(childName).join('.'));
|
|
@@ -536,7 +536,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
536
536
|
addElement(draftUuid, artifact, artifactName);
|
|
537
537
|
|
|
538
538
|
// CreationDateTime : Timestamp;
|
|
539
|
-
const creationDateTime = createScalarElement('CreationDateTime',
|
|
539
|
+
const creationDateTime = createScalarElement('CreationDateTime', 'cds.Timestamp');
|
|
540
540
|
creationDateTime.CreationDateTime['@Common.Label'] = '{i18n>Draft_CreationDateTime}';
|
|
541
541
|
addElement(creationDateTime, artifact, artifactName);
|
|
542
542
|
|
|
@@ -553,7 +553,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
553
553
|
addElement(draftIsCreatedByMe, artifact, artifactName);
|
|
554
554
|
|
|
555
555
|
// LastChangeDateTime : Timestamp;
|
|
556
|
-
const lastChangeDateTime = createScalarElement('LastChangeDateTime',
|
|
556
|
+
const lastChangeDateTime = createScalarElement('LastChangeDateTime', 'cds.Timestamp');
|
|
557
557
|
lastChangeDateTime.LastChangeDateTime['@Common.Label'] = '{i18n>Draft_LastChangeDateTime}';
|
|
558
558
|
addElement(lastChangeDateTime, artifact, artifactName);
|
|
559
559
|
|
|
@@ -1175,13 +1175,12 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1175
1175
|
// do the paths match?
|
|
1176
1176
|
if(op !== 'like' && !(x.lhs && x.rhs)) {
|
|
1177
1177
|
if(xn.length) {
|
|
1178
|
-
error(
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
'$(PREFIX): Sub path $(NAME) not found in $(ALIAS)');
|
|
1178
|
+
error('expr-invalid-expansion', location, {
|
|
1179
|
+
value: prefix(lhs, op, rhs),
|
|
1180
|
+
name: xn,
|
|
1181
|
+
alias: (x.lhs ? rhs : lhs).ref.join('.')
|
|
1182
|
+
},
|
|
1183
|
+
'Missing sub path $(NAME) in $(ALIAS) for tuple expansion of $(VALUE); both sides must expand to the same sub paths');
|
|
1185
1184
|
}
|
|
1186
1185
|
else {
|
|
1187
1186
|
error(null, location,
|
package/lib/utils/file.js
CHANGED
|
@@ -101,22 +101,22 @@ function cdsFs( fileCache, enableTrace ) {
|
|
|
101
101
|
if (body && body.stack && body.message) {
|
|
102
102
|
// NOTE: checks for instanceof Error are not reliable if error
|
|
103
103
|
// created in different execution env
|
|
104
|
-
traceFS( 'READFILE:cache-
|
|
104
|
+
traceFS( 'READFILE:cache-err:', filename, body.message );
|
|
105
105
|
cb( body ); // no need for process.nextTick( cb, body ) with moduleResolve
|
|
106
106
|
}
|
|
107
107
|
else {
|
|
108
|
-
traceFS( 'READFILE:cache:', filename, body );
|
|
108
|
+
traceFS( 'READFILE:cache: ', filename, body );
|
|
109
109
|
cb( null, body );
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
else {
|
|
113
|
-
traceFS( 'READFILE:start:', filename );
|
|
113
|
+
traceFS( 'READFILE:start: ', filename );
|
|
114
114
|
// TODO: set cache directly to some "delay" - store error differently?
|
|
115
115
|
// e.g. an error of callback functions!
|
|
116
116
|
try {
|
|
117
117
|
reader(filename, enc, (err, data) => {
|
|
118
118
|
fileCache[filename] = err || data;
|
|
119
|
-
traceFS('READFILE:data:', filename, err || data);
|
|
119
|
+
traceFS('READFILE:data: ', filename, err || data);
|
|
120
120
|
cb(err, data);
|
|
121
121
|
});
|
|
122
122
|
}
|
|
@@ -137,14 +137,14 @@ function cdsFs( fileCache, enableTrace ) {
|
|
|
137
137
|
return ( filename, cb ) => {
|
|
138
138
|
let body = fileCache[filename];
|
|
139
139
|
if (body !== undefined) {
|
|
140
|
-
traceFS( 'ISFILE:cache:', filename, body );
|
|
140
|
+
traceFS( 'ISFILE:cache: ', filename, body );
|
|
141
141
|
if (body instanceof Error)
|
|
142
142
|
cb( body ); // no need for process.nextTick( cb, body ) with moduleResolve
|
|
143
143
|
else // body could be empty string
|
|
144
144
|
cb( null, !!body || typeof body === 'string');
|
|
145
145
|
}
|
|
146
146
|
else {
|
|
147
|
-
traceFS( 'ISFILE:start:', filename, body );
|
|
147
|
+
traceFS( 'ISFILE:start: ', filename, body );
|
|
148
148
|
// in the future (if we do module resolve ourselves with just readFile),
|
|
149
149
|
// we avoid parallel readFile by storing having an array of `cb`s in
|
|
150
150
|
// fileCache[ filename ] before starting fs.readFile().
|
|
@@ -156,7 +156,7 @@ function cdsFs( fileCache, enableTrace ) {
|
|
|
156
156
|
body = !!(stat.isFile() || stat.isFIFO());
|
|
157
157
|
if (fileCache[filename] === undefined) // parallel readFile() has been processed
|
|
158
158
|
fileCache[filename] = body;
|
|
159
|
-
traceFS('ISFILE:data:', filename, body);
|
|
159
|
+
traceFS('ISFILE:data: ', filename, body);
|
|
160
160
|
if (body instanceof Error)
|
|
161
161
|
cb(err);
|
|
162
162
|
else
|