@sap/cds-compiler 2.5.2 → 2.11.0

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 (102) hide show
  1. package/CHANGELOG.md +235 -9
  2. package/bin/cdsc.js +44 -27
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +37 -3
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +37 -123
  7. package/lib/api/options.js +27 -15
  8. package/lib/api/validate.js +34 -9
  9. package/lib/backends.js +9 -89
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +73 -11
  13. package/lib/base/messages.js +86 -30
  14. package/lib/base/model.js +6 -6
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/defaultValues.js +27 -2
  17. package/lib/checks/elements.js +1 -6
  18. package/lib/checks/foreignKeys.js +0 -6
  19. package/lib/checks/managedWithoutKeys.js +17 -0
  20. package/lib/checks/nonexpandableStructured.js +38 -0
  21. package/lib/checks/onConditions.js +9 -45
  22. package/lib/checks/queryNoDbArtifacts.js +25 -7
  23. package/lib/checks/selectItems.js +29 -2
  24. package/lib/checks/types.js +26 -2
  25. package/lib/checks/unknownMagic.js +41 -0
  26. package/lib/checks/utils.js +61 -0
  27. package/lib/checks/validator.js +60 -7
  28. package/lib/compiler/assert-consistency.js +23 -7
  29. package/lib/compiler/base.js +65 -0
  30. package/lib/compiler/builtins.js +30 -1
  31. package/lib/compiler/checks.js +8 -5
  32. package/lib/compiler/definer.js +157 -133
  33. package/lib/compiler/index.js +89 -31
  34. package/lib/compiler/propagator.js +5 -2
  35. package/lib/compiler/resolver.js +375 -185
  36. package/lib/compiler/shared.js +49 -202
  37. package/lib/compiler/utils.js +173 -0
  38. package/lib/edm/annotations/genericTranslation.js +183 -187
  39. package/lib/edm/csn2edm.js +104 -108
  40. package/lib/edm/edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +388 -146
  42. package/lib/edm/edmUtils.js +104 -34
  43. package/lib/gen/Dictionary.json +22 -0
  44. package/lib/gen/language.checksum +1 -1
  45. package/lib/gen/language.interp +28 -1
  46. package/lib/gen/language.tokens +79 -69
  47. package/lib/gen/languageLexer.interp +28 -1
  48. package/lib/gen/languageLexer.js +879 -805
  49. package/lib/gen/languageLexer.tokens +71 -62
  50. package/lib/gen/languageParser.js +5330 -4300
  51. package/lib/json/from-csn.js +110 -52
  52. package/lib/json/to-csn.js +434 -120
  53. package/lib/language/antlrParser.js +15 -3
  54. package/lib/language/errorStrategy.js +1 -0
  55. package/lib/language/genericAntlrParser.js +93 -26
  56. package/lib/language/language.g4 +172 -31
  57. package/lib/main.d.ts +216 -19
  58. package/lib/main.js +32 -7
  59. package/lib/model/api.js +78 -0
  60. package/lib/model/csnRefs.js +413 -149
  61. package/lib/model/csnUtils.js +286 -75
  62. package/lib/model/enrichCsn.js +50 -6
  63. package/lib/model/revealInternalProperties.js +22 -5
  64. package/lib/modelCompare/compare.js +39 -21
  65. package/lib/optionProcessor.js +35 -18
  66. package/lib/render/.eslintrc.json +4 -1
  67. package/lib/render/DuplicateChecker.js +9 -6
  68. package/lib/render/toCdl.js +121 -36
  69. package/lib/render/toHdbcds.js +148 -98
  70. package/lib/render/toSql.js +114 -43
  71. package/lib/render/utils/common.js +8 -13
  72. package/lib/render/utils/sql.js +3 -3
  73. package/lib/sql-identifier.js +6 -1
  74. package/lib/transform/db/assertUnique.js +5 -6
  75. package/lib/transform/db/constraints.js +281 -106
  76. package/lib/transform/db/draft.js +11 -8
  77. package/lib/transform/db/expansion.js +584 -0
  78. package/lib/transform/db/flattening.js +341 -0
  79. package/lib/transform/db/groupByOrderBy.js +2 -2
  80. package/lib/transform/db/transformExists.js +345 -65
  81. package/lib/transform/db/views.js +438 -0
  82. package/lib/transform/forHanaNew.js +131 -793
  83. package/lib/transform/forOdataNew.js +30 -24
  84. package/lib/transform/localized.js +39 -10
  85. package/lib/transform/odata/attachPath.js +19 -4
  86. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  87. package/lib/transform/odata/referenceFlattener.js +60 -39
  88. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  89. package/lib/transform/odata/structuralPath.js +72 -0
  90. package/lib/transform/odata/structureFlattener.js +19 -18
  91. package/lib/transform/odata/typesExposure.js +22 -12
  92. package/lib/transform/transformUtilsNew.js +144 -78
  93. package/lib/transform/translateAssocsToJoins.js +22 -27
  94. package/lib/transform/universalCsnEnricher.js +67 -0
  95. package/lib/utils/file.js +5 -14
  96. package/lib/utils/moduleResolve.js +6 -8
  97. package/lib/utils/term.js +65 -42
  98. package/lib/utils/timetrace.js +48 -26
  99. package/package.json +1 -1
  100. package/lib/json/walker.js +0 -26
  101. package/lib/transform/sqlite +0 -0
  102. package/lib/utils/string.js +0 -17
@@ -6,7 +6,7 @@ const { getUtils, cloneCsn, forEachGeneric,
6
6
  forEachMemberRecursively, forEachRef,
7
7
  forAllQueries, forAllElements, hasAnnotationValue, getArtifactDatabaseNameOf,
8
8
  getElementDatabaseNameOf, isBuiltinType, applyTransformations,
9
- isPersistedOnDatabase, getNormalizedQuery, isAspect,
9
+ isPersistedOnDatabase, getNormalizedQuery, isAspect, walkCsnPath,
10
10
  } = require('../model/csnUtils');
11
11
  const { makeMessageFunction } = require('../base/messages');
12
12
  const transformUtils = require('./transformUtilsNew');
@@ -15,15 +15,18 @@ const { csnRefs, pathId, implicitAs } = require('../model/csnRefs');
15
15
  const { checkCSNVersion } = require('../json/csnVersion');
16
16
  const validate = require('../checks/validator');
17
17
  const { addLocalizationViewsWithJoins, addLocalizationViews } = require('../transform/localized');
18
- const timetrace = require('../utils/timetrace');
18
+ const { timetrace } = require('../utils/timetrace');
19
19
  const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
20
20
  const { createDict } = require('../utils/objectUtils');
21
21
  const handleExists = require('./db/transformExists');
22
- const { usesMixinAssociation, getMixinAssocOfQueryIfPublished } = require('./db/helpers');
23
22
  const replaceAssociationsInGroupByOrderBy = require('./db/groupByOrderBy');
24
23
  const _forEachDefinition = require('../model/csnUtils').forEachDefinition;
24
+ const flattening = require('./db/flattening');
25
+ const expansion = require('./db/expansion');
25
26
  const assertUnique = require('./db/assertUnique');
26
27
  const generateDrafts = require('./db/draft');
28
+ const enrichUniversalCsn = require('./universalCsnEnricher');
29
+ const { getViewTransformer } = require('./db/views');
27
30
 
28
31
  // By default: Do not process non-entities/views
29
32
  function forEachDefinition(csn, cb) {
@@ -36,12 +39,6 @@ function forEachDefinition(csn, cb) {
36
39
  * The behavior is controlled by the following options:
37
40
  * options = {
38
41
  * forHana.names // See the behavior of 'names' in toHana, toSql and toRename
39
- * forHana.keepNamespaces // Do not transform namespaces to contexts (to be used for
40
- * // producing HANA-CDS compatible names with 'toHana', 'toSql' ...)
41
- * forHana.keepStructsAssocs // Do not flatten structs, do not convert managed assocs to
42
- * // unmanaged ones, do not convert assocs to joins (to be used
43
- * // for rendering strictly HANA-CDS compatible CDS source with
44
- * // 'toHana')
45
42
  * forHana.alwaysResolveDerivedTypes // Always resolve derived type chains (by default, this is only
46
43
  * // done for 'quoted' names). FIXME: Should always be done in general.
47
44
  * }
@@ -50,12 +47,11 @@ function forEachDefinition(csn, cb) {
50
47
  * - (000) Some primitive type names are mapped to HANA type names (e.g. DateTime => UTCDateTime,
51
48
  * Date => LocalDate, ...).The primitive type 'UUID' is renamed to 'String' (see also 060 below).
52
49
  * - (001) Add a temporal where condition to views where applicable before assoc2join
53
- * - (010) (not for 'keepStructsAssocs'): Transform associations to joins
50
+ * - (010) (not for to.hdbcds with hdbcds names): Transform associations to joins
54
51
  * - (015) Draft shadow entities are generated for entities/views annotated with '@odata.draft.enabled'.
55
52
  * - (020) Check: in "plain" mode, quoted ids are not allowed.
56
53
  * (a) check in namespace declarations
57
54
  * (b) check in artifact/element definitions.
58
- * - (030) For all elements, derived types are replaced by their final base type.
59
55
  * - (040) Abstract entities and entities 'implemented in' something are ignored, as well
60
56
  * as entities annotated with '@cds.persistence.skip' or '@cds.persistence.exists'.
61
57
  * - (050) Checks on the hierarchical model (pre-flattening)
@@ -69,10 +65,9 @@ function forEachDefinition(csn, cb) {
69
65
  * - (100) 'masked' is ignored (a), and attribute 'localized' is removed (b)
70
66
  * - (110) Actions and functions (bound or unbound) are ignored.
71
67
  * - (120) (a) Services become contexts.
72
- * (b) (not for 'keepNamespaces'): Namespaces become contexts.
73
- * - (130) (not for 'keepStructsAssocs'): Elements having structured types are flattened into
68
+ * - (130) (not for to.hdbcds with hdbcds names): Elements having structured types are flattened into
74
69
  * multiple elements (using '_' or '.' as name separator, depending on 'forHana.names').
75
- * - (140) (not for 'keepStructsAssocs'): Managed associations get explicit ON-conditions, with
70
+ * - (140) (not for to.hdbcds with hdbcds names): Managed associations get explicit ON-conditions, with
76
71
  * generated foreign key elements (also using '_' or '.' as name separator, depending on 'forHana.names').
77
72
  * - (150) (a) Elements from inherited (included) entities are copied into the receiving entity
78
73
  * (b) The 'include' property is removed from entities.
@@ -86,10 +81,10 @@ function forEachDefinition(csn, cb) {
86
81
  * (a) enum constants in defaults are replaced by their values (assuming a matching enum as element type)
87
82
  * (b) the enum-ness is stripped off (i.e. the enum type is replaced by its final base type).
88
83
  * - (200) The 'key' property is removed from all elements of types.
89
- * - (210) (not for 'keepStructsAssocs'): Managed associations in GROUP BY and ORDER BY are
84
+ * - (210) (not for to.hdbcds with hdbcds names): Managed associations in GROUP BY and ORDER BY are
90
85
  * replaced by by their foreign key fields.
91
86
  * - (220) Contexts that contain no artifacts or only ignored artifacts are ignored.
92
- * - (230) (only for 'keepStructsAssocs'): The following are rejected in views
87
+ * - (230) (only for to.hdbcds with hdbcds names): The following are rejected in views
93
88
  * (a) Structured elements
94
89
  * (b) Managed association elements
95
90
  * (c) Managed association entries in GROUP BY
@@ -105,7 +100,6 @@ function forEachDefinition(csn, cb) {
105
100
  * @param {string} moduleName The calling compiler module name, e.g. `to.hdi` or `to.hdbcds`.
106
101
  */
107
102
  function transformForHanaWithCsn(inputModel, options, moduleName) {
108
- const columnClearer = [];
109
103
  // copy the model as we don't want to change the input model
110
104
  timetrace.start('HANA transformation');
111
105
  /** @type {CSN.Model} */
@@ -120,15 +114,21 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
120
114
  let error, warning, info; // message functions
121
115
  /** @type {() => void} */
122
116
  let throwWithError;
123
- let artifactRef, inspectRef, queryOrMain, effectiveType, // csnRefs
124
- addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType, getFinalBaseType; // transformUtils
117
+ let artifactRef, inspectRef, effectiveType, // csnRefs
118
+ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType, getFinalBaseType, // transformUtils
119
+ get$combined; // csnUtils
125
120
 
126
121
  bindCsnReference();
127
122
 
128
123
  throwWithError(); // reclassify and throw in case of non-configurable errors
124
+
125
+ if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
126
+ enrichUniversalCsn(csn, options);
127
+ bindCsnReference();
128
+ }
129
129
 
130
130
  const dialect = options.forHana && options.forHana.dialect || options.toSql && options.toSql.dialect;
131
- const doA2J = !options.forHana.keepStructsAssocs;
131
+ const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
132
132
  if (!doA2J)
133
133
  forEachDefinition(csn, handleMixinOnConditions);
134
134
 
@@ -142,25 +142,48 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
142
142
  // subsequent procession steps (especially a2j) to see plain paths in expressions.
143
143
  // If errors are detected, throwWithError() will return from further processing
144
144
 
145
- forEachDefinition(csn, expandStructsInExpression);
145
+ expandStructsInExpression(csn, { drillRef: true });
146
146
 
147
147
  throwWithError();
148
148
 
149
149
  // FIXME: This does something very similar to cloneWithTransformations -> refactor?
150
150
  const transformCsn = transformUtils.transformModel;
151
151
 
152
- handleExists(csn, error);
152
+ handleExists(csn, options, error);
153
153
 
154
154
  // (001) Add a temporal where condition to views where applicable before assoc2join
155
155
  // assoc2join eventually rewrites the table aliases
156
156
  forEachDefinition(csn, addTemporalWhereConditionToView);
157
157
 
158
- // check unique constraints - further processing is done in rewriteUniqueConstraints`
158
+ // check unique constraints - further processing is done in rewriteUniqueConstraints
159
159
  assertUnique.prepare(csn, options, error, info);
160
160
 
161
+ if(doA2J) {
162
+ // Expand a structured thing in: keys, columns, order by, group by
163
+ expansion.expandStructureReferences(csn, options, pathDelimiter, {error, info, throwWithError});
164
+ bindCsnReference();
165
+ }
166
+
161
167
  // Remove properties attached by validator - they do not "grow" as the model grows.
162
168
  cleanup();
163
169
 
170
+ bindCsnReferenceOnly();
171
+
172
+
173
+ if(doA2J) {
174
+ const resolved = new WeakMap();
175
+ // No refs with struct-steps exist anymore
176
+ flattening.flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter);
177
+ // No type references exist anymore
178
+ // Needs to happen exactly between flattenAllStructStepsInRefs and flattenElements to keep model resolvable.
179
+ flattening.resolveTypeReferences(csn, options, resolved, pathDelimiter);
180
+ // No structured elements exists anymore
181
+ flattening.flattenElements(csn, options, pathDelimiter, error);
182
+ } else {
183
+ // For to.hdbcds with naming mode hdbcds we also need to resolve the types
184
+ flattening.resolveTypeReferences(csn, options, undefined, pathDelimiter);
185
+ }
186
+
164
187
  // (010) If requested, translate associations to joins
165
188
  if (doA2J)
166
189
  handleAssocToJoins();
@@ -188,6 +211,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
188
211
  }
189
212
  });
190
213
 
214
+ // Must happen after A2J, as A2J needs $self to correctly resolve stuff
215
+ if(doA2J)
216
+ flattening.removeLeadingSelf(csn);
217
+
191
218
  const {
192
219
  flattenStructuredElement,
193
220
  flattenStructStepsInRef, getForeignKeyArtifact,
@@ -242,13 +269,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
242
269
  },
243
270
  }, true);
244
271
 
245
- // (030) - For all elements, replace derived types by final base type
246
- forEachDefinition(csn, (artifact) => {
247
- forEachMemberRecursively(artifact, (member) => {
248
- toFinalBaseType(member);
249
- });
250
- });
251
-
252
272
  // (040) Ignore entities and views that are abstract or implemented
253
273
  // or carry the annotation cds.persistence.skip/exists
254
274
  // These entities are not removed from the csn, but flagged as "to be ignored"
@@ -260,57 +280,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
260
280
  // Temporal only in beta-mode
261
281
  forEachDefinition(csn, handleTemporalAnnotations);
262
282
 
263
- // Basic flattening of views and entities
264
- // These steps need happen in isolation so that
265
- // the following step can rely on the rest of the model having
266
- // a certain structure, i.e. structs unfolded
267
- if(!options.forHana.keepStructsAssocs) {
268
- const flatteningOfStructured = [];
269
- const adaptRefs = [];
270
- // A2J resolves paths in queries to their flattened version,
271
- // that is the foreign key of a managed assoc that will be generated in a later step
272
- // we need to adapt the refs after the foreign keys are generated
273
- const adaptQueryRefLater = [];
274
-
275
- const fkRefs = new WeakMap();
276
-
277
- applyTransformations(csn, {
278
- keys: (parent, prop, keys) => {
279
- keys.forEach(key => fkRefs.set(key.ref, true));
280
- },
281
- ref: (parent, prop, ref, path) => {
282
- // Do not process fk refs - no need to adapt
283
- if(fkRefs.has(ref))
284
- return;
285
-
286
- setProp(parent, '$path', [...path]);
287
- const lastRef = ref[ref.length-1];
288
- const fn = () => {
289
- const scopedPath = [...parent.$path];
290
- parent.ref = flattenStructStepsInRef(ref, scopedPath);
291
- // Explicitly set implicit alias for things that are now flattened - but only in columns
292
- if (parent.ref[ref.length - 1] != lastRef && insideColumns(scopedPath) && !parent.as)
293
- parent.as = lastRef;
294
- };
295
- // adapt queries later
296
- const enclosingArtifact = csn.definitions[path[1]];
297
- if(enclosingArtifact.query)
298
- adaptQueryRefLater.push(fn);
299
- else
300
- adaptRefs.push(fn);
301
- }
302
- }, [(definitions, artifactName, artifact) => flatteningOfStructured.push(() => flattenStructuredElements(artifact, artifactName))]);
303
-
304
- adaptRefs.forEach(fn => fn());
305
- flatteningOfStructured.forEach(fn => fn());
306
- handleManagedAssociationsAndCreateForeignKeys();
307
- // now the foreign key references in queries are resolvable
308
- bindCsnReferenceOnly();
309
- adaptQueryRefLater.forEach(fn => fn());
310
- }else {
311
- handleManagedAssociationsAndCreateForeignKeys();
312
- }
313
-
283
+ handleManagedAssociationsAndCreateForeignKeys();
284
+
314
285
  function handleManagedAssociationsAndCreateForeignKeys() {
315
286
  forEachDefinition(csn, (art, artName) => handleManagedAssociationFKs(art, artName));
316
287
  forEachDefinition(csn, (art, artName) => createForeignKeyElements(art, artName));
@@ -359,20 +330,19 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
359
330
  // because otherwise we would produce wrong ON-conditions for the keys involved. Sigh ...
360
331
  forEachDefinition(csn, transformSelfInBacklinks);
361
332
 
362
- if(isBetaEnabled(options, 'foreignKeyConstraints') && options.forHana){
333
+ if(options.forHana){
363
334
  /**
364
335
  * Referential Constraints are only supported for sql-dialect "hana" and "sqlite".
365
336
  * For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
366
337
  * hence we do not generate the referential constraints for them.
367
338
  */
368
339
  const validOptionsForConstraint = () => {
369
- return (options.forHana.dialect === 'sqlite' || options.forHana.dialect === 'hana') &&
370
- !(options.toHana && options.toHana.names === 'hdbcds')
340
+ return (options.forHana.dialect === 'sqlite' || options.forHana.dialect === 'hana') && doA2J;
371
341
  }
372
342
  if(validOptionsForConstraint())
373
343
  createReferentialConstraints(csn, options);
374
344
  }
375
-
345
+ // no constraints for drafts
376
346
  generateDrafts(csn, options, pathDelimiter, { info, warning, error });
377
347
 
378
348
  // Set the final constraint paths and produce hana tc indexes if required
@@ -385,17 +355,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
385
355
  // Apply view-specific transformations
386
356
  // (160) Projections now finally become views
387
357
  // Replace managed association in group/order by with foreign keys
358
+ const transformEntityOrViewPass2 = getViewTransformer(csn, options, {error, info}, transformCommon);
388
359
  forEachDefinition(csn, transformViews);
389
360
 
390
361
  // Recursively apply transformCommon and attach @cds.persistence.name
391
362
  forEachDefinition(csn, recursivelyApplyCommon);
392
363
 
393
- // (20 a) If we keep associations as they are (hdbcds naming convention), we cannot have structured
394
- // view elements (we could enumerate the elements but we can't give them the names one would expect)
395
- // Check foreign keys of redirected associations
396
- // (200) Strip 'key' property from type elements
397
- forEachDefinition(csn, recursiveChecks);
398
-
399
364
  const checkConstraintIdentifiers = (artifact, artifactName, prop, path) => {
400
365
  assertConstraintIdentifierUniqueness(artifact, artifactName, path, error);
401
366
  };
@@ -419,12 +384,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
419
384
  checkConstraintIdentifiers,
420
385
  /* (250) Remove all namespaces from definitions */
421
386
  removeNamespaces,
422
- /* (190 b) Replace enum types by their final base type */
423
- replaceEnumsByBaseTypes,
424
387
  /* Check Type Parameters (precision, scale, length ...) */
425
388
  checkTypeParameters,
426
389
  /* Filter out aspects/types/abstract entities containing managed compositions of anonymous aspects */
427
- ignoreNonPersistedArtifactsWithAnonymousAspectComposition
390
+ ignoreNonPersistedArtifactsWithAnonymousAspectComposition,
391
+ // (200) Strip 'key' property from type elements
392
+ removeKeyPropInType,
428
393
  ]);
429
394
 
430
395
  throwWithError();
@@ -436,34 +401,31 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
436
401
  }
437
402
 
438
403
  const killers = {
404
+ // Used to ignore actions etc from processing and remove associations/elements
439
405
  '_ignore': function (parent, a, b, path){
440
406
  if(path.length > 2) {
441
407
  const tail = path[path.length-1];
442
- const parentParent = walkCsnPath(path.slice(0, -1));
408
+ const parentPath = path.slice(0, -1)
409
+ const parentParent = walkCsnPath(csn, parentPath);
443
410
  delete parentParent[tail];
444
411
  } else {
445
412
  delete parent._ignore;
446
413
  }
447
414
  },
448
- '_art': killProp,
449
- '_effectiveType': killProp,
415
+ // Still used in flattenStructuredElements - in db/flattening.js
450
416
  '_flatElementNameWithDots': killProp,
451
- '_sources': killProp,
417
+ // Set when setting default string/binary length - used in copyTypeProperties and fixBorkedElementsOfLocalized
418
+ // to not copy the .length property if it was only set via default
452
419
  '$default': killProp,
453
- '$draftRoot': killProp,
454
- '$env': killProp,
455
- '$fksgenerated': killProp,
456
- '$lateFlattening': killProp,
457
- '$path': killProp,
420
+ // Set when we turn UUID into String, checked during generateDraftForHana
458
421
  '$renamed': killProp,
459
- '$key': killProp,
460
- '$generatedExists': killProp
422
+ // Set when we remove .key from temporal things, used in localized.js
423
+ '$key': killProp
461
424
  }
462
425
 
463
426
  applyTransformations(csn, killers, [], false);
464
427
 
465
428
  redoProjections.forEach(fn => fn());
466
- columnClearer.forEach(fn => fn());
467
429
 
468
430
  return csn;
469
431
 
@@ -478,7 +440,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
478
440
  * @param {string} artName
479
441
  */
480
442
  function createForeignKeyElements(art, artName) {
481
- if ((art.kind === 'entity' || art.kind === 'view') && !options.forHana.keepStructsAssocs) {
443
+ if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
482
444
  forAllElements(art, artName, (parent, elements, pathToElements) => {
483
445
  const elementsArray = [];
484
446
  forEachGeneric(parent, 'elements', (element, elemName) => {
@@ -528,14 +490,14 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
528
490
 
529
491
  function bindCsnReference(){
530
492
  ({ error, warning, info, throwWithError } = makeMessageFunction(csn, options, moduleName));
531
- ({ artifactRef, inspectRef, queryOrMain, effectiveType } = csnRefs(csn));
532
- ({ getFinalBaseType } = getUtils(csn));
493
+ ({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
494
+ ({ getFinalBaseType, get$combined } = getUtils(csn));
533
495
  ({ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter));
534
496
  }
535
497
 
536
498
  function bindCsnReferenceOnly(){
537
499
  // invalidate caches for CSN ref API
538
- ({ artifactRef, inspectRef, queryOrMain, effectiveType } = csnRefs(csn));
500
+ ({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
539
501
  }
540
502
 
541
503
  function handleMixinOnConditions(artifact, artifactName) {
@@ -568,12 +530,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
568
530
  if(onConditionPart.ref && (onConditionPart.ref[0] === '$projection' || onConditionPart.ref[0] === '$self')){
569
531
  const { links } = inspectRef(path.concat(['on', i]));
570
532
  if(links){
571
- // no assocs in $projection / $self paths #5050
572
- links.forEach((element, j) => {
573
- const { art } = links[j];
574
- if(art && art.target) // Fine for plain
575
- error(null, path.concat(['on', j]), `Step "${onConditionPart.ref[j]}" in path "${onConditionPart.ref.join('.')}" must not be an association`);
576
- });
577
533
  columnToReplace = onConditionPart.ref[links.length - 1];
578
534
  }
579
535
  }
@@ -648,36 +604,15 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
648
604
  * @param {CSN.Artifact} artifact
649
605
  * @param {string} artifactName
650
606
  */
651
- function recursiveChecks(artifact, artifactName) {
607
+ function removeKeyPropInType(artifact, artifactName) {
652
608
  if (!artifact._ignore) {
653
- forEachMemberRecursively(artifact, (member, memberName, property, path) => {
654
- if (options.forHana.keepStructsAssocs &&
655
- artifact.query &&
656
- isStructured(member)) {
657
- error(null, path, `With "hdbcds" naming, structured elements can't be used in a view`);
658
- return;
659
- }
660
-
609
+ forEachMemberRecursively(artifact, (member) => {
661
610
  if (artifact.kind === 'type' && member.key)
662
611
  delete member.key;
663
612
  }, [ 'definitions', artifactName ]);
664
613
  }
665
614
  }
666
615
 
667
- /**
668
- * @param {CSN.Artifact} artifact
669
- * @param {string} artifactName
670
- */
671
- function replaceEnumsByBaseTypes(artifact, artifactName) {
672
- replaceEnumByBaseType(artifact);
673
- forEachMemberRecursively(artifact, (member) => {
674
- replaceEnumByBaseType(member);
675
- if (options.forHana.alwaysResolveDerivedTypes || options.forHana.names === 'plain') {
676
- toFinalBaseType(member);
677
- addDefaultTypeFacets(member);
678
- }
679
- }, [ 'definitions', artifactName ]);
680
- }
681
616
 
682
617
  /**
683
618
  * @param {CSN.Artifact} artifact
@@ -720,7 +655,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
720
655
  if (artifact.params) {
721
656
  // HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
722
657
  // SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
723
- error(null, path, `Associations are not allowed in entities with parameters`);
658
+ error(null, path, 'Unexpected association in parameterized view');
724
659
  }
725
660
  else if(artifact['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
726
661
  // UDF/CVs w/o params don't support 'WITH ASSOCIATIONS'
@@ -729,7 +664,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
729
664
  if (csn.definitions[member.target].params) {
730
665
  // HANA does not allow association targets with parameters or to UDFs/CVs w/o parameters:
731
666
  // SAP DBTech JDBC: [7]: feature not supported: cannot support create association to a parameterized view
732
- error(null, path, `Associations can't point to entities with parameters`);
667
+ error(null, path, 'Unexpected parameterized association target');
733
668
  }
734
669
  else if(csn.definitions[member.target]['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
735
670
  // HANA won't check the assoc target but when querying an association with target UDF, this is the error:
@@ -755,14 +690,15 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
755
690
  function handleAssociations(artifact, artifactName) {
756
691
  // Do things specific for entities and views (pass 1)
757
692
  if (artifact.kind === 'entity' || artifact.kind === 'view') {
693
+ const alreadyHandled = new WeakMap();
758
694
  forAllElements(artifact, artifactName, (parent, elements) => {
759
695
  for (const elemName in elements) {
760
696
  const elem = elements[elemName];
761
697
  // (140) Generate foreign key elements and ON-condition for managed associations
762
698
  // (unless explicitly asked to keep assocs unchanged)
763
- if (!options.forHana.keepStructsAssocs) {
699
+ if (doA2J) {
764
700
  if (isManagedAssociationElement(elem))
765
- transformManagedAssociation(parent, artifactName, elem, elemName);
701
+ transformManagedAssociation(parent, artifactName, elem, elemName, alreadyHandled);
766
702
  }
767
703
  }
768
704
  })
@@ -771,7 +707,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
771
707
 
772
708
  function fixBorkedElementsOfLocalized(elements, pathToElements){
773
709
  const pathToNonLocalized = ['definitions', pathToElements[1].replace('localized.',''), ...pathToElements.slice(2)];
774
- const nonLocalizedElements = walkCsnPath(pathToNonLocalized);
710
+ const nonLocalizedElements = walkCsnPath(csn, pathToNonLocalized);
775
711
 
776
712
 
777
713
  for(const elementName in elements){
@@ -818,7 +754,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
818
754
  else {
819
755
  for (const pname in artifact.params) {
820
756
  if (pname.match(/\W/g) || pname.match(/^\d/) || pname.match(/^_/)) { // parameter name must be regular SQL identifier
821
- warning(null, [ 'definitions', artifactName, 'params', pname ], `"${ artifactName }", parameter "${ pname }": is not a regular SQL identifier`);
757
+ warning(null, [ 'definitions', artifactName, 'params', pname ], `Expecting regular SQL-Identifier`);
822
758
  }
823
759
  else if (options.forHana.names !== 'plain' && pname.toUpperCase() !== pname) { // not plain mode: param name must be all upper
824
760
  warning(null, [ 'definitions', artifactName, 'params', pname ], { name: options.forHana.names },
@@ -872,9 +808,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
872
808
  }
873
809
 
874
810
  function handleAssocToJoins() {
811
+ // With flattening errors, it makes little sense to continue.
812
+ throwWithError();
875
813
  // the augmentor isn't able to deal with technical configurations and since assoc2join can ignore it we
876
814
  // simply make it invisible and copy it over to the result csn
877
815
  forEachDefinition(csn, art => art.technicalConfig && setProp(art, 'technicalConfig', art.technicalConfig));
816
+
878
817
  const newCsn = translateAssocsToJoinsCSN(csn, options);
879
818
 
880
819
  // restore all (non-enumerable) properties that wouldn't survive reaugmentation/compactification into the new compact model
@@ -885,30 +824,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
885
824
  if (art.technicalConfig)
886
825
  newCsn.definitions[artName].technicalConfig = art.technicalConfig;
887
826
 
888
- const newArt = newCsn.definitions[artName];
889
-
890
- // No need to loop/check artifacts that won't reach the DB anyways
891
- if (art.query && newArt && newArt.query && isPersistedOnDatabase(newArt)) {
892
- // Loop through the newCSN and add possible new _ignore mixin to the kill list
893
- forAllQueries(newArt.query, (q, p) => {
894
- if (q.SELECT && q.SELECT.mixin) {
895
- for(let mixinName of Object.keys(q.SELECT.mixin)) {
896
- const mixinElement = q.SELECT.mixin[mixinName];
897
- if (mixinElement._ignore && options.toSql) {
898
- columnClearer.push(() => {
899
- const query = walkCsnPath(p);
900
- for(let i = query.columns.length-1; i > -1; i--){
901
- const col = query.columns[i];
902
- if(col && col.ref && col.ref[0] === mixinName){
903
- query.columns.splice(i, 1);
904
- }
905
- }
906
- });
907
- }
908
- }
909
- }
910
- }, ['definitions', artName, 'query']);
911
- }
912
827
  });
913
828
  csn = newCsn;
914
829
  }
@@ -1056,127 +971,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1056
971
  }
1057
972
  }
1058
973
 
1059
-
1060
- /**
1061
- * Compute and return $combined for the given query.
1062
- *
1063
- * @param {CSN.Query} query
1064
- * @returns {object}
1065
- */
1066
- function get$combined(query) {
1067
- const sources = getSources(query);
1068
- return sources;
1069
-
1070
- /**
1071
- * Get the union of all elements from the from clause
1072
- * - descend into unions, following the lead query
1073
- * - merge all queries in case of joins
1074
- * - follow subqueries
1075
- *
1076
- * @param {CSN.Query} query Query to check
1077
- * @returns {object} Map of sources
1078
- */
1079
- function getSources(query) {
1080
- // Remark CW: better just a while along query.SET.args[0]
1081
- if (query.SET) {
1082
- if (query.SET.args[0].SELECT && query.SET.args[0].SELECT.elements)
1083
- return mergeElementsIntoMap(Object.create(null), query.SET.args[0].SELECT.elements, query.SET.args[0].$location);
1084
-
1085
- return getSources(query.SET.args[0]);
1086
- }
1087
- else if (query.SELECT) {
1088
- if (query.SELECT.from.args) {
1089
- return walkArgs(query.SELECT.from.args);
1090
- }
1091
- else if (query.SELECT.from.ref) {
1092
- const art = artifactRef(query.SELECT.from);
1093
- return mergeElementsIntoMap(Object.create(null), art.elements, art.$location,
1094
- query.SELECT.from.as || query.SELECT.from.ref[query.SELECT.from.ref.length - 1],
1095
- query.SELECT.from.ref[query.SELECT.from.ref.length - 1] || query.SELECT.from.as );
1096
- }
1097
- else if (query.SELECT.from.SET || query.SELECT.from.SELECT) {
1098
- return getSources(query.SELECT.from);
1099
- }
1100
- }
1101
-
1102
- function walkArgs(args) {
1103
- let elements = Object.create(null);
1104
- for (const arg of args) {
1105
- if (arg.args) {
1106
- elements = mergeElementMaps(elements, walkArgs(arg.args));
1107
- }
1108
- else if (arg.ref) {
1109
- const art = artifactRef(arg);
1110
- elements = mergeElementsIntoMap(elements, art.elements, art.$location, arg.as || arg.ref[arg.ref.length - 1], arg.ref[arg.ref.length - 1] || arg.as);
1111
- }
1112
- else if (arg.SELECT || arg.SET) {
1113
- elements = mergeElementMaps(elements, getSources(arg));
1114
- }
1115
- }
1116
-
1117
- return elements;
1118
- }
1119
-
1120
- return {};
1121
-
1122
- /**
1123
- * Merge two maps of elements together
1124
- *
1125
- * @param {object} mapA Map a - will be returned
1126
- * @param {object} mapB Map b - will not be returned
1127
- * @returns {object} mapA
1128
- */
1129
- function mergeElementMaps(mapA, mapB) {
1130
- for (const elementName in mapB) {
1131
- if (!mapA[elementName])
1132
- mapA[elementName] = [];
1133
-
1134
- mapB[elementName].forEach(e => mapA[elementName].push(e));
1135
- }
1136
-
1137
- return mapA;
1138
- }
1139
-
1140
- /**
1141
- * Merge elements into an existing map
1142
- *
1143
- * @param {any} existingMap map to merge into - will be returned
1144
- * @param {object} elements elements to merge into the map
1145
- * @param {CSN.Location} $location $location of the elements - where they come from
1146
- * @param {any} [parent] Name of the parent of the elements, alias before ref
1147
- * @param {any} [error_parent] Parent name to use for error messages, ref before alias
1148
- * @returns {object} existingMap
1149
- */
1150
- function mergeElementsIntoMap(existingMap, elements, $location, parent, error_parent) {
1151
- for (const elementName in elements) {
1152
- const element = elements[elementName];
1153
- if (!existingMap[elementName])
1154
- existingMap[elementName] = [];
1155
-
1156
-
1157
- existingMap[elementName].push({
1158
- element, name: elementName, source: $location, parent: getBaseName(parent), error_parent,
1159
- });
1160
- }
1161
-
1162
- return existingMap;
1163
- }
1164
- }
1165
- }
1166
- /**
1167
- * Return the name part of the artifact name - no namespace etc.
1168
- * @param {string|object} name Absolute name of the artifact
1169
- */
1170
- function getBaseName(name) {
1171
- if (!name)
1172
- return name;
1173
-
1174
- if (name.id)
1175
- return name.id.substring( name.id.lastIndexOf('.')+1 );
1176
-
1177
- return name.substring( name.lastIndexOf('.')+1 )
1178
- }
1179
-
1180
974
  /**
1181
975
  * Get all elements tagged with @cds.valid.from/to from the union of all entities of the from-clause.
1182
976
  *
@@ -1215,10 +1009,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1215
1009
  if (isPersistedOnDatabase(artifact)) {
1216
1010
  // TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
1217
1011
  if (artifact.query) {
1218
- if (artifact.query.SELECT && artifact.query.SELECT.mixin)
1012
+ // If we do A2J, we don't need to check the mixin. Either it is used -> a join
1013
+ // or published -> handled via elements/members. Unused mixins are removed anyway.
1014
+ if (!doA2J && artifact.query.SELECT && artifact.query.SELECT.mixin)
1219
1015
  forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
1220
1016
 
1221
- else if (artifact.query.SET && artifact.query.SET.mixin)
1017
+ else if (!doA2J && artifact.query.SET && artifact.query.SET.mixin)
1222
1018
  forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
1223
1019
  }
1224
1020
  forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
@@ -1259,16 +1055,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1259
1055
 
1260
1056
  // (190 a) Replace enum symbols by their value (if found)
1261
1057
  replaceEnumSymbolsByValues(obj, path);
1262
- }
1263
-
1264
- function walkCsnPath(path) {
1265
- /** @type {object} */
1266
- let obj = csn;
1267
- for(let i = 0; i < path.length; i++){
1268
- obj = obj[path[i]];
1269
- }
1270
1058
 
1271
- return obj;
1059
+ if (obj.enum)
1060
+ delete obj.enum;
1272
1061
  }
1273
1062
 
1274
1063
  // Change the names of those builtin types that have different names in HANA.
@@ -1302,320 +1091,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1302
1091
  // }
1303
1092
  // }
1304
1093
 
1305
- /**
1306
- * Strip of leading $self of the ref
1307
- * @param {object} col A column
1308
- *
1309
- * @returns {object}
1310
- */
1311
- function stripLeadingSelf(col) {
1312
- if (col.ref && col.ref.length > 1 && col.ref[0] === '$self')
1313
- col.ref = col.ref.slice(1);
1314
-
1315
-
1316
- return col;
1317
- }
1318
-
1319
- function isUnion(path){
1320
- const subquery = path[path.length-1];
1321
- const queryIndex = path[path.length-2]
1322
- const args = path[path.length-3];
1323
- const unionOperator = path[path.length-4];
1324
- return path.length > 3 && (subquery === 'SET' || subquery === 'SELECT') && typeof queryIndex === 'number' && queryIndex >= 0 && args === 'args' && unionOperator === 'SET';
1325
- }
1326
-
1327
- function transformEntityOrViewPass2(query, artifact, artName, path) {
1328
- const { elements } = queryOrMain(query, artifact);
1329
- let hasNonAssocElements = false;
1330
- const isSelect = query && query.SELECT;
1331
- let isProjection = !!artifact.projection;
1332
- const columnMap = Object.create(null);
1333
- let isSelectStar = false;
1334
- if (isSelect) {
1335
- if (!query.SELECT.columns) {
1336
- isProjection = true;
1337
- }
1338
- else {
1339
- query.SELECT.columns.forEach((col) => {
1340
- if (col === '*') {
1341
- isSelectStar = true;
1342
- }
1343
- else if (col.as) {
1344
- if (!columnMap[col.as])
1345
- columnMap[col.as] = col;
1346
- }
1347
- else if (col.ref) {
1348
- if (!columnMap[col.ref[col.ref.length - 1]])
1349
- columnMap[col.ref[col.ref.length - 1]] = col;
1350
- }
1351
- else if (col.func) {
1352
- columnMap[col.func] = col;
1353
- }
1354
- else if (!columnMap[col]) {
1355
- columnMap[col] = col;
1356
- }
1357
- });
1358
- }
1359
- }
1360
- if (query && options.toHana) {
1361
- // check all queries/subqueries for mixin publishing inside of unions -> forbidden in hdbcds
1362
- if (query.SELECT && query.SELECT.mixin && path.indexOf('SET') !== -1) {
1363
- for (const elementName in elements) {
1364
- const element = elements[elementName];
1365
- if (element.target) {
1366
- let colLocation;
1367
- for (let i = 0; i < query.SELECT.columns.length; i++) {
1368
- const col = query.SELECT.columns[i];
1369
- if (col.ref && col.ref.length === 1) {
1370
- if (!colLocation && col.ref[0] === elementName)
1371
- colLocation = i;
1372
-
1373
-
1374
- if (col.as === elementName)
1375
- colLocation = i;
1376
- }
1377
- }
1378
- if (colLocation) {
1379
- const matchingCol = query.SELECT.columns[colLocation];
1380
- const possibleMixinName = matchingCol.ref[0];
1381
- const isMixin = query.SELECT.mixin[possibleMixinName] !== undefined;
1382
- if (element.target && isMixin)
1383
- error(null, path.concat([ 'columns', colLocation ]),
1384
- `Element "${ elementName }" is a mixin association${ possibleMixinName !== elementName ? ` ("${ possibleMixinName }")` : '' } and can't be published in a UNION`);
1385
- }
1386
- }
1387
- }
1388
- }
1389
- }
1390
-
1391
- // Second walk through the entity elements: Deal with associations (might also result in new elements)
1392
-
1393
- // Will be initialized JIT inside the elements-loop
1394
- let $combined;
1395
-
1396
- for (const elemName in elements) {
1397
- const elem = elements[elemName];
1398
- if (isSelect) {
1399
- if (!columnMap[elemName]) {
1400
- // Prepend an alias if present
1401
- let alias = (isProjection || isSelectStar) &&
1402
- (query.SELECT.from.as || (query.SELECT.from.ref && implicitAs(query.SELECT.from.ref)));
1403
- // In case of * and no explicit alias
1404
- // find the source of the col by looking at $combined and prepend it
1405
- if (isSelectStar && !alias && !isProjection) {
1406
- if (!$combined)
1407
- $combined = get$combined(query);
1408
-
1409
-
1410
- const matchingCombined = $combined[elemName];
1411
- // Internal errors - this should never happen!
1412
- if (matchingCombined.length > 1) { // should already be caught by compiler
1413
- throw new Error(`Ambiguous name - can't be resolved: ${ elemName }. Found in: ${ matchingCombined.map(o => o.parent) }`);
1414
- }
1415
- else if (matchingCombined.length === 0) { // no clue how this could happen? Invalid CSN?
1416
- throw new Error(`No matching entry found in UNION of all elements for: ${ elemName }`);
1417
- }
1418
- alias = matchingCombined[0].parent;
1419
- }
1420
- if (alias)
1421
- columnMap[elemName] = { ref: [ alias, elemName ] };
1422
- else
1423
- columnMap[elemName] = { ref: [ elemName ] };
1424
- }
1425
-
1426
- // For associations - make sure that the foreign keys have the same "style"
1427
- // If A.assoc => A.assoc_id, else if assoc => assoc_id or assoc as Assoc => Assoc_id
1428
- if (elem.keys && !options.forHana.keepStructsAssocs) {
1429
- const assoc_col = columnMap[elemName];
1430
- if (assoc_col && assoc_col.ref) {
1431
- elem.keys.forEach((key) => {
1432
- const ref = cloneCsn(assoc_col.ref, options);
1433
- ref[ref.length - 1] = [ ref[ref.length - 1] ].concat(key.as || key.ref).join(pathDelimiter);
1434
- const result = {
1435
- ref,
1436
- };
1437
- if (assoc_col.as)
1438
- result.as = key.$generatedFieldName;
1439
-
1440
-
1441
- if (assoc_col.key)
1442
- result.key = true;
1443
-
1444
-
1445
- const colName = result.as || ref[ref.length - 1];
1446
- columnMap[colName] = result;
1447
- });
1448
- }
1449
- }
1450
- // Add flattened structured things preserving aliases and refs with/without table alias
1451
- // If we add them when we get to them in "elements", we cannot know what table alias was used...
1452
- if (isStructured(elem) && !options.forHana.keepStructsAssocs) {
1453
- const col = columnMap[elemName];
1454
- const originalName = col.ref[col.ref.length - 1];
1455
- const flatElements = flattenStructuredElement(elem, originalName, [], path);
1456
- const aliasedFlatElements = originalName !== elemName ? Object.keys(flattenStructuredElement(elem, elemName, [], path)) : [];
1457
-
1458
- Object.keys(flatElements).forEach((flatElemName, index ) => {
1459
- const clone = cloneCsn(col, options);
1460
- // For the ref, use the "original"
1461
- if (clone.ref)
1462
- clone.ref[clone.ref.length - 1] = flatElemName;
1463
-
1464
- // If the column was aliased, use the alias-prefix for the flattened element
1465
- if (originalName !== elemName)
1466
- clone.as = aliasedFlatElements[index];
1467
-
1468
- // Insert into map, giving precedence to the alias
1469
- columnMap[clone.as || flatElemName] = clone;
1470
- });
1471
- }
1472
- }
1473
- // Views must have at least one element that is not an unmanaged assoc
1474
- if (!elem.on && !elem._ignore)
1475
- hasNonAssocElements = true;
1476
-
1477
-
1478
- // (230 b) If we keep associations as they are (hdbcds naming convention), we cannot have managed associations
1479
- // as view elements (their foreign keys cannot be addressed in the view)
1480
- if (options.forHana.keepStructsAssocs &&
1481
- query &&
1482
- isAssocOrComposition(elem.type) &&
1483
- !elem.on) {
1484
- error(null, [ 'definitions', artName, 'elements', elemName ], `With "hdbcds" naming, managed association elements can't be used in a view`);
1485
- continue;
1486
- }
1487
-
1488
- // (180 b) Create MIXINs for association elements in projections or views (those that are not mixins by themselves)
1489
- // CDXCORE-585: Allow mixin associations to be used and published in parallel
1490
- if (query !== undefined && elem.target) {
1491
- if(isUnion(path) && options.toHana){
1492
- if(isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J){
1493
- if(elem.keys) {
1494
- info(null, path, `Managed association "${elemName}", published in a UNION, will be ignored`)
1495
- } else {
1496
- info(null, path, `Association "${elemName}", published in a UNION, will be ignored`)
1497
- }
1498
- elem._ignore = true;
1499
- }
1500
- else {
1501
- error(null, path, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`)
1502
- }
1503
- } else if(path.length > 4 && options.toHana){ // path.length > 4 -> is a subquery
1504
- error(null, path, { name: elemName },
1505
- 'Association $(NAME) can\'t be published in a subquery')
1506
- } else {
1507
- /* Old implementation:
1508
- const isNotMixinByItself = !(elem.value && elem.value.path && elem.value.path.length == 1 && art.query && art.query.mixin && art.query.mixin[elem.value.path[0].id]);
1509
- */
1510
- const isNotMixinByItself = checkIsNotMixinByItself(query, columnMap, elem, elemName);
1511
- const {mixinElement, mixinName } = getMixinAssocOfQueryIfPublished(query, elem, elemName);
1512
- if (isNotMixinByItself || mixinElement !== undefined) {
1513
- // If the mixin is only published and not used, only display the __ clone. Ignore the "original".
1514
- if (mixinElement !== undefined && !usesMixinAssociation(query, elem, elemName)){
1515
- mixinElement._ignore = true;
1516
- }
1517
-
1518
- delete elem._typeIsExplicit;
1519
- // Create an unused alias name for the MIXIN - use 3 _ to avoid collision with usings
1520
- let mixinElemName = `___${ mixinName || elemName }`;
1521
- while (elements[mixinElemName])
1522
- mixinElemName = `_${ mixinElemName }`;
1523
-
1524
- // Copy the association element to the MIXIN clause under its alias name
1525
- // (shallow copy is sufficient, just fix name and value)
1526
- const mixinElem = Object.assign({}, elem);
1527
- // Perform common transformations on the newly generated MIXIN element (won't be reached otherwise)
1528
- transformCommon(mixinElem, mixinElemName);
1529
- // TODO: Can we rely on query.SELECT.mixin to check for mixins?
1530
- // Yes, we can - only SELECT can have mixin. But:
1531
- // - UNION
1532
- // - JOINS
1533
- // - Subqueries
1534
- // Are currently (and in the old transformer) not handled!
1535
- if (query.SELECT && !query.SELECT.mixin)
1536
- query.SELECT.mixin = Object.create(null);
1537
-
1538
- // Let the original association element use the newly generated MIXIN name as value and alias
1539
- delete elem.viaAll;
1540
-
1541
- // Clone 'on'-condition, pre-pending '$projection' to paths where appropriate,
1542
- // and fixing the association alias just created
1543
-
1544
- if (mixinElem.on) {
1545
- mixinElem.on = cloneWithTransformations(mixinElem.on, {
1546
- ref: (ref) => {
1547
- // Clone the path, without any transformations
1548
- const clonedPath = cloneWithTransformations(ref, {});
1549
- // Prepend '$projection' to the path, unless the first path step is the (mixin) element itself or starts with '$')
1550
- if (clonedPath[0] == elemName) {
1551
- clonedPath[0] = mixinElemName;
1552
- }
1553
- else if (!(clonedPath[0] && clonedPath[0].startsWith('$'))) {
1554
- const projectionId = '$projection';
1555
- clonedPath.unshift(projectionId);
1556
- }
1557
- return clonedPath;
1558
- },
1559
- func: (func) => {
1560
- // Unfortunately, function names are disguised as paths, so we would prepend a '$projection'
1561
- // above (no way to distinguish that in the callback for 'path' above). We can only pluck it
1562
- // off again here ... sigh
1563
- if (func.ref && func.ref[0] && func.ref[0] === '$projection')
1564
- func.ref = func.ref.slice(1);
1565
-
1566
- return func;
1567
- },
1568
- });
1569
- }
1570
-
1571
- if (!mixinElem._ignore)
1572
- columnMap[elemName] = { ref: [ mixinElemName ], as: elemName };
1573
-
1574
- if (query.SELECT) {
1575
- query.SELECT.mixin[mixinElemName] = mixinElem;
1576
- }
1577
- }
1578
- }
1579
- }
1580
- }
1581
-
1582
- if (query && !hasNonAssocElements) {
1583
- // Complain if there are no elements other than unmanaged associations
1584
- // Allow with plain
1585
- error(null, [ 'definitions', artName ], { $reviewed: true } ,
1586
- 'Expecting view or projection to have at least one element that is not an unmanaged association');
1587
- }
1588
-
1589
- if (isSelect) {
1590
- // Workaround for bugzilla 176495 FIXME FIXME FIXME: is this really still needed?
1591
- // If a select item of a cdx view contains an expression, the result type cannot be computed
1592
- // but must be explicitly specified. This is important for the OData channel, which doesn't
1593
- // work if the type is missing (for HANA channel an explicit type is not required, as HANA CDS
1594
- // can compute the result type).
1595
- // Due to bug in HANA CDS, providing explicit type 'LargeString' or 'LargeBinary' causes a
1596
- // diserver crash. Until a fix in HANA CDS is available, we allow to suppress the explicit
1597
- // type in the HANA channel via an annotation.
1598
- Object.keys(columnMap).forEach((value) => {
1599
- const elem = elements[value];
1600
- if (elem && elem['@cds.workaround.noExplicitTypeForHANA'])
1601
- delete columnMap[value].cast;
1602
- });
1603
-
1604
- query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem]._ignore).map(key => stripLeadingSelf(columnMap[key]));
1605
- // If following an association, explicitly set the implicit alias
1606
- // due to an issue with HANA
1607
- for (let i = 0; i < query.SELECT.columns.length; i++) {
1608
- const col = query.SELECT.columns[i];
1609
- if (!col.as && col.ref && col.ref.length > 1) {
1610
- const { links } = inspectRef(path.concat([ 'columns', i ]));
1611
- if (links && links.slice(0, -1).some(({ art }) => isAssocOrComposition(art && art.type || '')))
1612
- col.as = col.ref[col.ref.length - 1];
1613
- }
1614
- }
1615
- delete query.SELECT.excluding; // just to make the output of the new transformer the same as the old
1616
- }
1617
- }
1618
-
1619
1094
 
1620
1095
  // If 'elem' has a default that is an enum constant, replace that by its value. Complain
1621
1096
  // if not found or not an enum type,
@@ -1632,7 +1107,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1632
1107
  // Looks like it is always run?! But message says HANA CDS?!
1633
1108
  error(null, path, {
1634
1109
  $reviewed: true,
1635
- name: `#${elem.default['#']}`
1110
+ name: `#${ elem.default['#'] }`
1636
1111
  },
1637
1112
  'Expecting enum literal $(NAME) to be used with an enum type');
1638
1113
  }
@@ -1642,7 +1117,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1642
1117
  if (!enumSymbol) {
1643
1118
  error(null, path, {
1644
1119
  $reviewed: true,
1645
- name: `#${elem.default['#']}`
1120
+ name: `#${ elem.default['#'] }`
1646
1121
  }, 'Enum literal $(NAME) is undefined in enumeration type');
1647
1122
  }
1648
1123
  else if (enumSymbol.val !== undefined) { // `val` may be `null`
@@ -1659,30 +1134,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1659
1134
  }
1660
1135
  }
1661
1136
 
1662
- // If 'node' has an enum type, change node's type to be the enum's base type
1663
- // and strip off the 'enum' property.
1664
- function replaceEnumByBaseType(node) {
1665
- if (node.items)
1666
- replaceEnumByBaseType(node.items);
1667
-
1668
- // (190 b) Replace enum types by their final base type (must happen after 190 a)
1669
- /* Old implementation:
1670
- if (node && node._finalType && (node.enum || node._finalType.enum)) {
1671
- node.type = node._finalType.type
1672
- // node.type = node._finalType.type._artifact._finalType.type;
1673
- if (node._finalType.length) {
1674
- node.length = node._finalType.length;
1675
- }
1676
- setProp(node, '_finalType', node.type._artifact);
1677
- delete node.enum;
1678
- }
1679
- */
1680
- if (node && node.enum) {
1681
- // toFinalBaseType(node);
1682
- // addDefaultTypeFacets(node);
1683
- delete node.enum;
1684
- }
1685
- }
1686
1137
 
1687
1138
  // If the association element 'elem' of 'art' is a backlink association, massage its ON-condition
1688
1139
  // (in place) so that it
@@ -1713,23 +1164,27 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1713
1164
  const assoc = inspectRef(path.concat([ i + 2 ])).art;
1714
1165
  if (multipleExprs)
1715
1166
  result.push('(');
1167
+ const backlinkName = xprArgs[i + 2].ref[xprArgs[i + 2].ref.length - 1];
1716
1168
  result.push(...transformDollarSelfComparison(xprArgs[i + 2],
1717
1169
  assoc,
1718
- xprArgs[i + 2].ref[xprArgs[i + 2].ref.length - 1],
1170
+ backlinkName,
1719
1171
  elem, elemName, art, artName, path.concat([ i ])
1720
1172
  ));
1721
1173
  if (multipleExprs)
1722
1174
  result.push(')');
1723
1175
  i += 3;
1176
+ attachBacklinkInformation(backlinkName);
1724
1177
  }
1725
1178
  else if (isDollarSelfOrProjectionOperand(xprArgs[i + 2]) && isAssociationOperand(xprArgs[i], path.concat([ i ]))) {
1726
1179
  const assoc = inspectRef(path.concat([ i ])).art;
1727
1180
  if (multipleExprs)
1728
1181
  result.push('(');
1729
- result.push(...transformDollarSelfComparison(xprArgs[i], assoc, xprArgs[i].ref[xprArgs[i].ref.length - 1], elem, elemName, art, artName, path.concat([ i + 2 ])));
1182
+ const backlinkName = xprArgs[i].ref[xprArgs[i].ref.length - 1];
1183
+ result.push(...transformDollarSelfComparison(xprArgs[i], assoc, backlinkName, elem, elemName, art, artName, path.concat([ i + 2 ])));
1730
1184
  if (multipleExprs)
1731
1185
  result.push(')');
1732
1186
  i += 3;
1187
+ attachBacklinkInformation(backlinkName);
1733
1188
  }
1734
1189
  // Otherwise take one (!) token unchanged
1735
1190
  else {
@@ -1749,6 +1204,24 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1749
1204
  }
1750
1205
  }
1751
1206
  return result;
1207
+
1208
+ /**
1209
+ * The knowledge whether an association was an `<up_>` association in a
1210
+ * `$self = <comp>.<up_>` comparison, is important for the foreign key constraints.
1211
+ * By the time we generate them, such on-conditions are already transformed
1212
+ * --> no more `$self` in the on-conditions, that is why we need to remember it here.
1213
+ *
1214
+ * @param {string} backlinkName name of `<up_>` in a `$self = <comp>.<up_>` comparison
1215
+ */
1216
+ function attachBacklinkInformation(backlinkName) {
1217
+ if (elem.$selfOnCondition)
1218
+ elem.$selfOnCondition.up_.push(backlinkName);
1219
+ else {
1220
+ setProp(elem, '$selfOnCondition', {
1221
+ up_: [backlinkName]
1222
+ });
1223
+ }
1224
+ }
1752
1225
  }
1753
1226
 
1754
1227
  elem.on = processExpressionArgs(elem.on, pathToOn);
@@ -1806,8 +1279,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1806
1279
  assoc.keys.forEach((k) => {
1807
1280
  // Depending on naming conventions, the foreign key may two path steps (hdbcds) or be a single path step with a flattened name (plain, quoted)
1808
1281
  // With to.hdbcds in conjunction with hdbcds naming, we need to NOT use the alias - else we get deployment errors
1809
- const keyName = k.as && !(options.toHana && options.toHana.names === 'hdbcds') ? [k.as] : k.ref;
1810
- const fKeyPath = options.forHana.keepStructsAssocs ? [ assocName, ...keyName ] : [ `${ assocName }${ pathDelimiter }${ keyName[0] }` ];
1282
+ const keyName = k.as && doA2J ? [k.as] : k.ref;
1283
+ const fKeyPath = !doA2J ? [ assocName, ...keyName ] : [ `${ assocName }${ pathDelimiter }${ keyName[0] }` ];
1811
1284
  // FIXME: _artifact to the args ???
1812
1285
  const a = [
1813
1286
  {
@@ -1862,30 +1335,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1862
1335
  }
1863
1336
  }
1864
1337
 
1865
- /**
1866
- * @todo: XSN - Implementation most likely too naive, can we rely on query.SELECT.mixin?
1867
- *
1868
- * @param {CSN.Query} query
1869
- * @param {object} columnMap
1870
- * @param {CSN.Artifact} columnMap
1871
- * @param {string} elementName
1872
- */
1873
- function checkIsNotMixinByItself(query, columnMap, element, elementName) {
1874
- if (query && query.SELECT && query.SELECT.mixin) {
1875
- const col = columnMap[elementName];
1876
-
1877
- const realName = col.ref[col.ref.length - 1];
1878
- // If the element is not part of the mixin => True
1879
- return query.SELECT.mixin[realName] == undefined;
1880
- }
1881
- // the artifact does not define any mixins, the element cannot be a mixin
1882
- return true;
1883
- }
1884
-
1885
- function insideColumns(path) {
1886
- return path.length >= 3 && path[path.length - 3] === 'SELECT' && path[path.length - 2] === 'columns';
1887
- }
1888
-
1889
1338
  /**
1890
1339
  * @param {CSN.Artifact} artifact
1891
1340
  * @param {string} artifactName
@@ -1982,119 +1431,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1982
1431
  return !(options.toSql && type === 'cds.String' && (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain'));
1983
1432
  }
1984
1433
 
1985
- /**
1986
- * Get not just the leafs, but all the branches of a structured element
1987
- *
1988
- * @param {object} element Structured element
1989
- * @param {string} elementName Name of the structured element
1990
- * @returns {object} Returns a dictionary, where the key is the flat name of the branch and the value is an array of element-steps.
1991
- */
1992
- function getBranches(element, elementName){
1993
- const branches = {};
1994
- const subbranchNames = [];
1995
- const subbranchElements = [];
1996
- walkElements(element, elementName);
1997
- function walkElements(e, name){
1998
- if(isBuiltinType(e)){
1999
- branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
2000
- } else {
2001
- const eType = effectiveType(e)
2002
- const subelements = e.elements || eType.elements;
2003
- if(subelements){
2004
- subbranchElements.push(e);
2005
- subbranchNames.push(name);
2006
- for(let [subelementName, subelement] of Object.entries(subelements)){
2007
- walkElements(subelement, subelementName);
2008
- }
2009
- subbranchNames.pop();
2010
- subbranchElements.pop();
2011
- } else {
2012
- branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
2013
- }
2014
-
2015
- }
2016
- }
2017
- return branches;
2018
- }
2019
-
2020
- /**
2021
- * Flatten structures
2022
- *
2023
- * @param {CSN.Artifact} art Artifact
2024
- * @param {string} artName Artifact Name
2025
- */
2026
- function flattenStructuredElements(art, artName) {
2027
- if ((art.kind === 'entity' || art.kind === 'view') && !options.forHana.keepStructsAssocs) {
2028
- forAllElements(art, artName, (parent, elements, pathToElements) => {
2029
- const elementsArray = [];
2030
- for (const elemName in elements) {
2031
- const pathToElement = pathToElements.concat([elemName])
2032
- const elem = parent.elements[elemName];
2033
- elementsArray.push([elemName, elem]);
2034
- if (isStructured(elem)) {
2035
- // Ignore the structured element, replace it by its flattened form
2036
- // TODO: use $ignore - _ is for links
2037
- elem._ignore = true;
2038
-
2039
- const branches = getBranches(elem, elemName);
2040
- const flatElems = flattenStructuredElement(elem, elemName, [], pathToElement);
2041
-
2042
- for (const flatElemName in flatElems) {
2043
- if (parent.elements[flatElemName])
2044
- error(null, pathToElement, `"${ artName }.${ elemName }": Flattened struct element name conflicts with existing element: "${ flatElemName }"`);
2045
-
2046
- const flatElement = flatElems[flatElemName];
2047
-
2048
- // Check if we have a valid notNull chain
2049
- const branch = branches[flatElemName];
2050
- if(flatElement.notNull !== false && !branch.some(s => !s.notNull)){
2051
- flatElement.notNull = true;
2052
- }
2053
-
2054
- if(flatElement.type && isAssocOrComposition(flatElement.type) && flatElement.on){
2055
- // Make refs resolvable by fixing the first ref step
2056
- for (let i = 0; i < flatElement.on.length; i++) {
2057
- const onPart = flatElement.on[i];
2058
- if (onPart.ref) {
2059
- const firstRef = flatElement.on[i].ref[0];
2060
-
2061
- /*
2062
- when element is defined in the current name resolution scope, like
2063
- entity E {
2064
- key x: Integer;
2065
- s : {
2066
- y : Integer;
2067
- a3 : association to E on a3.x = y;
2068
- }
2069
- }
2070
- We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
2071
- */
2072
- const prefix = flatElement._flatElementNameWithDots.split('.').slice(0,-1).join(pathDelimiter);
2073
- const possibleFlatName = prefix + pathDelimiter + firstRef;
2074
-
2075
- if (flatElems[possibleFlatName])
2076
- flatElement.on[i].ref[0] = possibleFlatName;
2077
- }
2078
- }
2079
- }
2080
- elementsArray.push([flatElemName, flatElement]);
2081
- // Still add them - otherwise we might not detect collisions between generated elements.
2082
- parent.elements[flatElemName] = flatElement;
2083
- }
2084
- }
2085
- }
2086
- // Don't fake consistency of the model by adding empty elements {}
2087
- if(elementsArray.length === 0)
2088
- return;
2089
-
2090
- parent.elements = elementsArray.reduce((previous, [name, element]) => {
2091
- previous[name] = element;
2092
- return previous;
2093
- }, Object.create(null));
2094
- });
2095
- }
2096
- }
2097
-
2098
1434
  /**
2099
1435
  * Flatten and create the foreign key elements of managed associaitons
2100
1436
  *
@@ -2102,7 +1438,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2102
1438
  * @param {string} artName
2103
1439
  */
2104
1440
  function handleManagedAssociationFKs(art, artName) {
2105
- if ((art.kind === 'entity' || art.kind === 'view') && !options.forHana.keepStructsAssocs) {
1441
+ if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
2106
1442
  forAllElements(art, artName, (parent, elements, pathToElements) => {
2107
1443
  if(artName.startsWith('localized.') && pathToElements.length > 3) {
2108
1444
  // In subqueries, the elements of localized views are missing all the important bits and pieces...
@@ -2246,7 +1582,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2246
1582
  function flattenIndexes(art, artName) {
2247
1583
  // Flatten structs in indexes (unless explicitly asked to keep structs)
2248
1584
  const tc = art.technicalConfig;
2249
- if ((art.kind === 'entity' || art.kind === 'view') && !options.forHana.keepStructsAssocs) {
1585
+ if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
2250
1586
  if (tc && tc[dialect]) {
2251
1587
  // Secondary and fulltext indexes
2252
1588
  for (const name in tc[dialect].indexes) {
@@ -2313,7 +1649,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2313
1649
  function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
2314
1650
  for (const elemName in artifact.elements) {
2315
1651
  const elem = artifact.elements[elemName];
2316
- if (!options.forHana.keepStructsAssocs) {
1652
+ if (doA2J) {
2317
1653
  // The association is an unmanaged on
2318
1654
  if (!elem.keys && elem.target && elem.on) {
2319
1655
  forEachRef(elem.on, (ref, refOwner, path) => {
@@ -2333,11 +1669,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2333
1669
  const source = findSource(links, i - 1) || artifact;
2334
1670
  // allow specifying managed assoc on the source side
2335
1671
  const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
2336
-
2337
1672
  if(fks && fks.length >= 1){
2338
1673
  const fk = fks[0];
2339
- if(source && source.elements[fk.$generatedFieldName])
2340
- refOwner.ref = [ ...ref.slice(0, i), fk.$generatedFieldName ];
1674
+ const managedAssocStepName = refOwner.ref[i];
1675
+ const fkName = `${ managedAssocStepName }${ pathDelimiter }${ fk.as }`;
1676
+ if(source && source.elements[fkName])
1677
+ refOwner.ref = [ ...ref.slice(0, i), fkName ];
2341
1678
  }
2342
1679
  }
2343
1680
  }
@@ -2376,12 +1713,13 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2376
1713
  * @param {string} artifactName
2377
1714
  * @param {Object} elem The association to process
2378
1715
  * @param {string} elemName
1716
+ * @param {WeakMap} alreadyHandled To cache which elements were already processed
2379
1717
  * @returns {void}
2380
1718
  */
2381
- function transformManagedAssociation(artifact, artifactName, elem, elemName) {
1719
+ function transformManagedAssociation(artifact, artifactName, elem, elemName, alreadyHandled) {
2382
1720
  // No need to run over this - we already did, possibly because it was referenced in the ON-Condition
2383
1721
  // of another association - see a few lines lower
2384
- if (elem.$fksgenerated)
1722
+ if (alreadyHandled.has(elem))
2385
1723
  return;
2386
1724
  // Generate foreign key elements for managed associations, and assemble an ON-condition with them
2387
1725
  const onCondParts = [];
@@ -2398,10 +1736,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2398
1736
  elemName,
2399
1737
  ].concat(foreignKey.ref),
2400
1738
  };
2401
-
1739
+ const fkName = `${ elemName }${ pathDelimiter }${ foreignKey.as }`;
2402
1740
  const fKeyArg = {
2403
1741
  ref: [
2404
- foreignKey.$generatedFieldName,
1742
+ fkName,
2405
1743
  ],
2406
1744
  };
2407
1745
 
@@ -2440,7 +1778,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2440
1778
  elem.implicitForeignKeys = true;
2441
1779
  */
2442
1780
  // Remember that we already processed this
2443
- setProp(elem, '$fksgenerated', true);
1781
+ alreadyHandled.set(elem, true);
2444
1782
  }
2445
1783
  }
2446
1784