@sap/cds-compiler 6.3.6 → 6.4.6
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 -3
- package/LICENSE +32 -0
- package/README.md +14 -2
- package/bin/cdsse.js +0 -3
- package/doc/CHANGELOG_BETA.md +1 -1
- package/doc/CHANGELOG_DEPRECATED.md +1 -1
- package/lib/base/message-registry.js +9 -2
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +2 -0
- package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
- package/lib/checks/existsMustEndInAssoc.js +1 -1
- package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
- package/lib/checks/validator.js +4 -2
- package/lib/compiler/assert-consistency.js +3 -2
- package/lib/compiler/builtins.js +5 -6
- package/lib/compiler/checks.js +37 -26
- package/lib/compiler/define.js +1 -1
- package/lib/compiler/extend.js +39 -50
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/lsp-api.js +1 -1
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +29 -6
- package/lib/compiler/resolve.js +13 -3
- package/lib/compiler/shared.js +157 -133
- package/lib/compiler/tweak-assocs.js +87 -29
- package/lib/compiler/xpr-rewrite.js +164 -160
- package/lib/edm/annotations/edmJson.js +206 -37
- package/lib/edm/csn2edm.js +13 -0
- package/lib/edm/edmUtils.js +2 -2
- package/lib/gen/BaseParser.js +106 -72
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1501 -1509
- package/lib/json/to-csn.js +8 -5
- package/lib/language/genericAntlrParser.js +0 -0
- package/lib/main.js +19 -16
- package/lib/model/csnRefs.js +589 -521
- package/lib/model/csnUtils.js +8 -5
- package/lib/model/enrichCsn.js +1 -0
- package/lib/parsers/AstBuildingParser.js +73 -28
- package/lib/render/toCdl.js +2 -1
- package/lib/render/toHdbcds.js +6 -3
- package/lib/render/toSql.js +5 -0
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +4 -1
- package/lib/transform/db/assocsToQueries/transformExists.js +3 -10
- package/lib/transform/db/assocsToQueries/utils.js +0 -5
- package/lib/transform/db/cdsPersistence.js +17 -18
- package/lib/transform/db/expansion.js +179 -3
- package/lib/transform/db/flattening.js +16 -5
- package/lib/transform/db/rewriteCalculatedElements.js +79 -283
- package/lib/transform/effective/main.js +8 -1
- package/lib/transform/forOdata.js +1 -1
- package/lib/transform/forRelationalDB.js +21 -80
- package/lib/transform/localized.js +75 -127
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
- package/lib/transform/transformUtils.js +23 -21
- package/lib/transform/translateAssocsToJoins.js +7 -5
- package/lib/transform/tupleExpansion.js +16 -3
- package/package.json +3 -3
- package/doc/DeprecatedOptions_v2.md +0 -150
- package/doc/NameResolution.md +0 -837
- package/lib/transform/parseExpr.js +0 -415
|
@@ -37,6 +37,7 @@ const { getDefaultTypeLengths } = require('../render/utils/common');
|
|
|
37
37
|
const { featureFlags } = require('./featureFlags');
|
|
38
38
|
const { cloneCsnNonDict, cloneFullCsn } = require('../model/cloneCsn');
|
|
39
39
|
const { processSqlServices, createServiceDummy } = require('./db/processSqlServices');
|
|
40
|
+
const { expandWildcard } = require('./db/expansion');
|
|
40
41
|
|
|
41
42
|
// By default: Do not process non-entities/views
|
|
42
43
|
function forEachDefinition(csn, cb) {
|
|
@@ -44,68 +45,12 @@ function forEachDefinition(csn, cb) {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* The behavior is controlled by the following options:
|
|
50
|
-
* options = {
|
|
51
|
-
* sqlMapping // See the behavior of 'sqlMapping' in toHana, toSql and toRename
|
|
52
|
-
* }
|
|
53
|
-
* The result model will always have 'options.forHana' set, to indicate that these transformations have happened.
|
|
54
|
-
* The following transformations are made:
|
|
55
|
-
* - (000) Some primitive type names are mapped to HANA type names (e.g. DateTime => UTCDateTime,
|
|
56
|
-
* Date => LocalDate, ...).The primitive type 'UUID' is renamed to 'String' (see also 060 below).
|
|
57
|
-
* - (001) Add a temporal where condition to views where applicable before assoc2join
|
|
58
|
-
* - (010) (not for to.hdbcds with hdbcds names): Transform associations to joins
|
|
59
|
-
* - (015) Draft shadow entities are generated for entities/views annotated with '@odata.draft.enabled'.
|
|
60
|
-
* - (020) Check: in "plain" mode, quoted ids are not allowed.
|
|
61
|
-
* (a) check in namespace declarations
|
|
62
|
-
* (b) check in artifact/element definitions.
|
|
63
|
-
* - (040) Abstract entities and entities 'implemented in' something are ignored, as well
|
|
64
|
-
* as entities annotated with '@cds.persistence.skip' or '@cds.persistence.exists'.
|
|
65
|
-
* - (050) Checks on the hierarchical model (pre-flattening)
|
|
66
|
-
* array of, @cds.valid.from/to
|
|
67
|
-
* - (045) The query is stripped from entities that are annotated with '@cds.persistence.table',
|
|
68
|
-
* essentially converting views to entities.
|
|
69
|
-
* - (060) Users of primitive type 'UUID' (which is renamed to 'String' in 000) get length 36'.
|
|
70
|
-
* - (070) Default length N is supplied for strings if not specified.
|
|
71
|
-
* - (080) Annotation definitions are ignored (note that annotation assignments are filtered out by toCdl).
|
|
72
|
-
* - (090) Compositions become associations.
|
|
73
|
-
* - (100) 'masked' is ignored (a), and attribute 'localized' is removed (b)
|
|
74
|
-
* - (110) Actions and functions (bound or unbound) are ignored.
|
|
75
|
-
* - (120) (a) Services become contexts.
|
|
76
|
-
* - (130) (not for to.hdbcds with hdbcds names): Elements having structured types are flattened into
|
|
77
|
-
* multiple elements (using '_' or '.' as name separator, depending on 'sqlMapping').
|
|
78
|
-
* - (140) (not for to.hdbcds with hdbcds names): Managed associations get explicit ON-conditions, with
|
|
79
|
-
* generated foreign key elements (also using '_' or '.' as name separator, depending on 'sqlMapping').
|
|
80
|
-
* - (150) (a) Elements from inherited (included) entities are copied into the receiving entity
|
|
81
|
-
* (b) The 'include' property is removed from entities.
|
|
82
|
-
* - (160) Projections become views, with MIXINs for association elements (adding $projection where
|
|
83
|
-
* appropriate for ON-conditions).
|
|
84
|
-
* - (170) ON-conditions referring to '$self' are transformed to compare explicit keys instead.
|
|
85
|
-
* - (180) In projections and views, ...
|
|
86
|
-
* (a) association elements that are mixins must not be explicitly redirected
|
|
87
|
-
* (b) MIXINs are created for association elements in the select list that are not mixins by themselves.
|
|
88
|
-
* - (190) For all enum types, ...
|
|
89
|
-
* (a) enum constants in defaults are replaced by their values (assuming a matching enum as element type)
|
|
90
|
-
* (b) the enum-ness is stripped off (i.e. the enum type is replaced by its final base type).
|
|
91
|
-
* - (200) The 'key' property is removed from all elements of types.
|
|
92
|
-
* - (210) (not for to.hdbcds with hdbcds names): Managed associations in GROUP BY and ORDER BY are
|
|
93
|
-
* replaced by by their foreign key fields.
|
|
94
|
-
* - (220) Contexts that contain no artifacts or only ignored artifacts are ignored.
|
|
95
|
-
* - (230) (only for to.hdbcds with hdbcds names): The following are rejected in views
|
|
96
|
-
* (a) Structured elements
|
|
97
|
-
* (b) Managed association elements
|
|
98
|
-
* (c) Managed association entries in GROUP BY
|
|
99
|
-
* (d) Managed association entries in ORDER BY
|
|
100
|
-
* - (240) All artifacts (a), elements, foreign keys, parameters (b) that have a DB representation are annotated
|
|
101
|
-
* with their database name (as '@cds.persistence.name') according to the naming convention chosen
|
|
102
|
-
* in 'options.sqlMapping'.
|
|
103
|
-
* - (250) Remove name space definitions again (only in forRelationalDB). Maybe we can omit inserting namespace definitions
|
|
104
|
-
* completely (TODO)
|
|
48
|
+
* Transform the given `csn` into a CSN that has SQL/HANA related transformations applied,
|
|
49
|
+
* such as flattening, wildcard expansion, etc.
|
|
105
50
|
*
|
|
106
|
-
* @param {CSN.Model}
|
|
107
|
-
* @param {CSN.
|
|
108
|
-
* @param {object}
|
|
51
|
+
* @param {CSN.Model} csn
|
|
52
|
+
* @param {CSN.SqlOptions} options
|
|
53
|
+
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
|
|
109
54
|
*/
|
|
110
55
|
function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
111
56
|
// copy the model as we don't want to change the input model
|
|
@@ -148,6 +93,12 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
148
93
|
|
|
149
94
|
ensureColumnNames(csn, options, csnUtils);
|
|
150
95
|
|
|
96
|
+
forEachDefinition(csn, (def) => {
|
|
97
|
+
// TODO: Combine query traversal with ensureColumnNames()
|
|
98
|
+
if (def.query || def.projection)
|
|
99
|
+
traverseQuery(def.query || def, null, null, query => expandWildcard(query, csnUtils, options));
|
|
100
|
+
});
|
|
101
|
+
|
|
151
102
|
const dialect = options.sqlDialect;
|
|
152
103
|
const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
|
|
153
104
|
if (!doA2J)
|
|
@@ -185,8 +136,8 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
185
136
|
expandStructsInExpression({ drillRef: true });
|
|
186
137
|
|
|
187
138
|
forEachDefinition(csn, [
|
|
188
|
-
//
|
|
189
|
-
//
|
|
139
|
+
// Add a temporal where condition to views where applicable before assoc2join
|
|
140
|
+
// assoc2join eventually rewrites the table aliases
|
|
190
141
|
temporal.getViewDecorator(csn, messageFunctions, csnUtils, options),
|
|
191
142
|
// check unique constraints - further processing is done in rewriteUniqueConstraints
|
|
192
143
|
assertUnique.prepare(csn, options, messageFunctions),
|
|
@@ -242,7 +193,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
242
193
|
// With flattening errors, it makes little sense to continue.
|
|
243
194
|
throwWithAnyError();
|
|
244
195
|
|
|
245
|
-
// (010) If requested, translate associations to joins
|
|
246
196
|
if (doA2J)
|
|
247
197
|
handleAssocToJoins();
|
|
248
198
|
|
|
@@ -287,12 +237,12 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
287
237
|
|
|
288
238
|
|
|
289
239
|
forEachDefinition(csn, [
|
|
290
|
-
//
|
|
240
|
+
// Ignore entities and views that are abstract or implemented
|
|
291
241
|
// or carry the annotation cds.persistence.skip/exists
|
|
292
242
|
// These entities are not removed from the csn, but flagged as "to be ignored"
|
|
293
243
|
cdsPersistence.getAnnoProcessor(),
|
|
294
|
-
//
|
|
295
|
-
//
|
|
244
|
+
// Check @cds.valid.from/to only on entity
|
|
245
|
+
// Views are checked in (001), unbalanced valid.from/to's or mismatching origins
|
|
296
246
|
temporal.getAnnotationHandler(csn, options, pathDelimiter, messageFunctions),
|
|
297
247
|
]);
|
|
298
248
|
|
|
@@ -308,7 +258,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
308
258
|
}
|
|
309
259
|
|
|
310
260
|
{
|
|
311
|
-
//
|
|
261
|
+
// Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',
|
|
312
262
|
// and make them entities
|
|
313
263
|
const fns = [ cdsPersistence.getPersistenceTableProcessor(csn, options, messageFunctions) ];
|
|
314
264
|
// Allow using managed associations as steps in on-conditions to access their fks
|
|
@@ -338,7 +288,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
338
288
|
rejectManagedAssociationsAndStructuresForHdbcdsNames.call(that, definition, path);
|
|
339
289
|
}
|
|
340
290
|
},
|
|
341
|
-
//
|
|
291
|
+
// Transform '$self' in backlink associations to appropriate key comparisons
|
|
342
292
|
// Must happen before draft processing because the artificial ON-conditions in generated
|
|
343
293
|
// draft shadow entities have crooked '_artifact' links, confusing the backlink processing.
|
|
344
294
|
// But it must also happen after flattenForeignKeys has been called for all artifacts,
|
|
@@ -371,7 +321,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
371
321
|
const findAndMarkSqlServiceArtifacts = options.sqlDialect === 'hana' && options.src === 'hdi' && (csn.meta?.[featureFlags]?.$sqlService || csn.meta?.[featureFlags]?.$dummyService || csn.meta?.[featureFlags]?.$dataProductService) ? processSqlServices(csn, options) : () => {};
|
|
372
322
|
|
|
373
323
|
// Apply view-specific transformations
|
|
374
|
-
//
|
|
324
|
+
// Projections now finally become views
|
|
375
325
|
// Replace managed association in group/order by with foreign keys
|
|
376
326
|
const transformEntityOrViewPass2 = getViewTransformer(csn, options, messageFunctions);
|
|
377
327
|
forEachDefinition(csn, [ (artifact, artifactName) => {
|
|
@@ -382,7 +332,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
382
332
|
|
|
383
333
|
if (!doA2J) {
|
|
384
334
|
forEachDefinition(csn, [
|
|
385
|
-
//
|
|
335
|
+
// Strip 'key' property from type elements
|
|
386
336
|
removeKeyPropInType,
|
|
387
337
|
(artifact, artifactName) => {
|
|
388
338
|
if (artifact.kind === 'type') {
|
|
@@ -697,15 +647,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
697
647
|
// Length/Precision/Scale is done in addDefaultTypeFacets
|
|
698
648
|
}
|
|
699
649
|
|
|
700
|
-
// If 'obj' has final type 'cds.UUID' (renamed to String in 000), set its length to 36.
|
|
701
|
-
// function setLengthForFormerUuid(obj) {
|
|
702
|
-
// if (!obj || !obj.type)
|
|
703
|
-
// return;
|
|
704
|
-
// if (obj.type === 'cds.UUID' && !obj.length) {
|
|
705
|
-
// obj.length = 36;
|
|
706
|
-
// }
|
|
707
|
-
// }
|
|
708
|
-
|
|
709
650
|
/**
|
|
710
651
|
* Check that required actual parameters on 'node.type' are set, that their values are in the correct range etc.
|
|
711
652
|
|
|
@@ -58,23 +58,7 @@ const annoPersistenceSkip = '@cds.persistence.skip';
|
|
|
58
58
|
* @param {CSN.Model} csn
|
|
59
59
|
* Input CSN model. Should not have existing convenience views.
|
|
60
60
|
*
|
|
61
|
-
* @param {
|
|
62
|
-
* CSN options. Only few options are used, see below for important ones.
|
|
63
|
-
* Options such as `testMode` or `testSortCsn` can also be set.
|
|
64
|
-
*
|
|
65
|
-
* @param {string} [options.localizedLanguageFallback]
|
|
66
|
-
* Valid values (if set): 'none', 'coalesce' (default)
|
|
67
|
-
* Whether to use a `coalesce()` function when selecting from `.texts` entities.
|
|
68
|
-
* If not set, untranslated strings may not return any value. If 'coalesce'
|
|
69
|
-
* is used, it will fall back to the original string.
|
|
70
|
-
*
|
|
71
|
-
* @param {boolean} [options.localizedWithoutCoalesce]
|
|
72
|
-
* Deprecated version of localizedLanguageFallback. Do not use.
|
|
73
|
-
*
|
|
74
|
-
* @param {boolean} [options.fewerLocalizedViews]
|
|
75
|
-
* Default: true
|
|
76
|
-
*
|
|
77
|
-
* @param {boolean} [options.testMode]
|
|
61
|
+
* @param {CSN.Options} options
|
|
78
62
|
*
|
|
79
63
|
* @param {object} config
|
|
80
64
|
* Configuration for creating convenience views. Non-user visible options.
|
|
@@ -156,14 +140,14 @@ function _addLocalizationViews(csn, options, config) {
|
|
|
156
140
|
|
|
157
141
|
/**
|
|
158
142
|
* Add a localized convenience view for the given artifact.
|
|
159
|
-
* Can either be an entity or view. `
|
|
143
|
+
* Can either be an entity or view. `localizedElements` are the elements which
|
|
160
144
|
* are needed for creating a horizontal convenience view, i.e. only required
|
|
161
145
|
* for entities.
|
|
162
146
|
*
|
|
163
147
|
* @param {string} artName
|
|
164
|
-
* @param {string[]} [
|
|
148
|
+
* @param {string[]} [localizedElements=[]]
|
|
165
149
|
*/
|
|
166
|
-
function addLocalizedView( artName,
|
|
150
|
+
function addLocalizedView( artName, localizedElements = [] ) {
|
|
167
151
|
const art = csn.definitions[artName];
|
|
168
152
|
const artPath = [ 'definitions', artName ];
|
|
169
153
|
const viewName = `localized.${ artName }`;
|
|
@@ -183,32 +167,32 @@ function _addLocalizationViews(csn, options, config) {
|
|
|
183
167
|
if (art.query || art.projection)
|
|
184
168
|
view = createLocalizedViewForView(art, viewName);
|
|
185
169
|
else
|
|
186
|
-
view = createLocalizedViewForEntity(art, artName, viewName,
|
|
170
|
+
view = createLocalizedViewForEntity(art, artName, viewName, localizedElements);
|
|
187
171
|
|
|
188
172
|
copyPersistenceAnnotations(view, art);
|
|
189
173
|
csn.definitions[viewName] = view;
|
|
190
174
|
}
|
|
191
175
|
|
|
192
176
|
/**
|
|
193
|
-
* Create a localized data view for the given entity `art` with `
|
|
177
|
+
* Create a localized data view for the given entity `art` with `localizedElements`.
|
|
194
178
|
* In JOIN mode the FROM query is rewritten to remove associations and the
|
|
195
179
|
* columns are expanded.
|
|
196
180
|
*
|
|
197
181
|
* @param {CSN.Definition} entity
|
|
198
182
|
* @param {string} entityName
|
|
199
183
|
* @param {string} viewName Name of the localized view.
|
|
200
|
-
* @param {string[]} [
|
|
184
|
+
* @param {string[]} [localizedElements]
|
|
201
185
|
* @returns {CSN.View}
|
|
202
186
|
*/
|
|
203
|
-
function createLocalizedViewForEntity( entity, entityName, viewName,
|
|
187
|
+
function createLocalizedViewForEntity( entity, entityName, viewName, localizedElements = [] ) {
|
|
204
188
|
// Only use joins if requested and text elements are provided.
|
|
205
|
-
const shouldUseJoin = useJoins && !!
|
|
189
|
+
const shouldUseJoin = useJoins && !!localizedElements.length;
|
|
206
190
|
const columns = [ ];
|
|
207
191
|
|
|
208
192
|
const convenienceView = {
|
|
209
193
|
'@odata.draft.enabled': false,
|
|
210
194
|
kind: 'entity',
|
|
211
|
-
query: {
|
|
195
|
+
query: {
|
|
212
196
|
SELECT: {
|
|
213
197
|
from: createFromClauseForEntity(),
|
|
214
198
|
columns,
|
|
@@ -222,20 +206,12 @@ function _addLocalizationViews(csn, options, config) {
|
|
|
222
206
|
|
|
223
207
|
if (shouldUseJoin)
|
|
224
208
|
// Expand elements; (variant 1)
|
|
225
|
-
columns.push( ...columnsForEntityWithExcludeList( entity, 'L_0',
|
|
209
|
+
columns.push( ...columnsForEntityWithExcludeList( entity, 'L_0', localizedElements ) );
|
|
226
210
|
else
|
|
227
211
|
columns.push( '*' ); // (variant 2)
|
|
228
212
|
|
|
229
|
-
for (const originalElement of
|
|
230
|
-
|
|
231
|
-
// Note: $key is used by forRelationalDB.js to indicate that this element was a key in the original,
|
|
232
|
-
// user's entity. Keys may have been changed by the backends (e.g. by `@cds.valid.key`)
|
|
233
|
-
if (!elem.key && !elem.$key)
|
|
234
|
-
columns.push( createColumnLocalizedElement( originalElement, shouldUseJoin ) );
|
|
235
|
-
else if (shouldUseJoin)
|
|
236
|
-
// In JOIN mode we also want to add keys.
|
|
237
|
-
columns.push( createColumnRef( [ 'L_0', originalElement ] ));
|
|
238
|
-
|
|
213
|
+
for (const originalElement of localizedElements) {
|
|
214
|
+
columns.push( createColumnLocalizedElement( originalElement, shouldUseJoin ) );
|
|
239
215
|
addCoreComputedIfNecessary(convenienceView.elements, originalElement);
|
|
240
216
|
}
|
|
241
217
|
|
|
@@ -254,22 +230,52 @@ function _addLocalizationViews(csn, options, config) {
|
|
|
254
230
|
on: [],
|
|
255
231
|
};
|
|
256
232
|
|
|
257
|
-
|
|
258
|
-
const elem = entity.elements[originalElement];
|
|
259
|
-
if (elem.key || elem.$key) {
|
|
260
|
-
from.on.push( createColumnRef( [ 'localized_1', originalElement ] ));
|
|
261
|
-
from.on.push( '=' );
|
|
262
|
-
from.on.push( createColumnRef( [ 'L_0', originalElement ] ));
|
|
263
|
-
from.on.push( 'and' );
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
from.on.push( createColumnRef( [ 'localized_1', 'locale' ] ) );
|
|
268
|
-
from.on.push( '=' );
|
|
269
|
-
from.on.push( createColumnRef( [ '$user', 'locale' ] ) );
|
|
233
|
+
from.on.push(...createJoinConditionFromLocaleElement());
|
|
270
234
|
|
|
271
235
|
return from;
|
|
272
236
|
}
|
|
237
|
+
|
|
238
|
+
function createJoinConditionFromLocaleElement() {
|
|
239
|
+
const targetAlias = 'localized_1';
|
|
240
|
+
const sourceAlias = 'L_0';
|
|
241
|
+
return adaptExpr(entity.elements.localized.on);
|
|
242
|
+
|
|
243
|
+
function adaptExpr(expr) {
|
|
244
|
+
// We only support a few specific ON-conditions, not generic expressions.
|
|
245
|
+
// In case of unsupported ON-conditions, we emit an error.
|
|
246
|
+
const res = expr.map((x) => {
|
|
247
|
+
if (!x || typeof x === 'string')
|
|
248
|
+
return x;
|
|
249
|
+
if (x.xpr)
|
|
250
|
+
return { xpr: adaptExpr(x.xpr) };
|
|
251
|
+
if (x.ref && !x.ref.some(ref => ref.args || ref.where))
|
|
252
|
+
return adaptRef(x);
|
|
253
|
+
|
|
254
|
+
messageFunctions.error(
|
|
255
|
+
'def-invalid-localized',
|
|
256
|
+
[ 'definitions', entityName, 'elements', 'localized', 'on' ],
|
|
257
|
+
{ name: 'localized', alias: entityName },
|
|
258
|
+
'Element $(NAME) of entity $(ALIAS) does not have a supported ON-condition'
|
|
259
|
+
);
|
|
260
|
+
return x;
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// the `localized` association does not contain the `tenant` element, so we need to add it here
|
|
264
|
+
const addTenantCol = options.tenantDiscriminator && entity.elements.tenant?.key;
|
|
265
|
+
if (addTenantCol)
|
|
266
|
+
return [ { ref: [ targetAlias, 'tenant' ] }, '=', { ref: [ sourceAlias, 'tenant' ] }, 'AND', { xpr: [ ...res ] } ];
|
|
267
|
+
|
|
268
|
+
return res;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function adaptRef(expr) {
|
|
272
|
+
if (expr.ref[0].charAt(0) === '$') // variable
|
|
273
|
+
return { ref: [ ...expr.ref ] };
|
|
274
|
+
if (expr.ref[0] === 'localized') // target side
|
|
275
|
+
return { ref: [ targetAlias, ...expr.ref.slice(1) ] };
|
|
276
|
+
return { ref: [ sourceAlias, ...expr.ref ] }; // source side
|
|
277
|
+
}
|
|
278
|
+
}
|
|
273
279
|
}
|
|
274
280
|
|
|
275
281
|
/**
|
|
@@ -316,7 +322,6 @@ function _addLocalizationViews(csn, options, config) {
|
|
|
316
322
|
if (noCoalesce)
|
|
317
323
|
return createColumnRef( [ ...localizedNames, elementName ], elementName );
|
|
318
324
|
|
|
319
|
-
|
|
320
325
|
return {
|
|
321
326
|
func: 'coalesce',
|
|
322
327
|
args: [
|
|
@@ -357,8 +362,8 @@ function _addLocalizationViews(csn, options, config) {
|
|
|
357
362
|
|
|
358
363
|
/**
|
|
359
364
|
* Returns all text element names for a definition `<artName>` if its texts entity
|
|
360
|
-
* exists and `<artName>` has localized fields. Otherwise `null` is returned.
|
|
361
|
-
* Text elements are localized elements
|
|
365
|
+
* exists and `<artName>` has localized fields. Otherwise, `null` is returned.
|
|
366
|
+
* Text elements are non-key localized elements.
|
|
362
367
|
*
|
|
363
368
|
* @param {string} artName Artifact name
|
|
364
369
|
* @return {string[] | null}
|
|
@@ -367,49 +372,31 @@ function _addLocalizationViews(csn, options, config) {
|
|
|
367
372
|
const art = csn.definitions[artName];
|
|
368
373
|
const artPath = [ 'definitions', artName ];
|
|
369
374
|
|
|
370
|
-
|
|
371
|
-
let textElements = [];
|
|
375
|
+
const localizedElements = [];
|
|
372
376
|
|
|
373
377
|
forEachGeneric(art, 'elements', (elem, elemName, _prop) => {
|
|
374
378
|
if (elem.$ignore) // from SAP HANA backend
|
|
375
379
|
return;
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
keyCount += 1;
|
|
379
|
-
|
|
380
|
-
if (elem.key || elem.$key || elem.localized)
|
|
381
|
-
textElements.push( elemName );
|
|
380
|
+
if (elem.localized && !elem.key && !elem.$key)
|
|
381
|
+
localizedElements.push( elemName );
|
|
382
382
|
}, artPath);
|
|
383
383
|
|
|
384
|
-
if (
|
|
384
|
+
if (!localizedElements.length) {
|
|
385
385
|
// Nothing to do: no localized fields or all localized fields are keys
|
|
386
386
|
return null;
|
|
387
|
-
|
|
388
|
-
if (!
|
|
389
|
-
messageFunctions.info(
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
387
|
+
}
|
|
388
|
+
if (!art.elements.localized) {
|
|
389
|
+
messageFunctions.info('def-expected-localized', artPath, { '#': 'missing', name: artName, alias: 'localized' });
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
if (!art.elements.localized.target) {
|
|
393
|
+
messageFunctions.info('def-expected-localized', artPath, { '#': 'non-assoc', name: artName, alias: 'localized' });
|
|
393
394
|
return null;
|
|
394
395
|
}
|
|
395
396
|
|
|
396
397
|
const textsName = textsEntityName( artName );
|
|
397
398
|
const textsEntity = csn.definitions[textsName];
|
|
398
399
|
|
|
399
|
-
if (!textsEntity) {
|
|
400
|
-
messageFunctions.info(
|
|
401
|
-
null, artPath, { name: artName },
|
|
402
|
-
'Skipped creation of convenience view for $(NAME) because its texts entity could not be found'
|
|
403
|
-
);
|
|
404
|
-
return null;
|
|
405
|
-
}
|
|
406
|
-
if (!isValidTextsEntity( textsEntity )) {
|
|
407
|
-
messageFunctions.info(
|
|
408
|
-
null, [ 'definitions', textsName ], { name: artName },
|
|
409
|
-
'Skipped creation of convenience view for $(NAME) because its texts entity does not appear to be valid'
|
|
410
|
-
);
|
|
411
|
-
return null;
|
|
412
|
-
}
|
|
413
400
|
if (!art[annoPersistenceSkip] && textsEntity[annoPersistenceSkip]) {
|
|
414
401
|
messageFunctions.message(
|
|
415
402
|
'anno-unexpected-localized-skip', artPath,
|
|
@@ -418,22 +405,10 @@ function _addLocalizationViews(csn, options, config) {
|
|
|
418
405
|
return null;
|
|
419
406
|
}
|
|
420
407
|
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
textElements = textElements.filter((elemName) => {
|
|
426
|
-
const hasElement = !!textsEntity.elements[elemName];
|
|
427
|
-
if (!hasElement && (art.elements[elemName].key || art.elements[elemName].$key))
|
|
428
|
-
keyCount--;
|
|
429
|
-
return hasElement;
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
if (textElements.length <= keyCount || keyCount <= 0)
|
|
433
|
-
// Repeat the check already used above as the number of keys may have changed.
|
|
434
|
-
return null;
|
|
435
|
-
|
|
436
|
-
return textElements;
|
|
408
|
+
// Due to recompilation / flattening, properties may have been propagated from "type-of".
|
|
409
|
+
// That means we have localized elements with no corresponding element in the texts-entity.
|
|
410
|
+
// Hence, we simply filter here.
|
|
411
|
+
return localizedElements.filter(elemName => textsEntity.elements[elemName]);
|
|
437
412
|
}
|
|
438
413
|
|
|
439
414
|
/**
|
|
@@ -646,8 +621,8 @@ function _addLocalizationViews(csn, options, config) {
|
|
|
646
621
|
* @param {string} artName
|
|
647
622
|
*/
|
|
648
623
|
function textsEntityName(artName) {
|
|
649
|
-
// We can assume that the element exists.
|
|
650
|
-
return csn.definitions[artName].elements.
|
|
624
|
+
// We can assume that the element exists.
|
|
625
|
+
return csn.definitions[artName].elements.localized.target;
|
|
651
626
|
}
|
|
652
627
|
|
|
653
628
|
/**
|
|
@@ -808,33 +783,6 @@ function checkExistingLocalizationViews(csn, options, messageFunctions) {
|
|
|
808
783
|
return hasExistingViews || hasNonViews;
|
|
809
784
|
}
|
|
810
785
|
|
|
811
|
-
/**
|
|
812
|
-
* Returns true if the given entity appears to be a valid texts entity.
|
|
813
|
-
*
|
|
814
|
-
* @param {CSN.Artifact} entity
|
|
815
|
-
*/
|
|
816
|
-
function isValidTextsEntity(entity) {
|
|
817
|
-
if (!entity)
|
|
818
|
-
return false;
|
|
819
|
-
const requiredTextsProps = [ 'locale' ];
|
|
820
|
-
return requiredTextsProps.some( prop => !!entity.elements[prop]);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
/**
|
|
824
|
-
* Returns true if the localized entity has elements that are generated by
|
|
825
|
-
* the core-compiler. If elements are missing but the entity is localized
|
|
826
|
-
* then the pre-processing by the core-compiler was not done.
|
|
827
|
-
*
|
|
828
|
-
* @param {CSN.Artifact} entity
|
|
829
|
-
*/
|
|
830
|
-
function isEntityPreprocessed(entity) {
|
|
831
|
-
if (!entity)
|
|
832
|
-
return false;
|
|
833
|
-
if (!entity.elements.localized)
|
|
834
|
-
return false;
|
|
835
|
-
return entity.elements.texts && entity.elements.texts.target;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
786
|
/**
|
|
839
787
|
* @param {string} name
|
|
840
788
|
* @returns {boolean}
|