@sap/cds-compiler 2.5.0 → 2.10.4

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 (92) hide show
  1. package/CHANGELOG.md +191 -9
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +33 -3
  4. package/lib/api/main.js +29 -101
  5. package/lib/api/options.js +15 -11
  6. package/lib/api/validate.js +12 -8
  7. package/lib/backends.js +0 -81
  8. package/lib/base/keywords.js +32 -2
  9. package/lib/base/message-registry.js +63 -9
  10. package/lib/base/messages.js +63 -21
  11. package/lib/base/model.js +2 -3
  12. package/lib/checks/defaultValues.js +27 -2
  13. package/lib/checks/elements.js +1 -6
  14. package/lib/checks/foreignKeys.js +0 -6
  15. package/lib/checks/managedWithoutKeys.js +17 -0
  16. package/lib/checks/nonexpandableStructured.js +38 -0
  17. package/lib/checks/onConditions.js +9 -45
  18. package/lib/checks/queryNoDbArtifacts.js +25 -7
  19. package/lib/checks/selectItems.js +25 -2
  20. package/lib/checks/types.js +26 -2
  21. package/lib/checks/unknownMagic.js +38 -0
  22. package/lib/checks/utils.js +61 -0
  23. package/lib/checks/validator.js +60 -7
  24. package/lib/compiler/assert-consistency.js +16 -7
  25. package/lib/compiler/builtins.js +2 -0
  26. package/lib/compiler/checks.js +6 -4
  27. package/lib/compiler/definer.js +99 -42
  28. package/lib/compiler/index.js +73 -27
  29. package/lib/compiler/resolver.js +288 -157
  30. package/lib/compiler/shared.js +31 -11
  31. package/lib/edm/annotations/genericTranslation.js +182 -186
  32. package/lib/edm/csn2edm.js +103 -108
  33. package/lib/edm/edm.js +18 -21
  34. package/lib/edm/edmPreprocessor.js +361 -114
  35. package/lib/edm/edmUtils.js +103 -33
  36. package/lib/gen/Dictionary.json +22 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +12 -1
  39. package/lib/gen/language.tokens +57 -53
  40. package/lib/gen/languageLexer.interp +10 -1
  41. package/lib/gen/languageLexer.js +770 -744
  42. package/lib/gen/languageLexer.tokens +49 -46
  43. package/lib/gen/languageParser.js +4713 -4279
  44. package/lib/json/from-csn.js +103 -45
  45. package/lib/json/to-csn.js +296 -117
  46. package/lib/language/antlrParser.js +4 -3
  47. package/lib/language/errorStrategy.js +1 -0
  48. package/lib/language/genericAntlrParser.js +21 -12
  49. package/lib/language/language.g4 +99 -31
  50. package/lib/main.d.ts +81 -3
  51. package/lib/main.js +30 -7
  52. package/lib/model/api.js +78 -0
  53. package/lib/model/csnRefs.js +329 -142
  54. package/lib/model/csnUtils.js +235 -58
  55. package/lib/model/enrichCsn.js +18 -1
  56. package/lib/model/revealInternalProperties.js +2 -1
  57. package/lib/modelCompare/compare.js +37 -20
  58. package/lib/optionProcessor.js +9 -3
  59. package/lib/render/.eslintrc.json +4 -1
  60. package/lib/render/DuplicateChecker.js +8 -5
  61. package/lib/render/toCdl.js +112 -33
  62. package/lib/render/toHdbcds.js +134 -64
  63. package/lib/render/toSql.js +91 -38
  64. package/lib/render/utils/common.js +8 -13
  65. package/lib/render/utils/sql.js +3 -3
  66. package/lib/sql-identifier.js +6 -1
  67. package/lib/transform/db/assertUnique.js +5 -6
  68. package/lib/transform/db/constraints.js +29 -13
  69. package/lib/transform/db/draft.js +8 -6
  70. package/lib/transform/db/expansion.js +582 -0
  71. package/lib/transform/db/flattening.js +325 -0
  72. package/lib/transform/db/groupByOrderBy.js +2 -2
  73. package/lib/transform/db/transformExists.js +284 -63
  74. package/lib/transform/forHanaNew.js +98 -381
  75. package/lib/transform/forOdataNew.js +21 -22
  76. package/lib/transform/localized.js +37 -10
  77. package/lib/transform/odata/attachPath.js +19 -4
  78. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  79. package/lib/transform/odata/referenceFlattener.js +60 -39
  80. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  81. package/lib/transform/odata/structuralPath.js +72 -0
  82. package/lib/transform/odata/structureFlattener.js +19 -18
  83. package/lib/transform/odata/typesExposure.js +22 -12
  84. package/lib/transform/transformUtilsNew.js +134 -78
  85. package/lib/transform/translateAssocsToJoins.js +17 -14
  86. package/lib/transform/universalCsnEnricher.js +67 -0
  87. package/lib/utils/file.js +0 -11
  88. package/lib/utils/moduleResolve.js +6 -8
  89. package/package.json +1 -1
  90. package/lib/json/walker.js +0 -26
  91. package/lib/transform/sqlite +0 -0
  92. 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');
@@ -22,8 +22,11 @@ const handleExists = require('./db/transformExists');
22
22
  const { usesMixinAssociation, getMixinAssocOfQueryIfPublished } = require('./db/helpers');
23
23
  const replaceAssociationsInGroupByOrderBy = require('./db/groupByOrderBy');
24
24
  const _forEachDefinition = require('../model/csnUtils').forEachDefinition;
25
+ const flattening = require('./db/flattening');
26
+ const expansion = require('./db/expansion');
25
27
  const assertUnique = require('./db/assertUnique');
26
28
  const generateDrafts = require('./db/draft');
29
+ const enrichUniversalCsn = require('./universalCsnEnricher');
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
@@ -121,14 +116,20 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
121
116
  /** @type {() => void} */
122
117
  let throwWithError;
123
118
  let artifactRef, inspectRef, queryOrMain, effectiveType, // csnRefs
124
- addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType, getFinalBaseType; // transformUtils
119
+ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType, getFinalBaseType, // transformUtils
120
+ get$combined; // csnUtils
125
121
 
126
122
  bindCsnReference();
127
123
 
128
124
  throwWithError(); // reclassify and throw in case of non-configurable errors
125
+
126
+ if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
127
+ enrichUniversalCsn(csn, options);
128
+ bindCsnReference();
129
+ }
129
130
 
130
131
  const dialect = options.forHana && options.forHana.dialect || options.toSql && options.toSql.dialect;
131
- const doA2J = !options.forHana.keepStructsAssocs;
132
+ const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
132
133
  if (!doA2J)
133
134
  forEachDefinition(csn, handleMixinOnConditions);
134
135
 
@@ -142,25 +143,48 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
142
143
  // subsequent procession steps (especially a2j) to see plain paths in expressions.
143
144
  // If errors are detected, throwWithError() will return from further processing
144
145
 
145
- forEachDefinition(csn, expandStructsInExpression);
146
+ expandStructsInExpression(csn, { drillRef: true });
146
147
 
147
148
  throwWithError();
148
149
 
149
150
  // FIXME: This does something very similar to cloneWithTransformations -> refactor?
150
151
  const transformCsn = transformUtils.transformModel;
151
152
 
152
- handleExists(csn, error);
153
+ handleExists(csn, options, error);
153
154
 
154
155
  // (001) Add a temporal where condition to views where applicable before assoc2join
155
156
  // assoc2join eventually rewrites the table aliases
156
157
  forEachDefinition(csn, addTemporalWhereConditionToView);
157
158
 
158
- // check unique constraints - further processing is done in rewriteUniqueConstraints`
159
+ // check unique constraints - further processing is done in rewriteUniqueConstraints
159
160
  assertUnique.prepare(csn, options, error, info);
160
161
 
162
+ if(doA2J) {
163
+ // Expand a structured thing in: keys, columns, order by, group by
164
+ expansion.expandStructureReferences(csn, options, pathDelimiter, {error, info, throwWithError});
165
+ bindCsnReference();
166
+ }
167
+
161
168
  // Remove properties attached by validator - they do not "grow" as the model grows.
162
169
  cleanup();
163
170
 
171
+ bindCsnReferenceOnly();
172
+
173
+
174
+ if(doA2J) {
175
+ const resolved = new WeakMap();
176
+ // No refs with struct-steps exist anymore
177
+ flattening.flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter);
178
+ // No type references exist anymore
179
+ // Needs to happen exactly between flattenAllStructStepsInRefs and flattenElements to keep model resolvable.
180
+ flattening.resolveTypeReferences(csn, options, resolved, pathDelimiter);
181
+ // No structured elements exists anymore
182
+ flattening.flattenElements(csn, options, pathDelimiter, error);
183
+ } else {
184
+ // For to.hdbcds with naming mode hdbcds we also need to resolve the types
185
+ flattening.resolveTypeReferences(csn, options, undefined, pathDelimiter);
186
+ }
187
+
164
188
  // (010) If requested, translate associations to joins
165
189
  if (doA2J)
166
190
  handleAssocToJoins();
@@ -188,6 +212,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
188
212
  }
189
213
  });
190
214
 
215
+ // Must happen after A2J, as A2J needs $self to correctly resolve stuff
216
+ if(doA2J)
217
+ flattening.removeLeadingSelf(csn);
218
+
191
219
  const {
192
220
  flattenStructuredElement,
193
221
  flattenStructStepsInRef, getForeignKeyArtifact,
@@ -242,13 +270,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
242
270
  },
243
271
  }, true);
244
272
 
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
273
  // (040) Ignore entities and views that are abstract or implemented
253
274
  // or carry the annotation cds.persistence.skip/exists
254
275
  // These entities are not removed from the csn, but flagged as "to be ignored"
@@ -260,57 +281,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
260
281
  // Temporal only in beta-mode
261
282
  forEachDefinition(csn, handleTemporalAnnotations);
262
283
 
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
-
284
+ handleManagedAssociationsAndCreateForeignKeys();
285
+
314
286
  function handleManagedAssociationsAndCreateForeignKeys() {
315
287
  forEachDefinition(csn, (art, artName) => handleManagedAssociationFKs(art, artName));
316
288
  forEachDefinition(csn, (art, artName) => createForeignKeyElements(art, artName));
@@ -366,8 +338,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
366
338
  * hence we do not generate the referential constraints for them.
367
339
  */
368
340
  const validOptionsForConstraint = () => {
369
- return (options.forHana.dialect === 'sqlite' || options.forHana.dialect === 'hana') &&
370
- !(options.toHana && options.toHana.names === 'hdbcds')
341
+ return (options.forHana.dialect === 'sqlite' || options.forHana.dialect === 'hana') && doA2J;
371
342
  }
372
343
  if(validOptionsForConstraint())
373
344
  createReferentialConstraints(csn, options);
@@ -390,12 +361,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
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
  };
@@ -424,7 +389,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
424
389
  /* Check Type Parameters (precision, scale, length ...) */
425
390
  checkTypeParameters,
426
391
  /* Filter out aspects/types/abstract entities containing managed compositions of anonymous aspects */
427
- ignoreNonPersistedArtifactsWithAnonymousAspectComposition
392
+ ignoreNonPersistedArtifactsWithAnonymousAspectComposition,
393
+ // (200) Strip 'key' property from type elements
394
+ removeKeyPropInType,
428
395
  ]);
429
396
 
430
397
  throwWithError();
@@ -439,7 +406,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
439
406
  '_ignore': function (parent, a, b, path){
440
407
  if(path.length > 2) {
441
408
  const tail = path[path.length-1];
442
- const parentParent = walkCsnPath(path.slice(0, -1));
409
+ const parentPath = path.slice(0, -1)
410
+ const parentParent = walkCsnPath(csn, parentPath);
443
411
  delete parentParent[tail];
444
412
  } else {
445
413
  delete parent._ignore;
@@ -478,7 +446,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
478
446
  * @param {string} artName
479
447
  */
480
448
  function createForeignKeyElements(art, artName) {
481
- if ((art.kind === 'entity' || art.kind === 'view') && !options.forHana.keepStructsAssocs) {
449
+ if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
482
450
  forAllElements(art, artName, (parent, elements, pathToElements) => {
483
451
  const elementsArray = [];
484
452
  forEachGeneric(parent, 'elements', (element, elemName) => {
@@ -529,7 +497,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
529
497
  function bindCsnReference(){
530
498
  ({ error, warning, info, throwWithError } = makeMessageFunction(csn, options, moduleName));
531
499
  ({ artifactRef, inspectRef, queryOrMain, effectiveType } = csnRefs(csn));
532
- ({ getFinalBaseType } = getUtils(csn));
500
+ ({ getFinalBaseType, get$combined } = getUtils(csn));
533
501
  ({ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter));
534
502
  }
535
503
 
@@ -568,12 +536,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
568
536
  if(onConditionPart.ref && (onConditionPart.ref[0] === '$projection' || onConditionPart.ref[0] === '$self')){
569
537
  const { links } = inspectRef(path.concat(['on', i]));
570
538
  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
539
  columnToReplace = onConditionPart.ref[links.length - 1];
578
540
  }
579
541
  }
@@ -648,16 +610,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
648
610
  * @param {CSN.Artifact} artifact
649
611
  * @param {string} artifactName
650
612
  */
651
- function recursiveChecks(artifact, artifactName) {
613
+ function removeKeyPropInType(artifact, artifactName) {
652
614
  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
-
615
+ forEachMemberRecursively(artifact, (member) => {
661
616
  if (artifact.kind === 'type' && member.key)
662
617
  delete member.key;
663
618
  }, [ 'definitions', artifactName ]);
@@ -720,7 +675,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
720
675
  if (artifact.params) {
721
676
  // HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
722
677
  // 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`);
678
+ error(null, path, 'Unexpected association in parameterized view');
724
679
  }
725
680
  else if(artifact['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
726
681
  // UDF/CVs w/o params don't support 'WITH ASSOCIATIONS'
@@ -729,7 +684,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
729
684
  if (csn.definitions[member.target].params) {
730
685
  // HANA does not allow association targets with parameters or to UDFs/CVs w/o parameters:
731
686
  // 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`);
687
+ error(null, path, 'Unexpected parameterized association target');
733
688
  }
734
689
  else if(csn.definitions[member.target]['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
735
690
  // HANA won't check the assoc target but when querying an association with target UDF, this is the error:
@@ -760,7 +715,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
760
715
  const elem = elements[elemName];
761
716
  // (140) Generate foreign key elements and ON-condition for managed associations
762
717
  // (unless explicitly asked to keep assocs unchanged)
763
- if (!options.forHana.keepStructsAssocs) {
718
+ if (doA2J) {
764
719
  if (isManagedAssociationElement(elem))
765
720
  transformManagedAssociation(parent, artifactName, elem, elemName);
766
721
  }
@@ -771,7 +726,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
771
726
 
772
727
  function fixBorkedElementsOfLocalized(elements, pathToElements){
773
728
  const pathToNonLocalized = ['definitions', pathToElements[1].replace('localized.',''), ...pathToElements.slice(2)];
774
- const nonLocalizedElements = walkCsnPath(pathToNonLocalized);
729
+ const nonLocalizedElements = walkCsnPath(csn, pathToNonLocalized);
775
730
 
776
731
 
777
732
  for(const elementName in elements){
@@ -818,7 +773,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
818
773
  else {
819
774
  for (const pname in artifact.params) {
820
775
  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`);
776
+ warning(null, [ 'definitions', artifactName, 'params', pname ], `Expecting regular SQL-Identifier`);
822
777
  }
823
778
  else if (options.forHana.names !== 'plain' && pname.toUpperCase() !== pname) { // not plain mode: param name must be all upper
824
779
  warning(null, [ 'definitions', artifactName, 'params', pname ], { name: options.forHana.names },
@@ -872,9 +827,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
872
827
  }
873
828
 
874
829
  function handleAssocToJoins() {
830
+ // With flattening errors, it makes little sense to continue.
831
+ throwWithError();
875
832
  // the augmentor isn't able to deal with technical configurations and since assoc2join can ignore it we
876
833
  // simply make it invisible and copy it over to the result csn
877
834
  forEachDefinition(csn, art => art.technicalConfig && setProp(art, 'technicalConfig', art.technicalConfig));
835
+
878
836
  const newCsn = translateAssocsToJoinsCSN(csn, options);
879
837
 
880
838
  // restore all (non-enumerable) properties that wouldn't survive reaugmentation/compactification into the new compact model
@@ -896,7 +854,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
896
854
  const mixinElement = q.SELECT.mixin[mixinName];
897
855
  if (mixinElement._ignore && options.toSql) {
898
856
  columnClearer.push(() => {
899
- const query = walkCsnPath(p);
857
+ const query = walkCsnPath(csn, p);
900
858
  for(let i = query.columns.length-1; i > -1; i--){
901
859
  const col = query.columns[i];
902
860
  if(col && col.ref && col.ref[0] === mixinName){
@@ -1056,127 +1014,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1056
1014
  }
1057
1015
  }
1058
1016
 
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
1017
  /**
1181
1018
  * Get all elements tagged with @cds.valid.from/to from the union of all entities of the from-clause.
1182
1019
  *
@@ -1261,16 +1098,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1261
1098
  replaceEnumSymbolsByValues(obj, path);
1262
1099
  }
1263
1100
 
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
-
1271
- return obj;
1272
- }
1273
-
1274
1101
  // Change the names of those builtin types that have different names in HANA.
1275
1102
  // (do that directly in the csn where the builtin types are defined, so that
1276
1103
  // all users of the types benefit from it). Also add the type parameter 'length'
@@ -1357,7 +1184,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1357
1184
  });
1358
1185
  }
1359
1186
  }
1360
- if (query && options.toHana) {
1187
+ if (query && options.transformation === 'hdbcds') {
1361
1188
  // check all queries/subqueries for mixin publishing inside of unions -> forbidden in hdbcds
1362
1189
  if (query.SELECT && query.SELECT.mixin && path.indexOf('SET') !== -1) {
1363
1190
  for (const elementName in elements) {
@@ -1425,7 +1252,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1425
1252
 
1426
1253
  // For associations - make sure that the foreign keys have the same "style"
1427
1254
  // If A.assoc => A.assoc_id, else if assoc => assoc_id or assoc as Assoc => Assoc_id
1428
- if (elem.keys && !options.forHana.keepStructsAssocs) {
1255
+ if (elem.keys && doA2J) {
1429
1256
  const assoc_col = columnMap[elemName];
1430
1257
  if (assoc_col && assoc_col.ref) {
1431
1258
  elem.keys.forEach((key) => {
@@ -1449,7 +1276,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1449
1276
  }
1450
1277
  // Add flattened structured things preserving aliases and refs with/without table alias
1451
1278
  // 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) {
1279
+ if (isStructured(elem) && doA2J) {
1453
1280
  const col = columnMap[elemName];
1454
1281
  const originalName = col.ref[col.ref.length - 1];
1455
1282
  const flatElements = flattenStructuredElement(elem, originalName, [], path);
@@ -1474,21 +1301,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1474
1301
  if (!elem.on && !elem._ignore)
1475
1302
  hasNonAssocElements = true;
1476
1303
 
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
1304
  // (180 b) Create MIXINs for association elements in projections or views (those that are not mixins by themselves)
1489
1305
  // CDXCORE-585: Allow mixin associations to be used and published in parallel
1490
1306
  if (query !== undefined && elem.target) {
1491
- if(isUnion(path) && options.toHana){
1307
+ if(isUnion(path) && options.transformation === 'hdbcds'){
1492
1308
  if(isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J){
1493
1309
  if(elem.keys) {
1494
1310
  info(null, path, `Managed association "${elemName}", published in a UNION, will be ignored`)
@@ -1500,7 +1316,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1500
1316
  else {
1501
1317
  error(null, path, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`)
1502
1318
  }
1503
- } else if(path.length > 4 && options.toHana){ // path.length > 4 -> is a subquery
1319
+ } else if(path.length > 4 && options.transformation === 'hdbcds'){ // path.length > 4 -> is a subquery
1504
1320
  error(null, path, { name: elemName },
1505
1321
  'Association $(NAME) can\'t be published in a subquery')
1506
1322
  } else {
@@ -1713,23 +1529,41 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1713
1529
  const assoc = inspectRef(path.concat([ i + 2 ])).art;
1714
1530
  if (multipleExprs)
1715
1531
  result.push('(');
1532
+ const backlinkName = xprArgs[i + 2].ref[xprArgs[i + 2].ref.length - 1];
1716
1533
  result.push(...transformDollarSelfComparison(xprArgs[i + 2],
1717
1534
  assoc,
1718
- xprArgs[i + 2].ref[xprArgs[i + 2].ref.length - 1],
1535
+ backlinkName,
1719
1536
  elem, elemName, art, artName, path.concat([ i ])
1720
1537
  ));
1721
1538
  if (multipleExprs)
1722
1539
  result.push(')');
1723
1540
  i += 3;
1541
+ // remember name of backlink, important for foreign key constraints
1542
+ if(elem.$selfOnCondition)
1543
+ elem.$selfOnCondition.backlinkName += `_${ backlinkName }`;
1544
+ else {
1545
+ setProp(elem, '$selfOnCondition', {
1546
+ backlinkName
1547
+ }) // important for the foreign key constraints
1548
+ }
1724
1549
  }
1725
1550
  else if (isDollarSelfOrProjectionOperand(xprArgs[i + 2]) && isAssociationOperand(xprArgs[i], path.concat([ i ]))) {
1726
1551
  const assoc = inspectRef(path.concat([ i ])).art;
1727
1552
  if (multipleExprs)
1728
1553
  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 ])));
1554
+ const backlinkName = xprArgs[i].ref[xprArgs[i].ref.length - 1];
1555
+ result.push(...transformDollarSelfComparison(xprArgs[i], assoc, backlinkName, elem, elemName, art, artName, path.concat([ i + 2 ])));
1730
1556
  if (multipleExprs)
1731
1557
  result.push(')');
1732
1558
  i += 3;
1559
+ // remember name of backlink, important for foreign key constraints
1560
+ if(elem.$selfOnCondition)
1561
+ elem.$selfOnCondition.backlinkName += `_${ backlinkName }`;
1562
+ else {
1563
+ setProp(elem, '$selfOnCondition', {
1564
+ backlinkName
1565
+ }) // important for the foreign key constraints
1566
+ }
1733
1567
  }
1734
1568
  // Otherwise take one (!) token unchanged
1735
1569
  else {
@@ -1806,8 +1640,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1806
1640
  assoc.keys.forEach((k) => {
1807
1641
  // 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
1642
  // 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] }` ];
1643
+ const keyName = k.as && doA2J ? [k.as] : k.ref;
1644
+ const fKeyPath = !doA2J ? [ assocName, ...keyName ] : [ `${ assocName }${ pathDelimiter }${ keyName[0] }` ];
1811
1645
  // FIXME: _artifact to the args ???
1812
1646
  const a = [
1813
1647
  {
@@ -1882,10 +1716,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1882
1716
  return true;
1883
1717
  }
1884
1718
 
1885
- function insideColumns(path) {
1886
- return path.length >= 3 && path[path.length - 3] === 'SELECT' && path[path.length - 2] === 'columns';
1887
- }
1888
-
1889
1719
  /**
1890
1720
  * @param {CSN.Artifact} artifact
1891
1721
  * @param {string} artifactName
@@ -1982,119 +1812,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1982
1812
  return !(options.toSql && type === 'cds.String' && (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain'));
1983
1813
  }
1984
1814
 
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
1815
  /**
2099
1816
  * Flatten and create the foreign key elements of managed associaitons
2100
1817
  *
@@ -2102,7 +1819,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2102
1819
  * @param {string} artName
2103
1820
  */
2104
1821
  function handleManagedAssociationFKs(art, artName) {
2105
- if ((art.kind === 'entity' || art.kind === 'view') && !options.forHana.keepStructsAssocs) {
1822
+ if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
2106
1823
  forAllElements(art, artName, (parent, elements, pathToElements) => {
2107
1824
  if(artName.startsWith('localized.') && pathToElements.length > 3) {
2108
1825
  // In subqueries, the elements of localized views are missing all the important bits and pieces...
@@ -2246,7 +1963,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2246
1963
  function flattenIndexes(art, artName) {
2247
1964
  // Flatten structs in indexes (unless explicitly asked to keep structs)
2248
1965
  const tc = art.technicalConfig;
2249
- if ((art.kind === 'entity' || art.kind === 'view') && !options.forHana.keepStructsAssocs) {
1966
+ if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
2250
1967
  if (tc && tc[dialect]) {
2251
1968
  // Secondary and fulltext indexes
2252
1969
  for (const name in tc[dialect].indexes) {
@@ -2313,7 +2030,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2313
2030
  function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
2314
2031
  for (const elemName in artifact.elements) {
2315
2032
  const elem = artifact.elements[elemName];
2316
- if (!options.forHana.keepStructsAssocs) {
2033
+ if (doA2J) {
2317
2034
  // The association is an unmanaged on
2318
2035
  if (!elem.keys && elem.target && elem.on) {
2319
2036
  forEachRef(elem.on, (ref, refOwner, path) => {