@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.
Files changed (62) hide show
  1. package/CHANGELOG.md +101 -3
  2. package/LICENSE +32 -0
  3. package/README.md +14 -2
  4. package/bin/cdsse.js +0 -3
  5. package/doc/CHANGELOG_BETA.md +1 -1
  6. package/doc/CHANGELOG_DEPRECATED.md +1 -1
  7. package/lib/base/message-registry.js +9 -2
  8. package/lib/base/messages.js +1 -1
  9. package/lib/base/model.js +2 -0
  10. package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
  11. package/lib/checks/existsMustEndInAssoc.js +1 -1
  12. package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
  13. package/lib/checks/validator.js +4 -2
  14. package/lib/compiler/assert-consistency.js +3 -2
  15. package/lib/compiler/builtins.js +5 -6
  16. package/lib/compiler/checks.js +37 -26
  17. package/lib/compiler/define.js +1 -1
  18. package/lib/compiler/extend.js +39 -50
  19. package/lib/compiler/finalize-parse-cdl.js +1 -1
  20. package/lib/compiler/lsp-api.js +1 -1
  21. package/lib/compiler/populate.js +2 -2
  22. package/lib/compiler/propagator.js +29 -6
  23. package/lib/compiler/resolve.js +13 -3
  24. package/lib/compiler/shared.js +157 -133
  25. package/lib/compiler/tweak-assocs.js +87 -29
  26. package/lib/compiler/xpr-rewrite.js +164 -160
  27. package/lib/edm/annotations/edmJson.js +206 -37
  28. package/lib/edm/csn2edm.js +13 -0
  29. package/lib/edm/edmUtils.js +2 -2
  30. package/lib/gen/BaseParser.js +106 -72
  31. package/lib/gen/CdlGrammar.checksum +1 -1
  32. package/lib/gen/CdlParser.js +1501 -1509
  33. package/lib/json/to-csn.js +8 -5
  34. package/lib/language/genericAntlrParser.js +0 -0
  35. package/lib/main.js +19 -16
  36. package/lib/model/csnRefs.js +589 -521
  37. package/lib/model/csnUtils.js +8 -5
  38. package/lib/model/enrichCsn.js +1 -0
  39. package/lib/parsers/AstBuildingParser.js +73 -28
  40. package/lib/render/toCdl.js +2 -1
  41. package/lib/render/toHdbcds.js +6 -3
  42. package/lib/render/toSql.js +5 -0
  43. package/lib/transform/db/applyTransformations.js +1 -1
  44. package/lib/transform/db/assertUnique.js +4 -1
  45. package/lib/transform/db/assocsToQueries/transformExists.js +3 -10
  46. package/lib/transform/db/assocsToQueries/utils.js +0 -5
  47. package/lib/transform/db/cdsPersistence.js +17 -18
  48. package/lib/transform/db/expansion.js +179 -3
  49. package/lib/transform/db/flattening.js +16 -5
  50. package/lib/transform/db/rewriteCalculatedElements.js +79 -283
  51. package/lib/transform/effective/main.js +8 -1
  52. package/lib/transform/forOdata.js +1 -1
  53. package/lib/transform/forRelationalDB.js +21 -80
  54. package/lib/transform/localized.js +75 -127
  55. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
  56. package/lib/transform/transformUtils.js +23 -21
  57. package/lib/transform/translateAssocsToJoins.js +7 -5
  58. package/lib/transform/tupleExpansion.js +16 -3
  59. package/package.json +3 -3
  60. package/doc/DeprecatedOptions_v2.md +0 -150
  61. package/doc/NameResolution.md +0 -837
  62. 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
- * Return a copy of the compact CSN model with a number of transformations made for rendering
48
- * in HANA CDS style, used by 'toHana', toSql' and 'toRename'.
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} csn
107
- * @param {CSN.Options} options
108
- * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
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
- // (001) Add a temporal where condition to views where applicable before assoc2join
189
- // assoc2join eventually rewrites the table aliases
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
- // (040) Ignore entities and views that are abstract or implemented
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
- // (050) Check @cds.valid.from/to only on entity
295
- // Views are checked in (001), unbalanced valid.from/to's or mismatching origins
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
- // (045) Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',
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
- // (170) Transform '$self' in backlink associations to appropriate key comparisons
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
- // (160) Projections now finally become views
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
- // (200) Strip 'key' property from type elements
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 {object} options
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. `textElements` are the elements which
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[]} [textElements=[]]
148
+ * @param {string[]} [localizedElements=[]]
165
149
  */
166
- function addLocalizedView( artName, textElements = [] ) {
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, textElements);
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 `textElements`.
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[]} [textElements]
184
+ * @param {string[]} [localizedElements]
201
185
  * @returns {CSN.View}
202
186
  */
203
- function createLocalizedViewForEntity( entity, entityName, viewName, textElements = [] ) {
187
+ function createLocalizedViewForEntity( entity, entityName, viewName, localizedElements = [] ) {
204
188
  // Only use joins if requested and text elements are provided.
205
- const shouldUseJoin = useJoins && !!textElements.length;
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: { // TODO: Use projection
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', textElements ) );
209
+ columns.push( ...columnsForEntityWithExcludeList( entity, 'L_0', localizedElements ) );
226
210
  else
227
211
  columns.push( '*' ); // (variant 2)
228
212
 
229
- for (const originalElement of textElements) {
230
- const elem = entity.elements[originalElement];
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
- for (const originalElement of textElements) {
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 as well as keys.
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
- let keyCount = 0;
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
- if (elem.key || elem.$key)
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 (textElements.length <= keyCount || keyCount <= 0)
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 (!isEntityPreprocessed( art )) {
389
- messageFunctions.info(
390
- null, artPath, { name: artName },
391
- 'Skipped creation of convenience view for $(NAME) because the artifact is missing localization elements'
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
- // There may be keys in the original artifact that were added by the core compiler,
422
- // for example elements that are marked @cds.valid.from.
423
- // These keys are not present in the texts entity generated by the compiler.
424
- // So if we don't filter them out, we may generate invalid SQL.
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. This is checked in isEntityPreprocessed().
650
- return csn.definitions[artName].elements.texts.target;
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}