@sap/cds-compiler 2.4.4 → 2.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +241 -1
  2. package/bin/.eslintrc.json +17 -0
  3. package/bin/cds_update_identifiers.js +8 -7
  4. package/bin/cdsc.js +180 -132
  5. package/bin/cdshi.js +18 -11
  6. package/bin/cdsse.js +38 -32
  7. package/bin/cdsv2m.js +8 -7
  8. package/doc/CHANGELOG_BETA.md +36 -1
  9. package/lib/api/main.js +81 -100
  10. package/lib/api/options.js +17 -11
  11. package/lib/api/validate.js +12 -8
  12. package/lib/backends.js +0 -81
  13. package/lib/base/keywords.js +32 -2
  14. package/lib/base/location.js +2 -2
  15. package/lib/base/message-registry.js +66 -4
  16. package/lib/base/messages.js +84 -27
  17. package/lib/base/model.js +2 -61
  18. package/lib/checks/arrayOfs.js +0 -1
  19. package/lib/checks/defaultValues.js +27 -2
  20. package/lib/checks/elements.js +1 -6
  21. package/lib/checks/enricher.js +8 -2
  22. package/lib/checks/foreignKeys.js +0 -6
  23. package/lib/checks/managedWithoutKeys.js +17 -0
  24. package/lib/checks/nonexpandableStructured.js +38 -0
  25. package/lib/checks/onConditions.js +9 -45
  26. package/lib/checks/queryNoDbArtifacts.js +27 -9
  27. package/lib/checks/selectItems.js +25 -2
  28. package/lib/checks/types.js +26 -2
  29. package/lib/checks/unknownMagic.js +38 -0
  30. package/lib/checks/utils.js +61 -0
  31. package/lib/checks/validator.js +66 -13
  32. package/lib/compiler/assert-consistency.js +24 -12
  33. package/lib/compiler/builtins.js +2 -0
  34. package/lib/compiler/checks.js +6 -4
  35. package/lib/compiler/definer.js +101 -39
  36. package/lib/compiler/index.js +88 -59
  37. package/lib/compiler/resolver.js +455 -209
  38. package/lib/compiler/shared.js +57 -33
  39. package/lib/edm/annotations/genericTranslation.js +183 -187
  40. package/lib/edm/csn2edm.js +128 -99
  41. package/lib/edm/edm.js +18 -21
  42. package/lib/edm/edmPreprocessor.js +361 -127
  43. package/lib/edm/edmUtils.js +103 -33
  44. package/lib/gen/Dictionary.json +74 -28
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +18 -4
  47. package/lib/gen/language.tokens +124 -118
  48. package/lib/gen/languageLexer.interp +13 -1
  49. package/lib/gen/languageLexer.js +870 -839
  50. package/lib/gen/languageLexer.tokens +116 -111
  51. package/lib/gen/languageParser.js +5894 -5614
  52. package/lib/json/from-csn.js +152 -67
  53. package/lib/json/to-csn.js +334 -135
  54. package/lib/language/antlrParser.js +4 -3
  55. package/lib/language/errorStrategy.js +1 -0
  56. package/lib/language/genericAntlrParser.js +24 -14
  57. package/lib/language/language.g4 +188 -128
  58. package/lib/main.d.ts +435 -0
  59. package/lib/main.js +31 -7
  60. package/lib/model/api.js +78 -0
  61. package/lib/model/csnRefs.js +463 -187
  62. package/lib/model/csnUtils.js +280 -136
  63. package/lib/model/enrichCsn.js +75 -4
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/modelCompare/compare.js +70 -25
  66. package/lib/optionProcessor.js +13 -10
  67. package/lib/render/.eslintrc.json +4 -1
  68. package/lib/render/DuplicateChecker.js +8 -5
  69. package/lib/render/toCdl.js +123 -40
  70. package/lib/render/toHdbcds.js +156 -65
  71. package/lib/render/toSql.js +87 -11
  72. package/lib/render/utils/common.js +55 -9
  73. package/lib/render/utils/sql.js +3 -3
  74. package/lib/sql-identifier.js +6 -1
  75. package/lib/transform/{sql → db}/.eslintrc.json +0 -0
  76. package/lib/transform/{sql → db}/assertUnique.js +7 -8
  77. package/lib/transform/{sql → db}/constraints.js +35 -20
  78. package/lib/transform/db/draft.js +353 -0
  79. package/lib/transform/db/expansion.js +582 -0
  80. package/lib/transform/db/flattening.js +325 -0
  81. package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
  82. package/lib/transform/{sql → db}/helpers.js +0 -0
  83. package/lib/transform/{sql → db}/transformExists.js +256 -60
  84. package/lib/transform/forHanaNew.js +216 -765
  85. package/lib/transform/forOdataNew.js +60 -56
  86. package/lib/transform/localized.js +48 -26
  87. package/lib/transform/odata/attachPath.js +19 -4
  88. package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
  89. package/lib/transform/odata/generateForeignKeyElements.js +13 -12
  90. package/lib/transform/odata/referenceFlattener.js +60 -36
  91. package/lib/transform/odata/sortByAssociationDependency.js +4 -4
  92. package/lib/transform/odata/structuralPath.js +76 -0
  93. package/lib/transform/odata/structureFlattener.js +21 -22
  94. package/lib/transform/odata/toFinalBaseType.js +5 -5
  95. package/lib/transform/odata/typesExposure.js +27 -17
  96. package/lib/transform/odata/utils.js +2 -2
  97. package/lib/transform/transformUtilsNew.js +141 -77
  98. package/lib/transform/translateAssocsToJoins.js +17 -14
  99. package/lib/transform/universalCsnEnricher.js +67 -0
  100. package/lib/utils/file.js +0 -11
  101. package/lib/utils/moduleResolve.js +6 -8
  102. package/lib/utils/timetrace.js +6 -1
  103. package/package.json +2 -1
  104. package/lib/base/deepCopy.js +0 -66
  105. package/lib/json/walker.js +0 -26
  106. package/lib/utils/string.js +0 -17
@@ -1,13 +1,12 @@
1
1
  'use strict';
2
2
 
3
- const { setProp, isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
3
+ const { setProp, isBetaEnabled } = require('../base/model');
4
4
  const { getUtils, cloneCsn, forEachGeneric,
5
5
  forEachMember,
6
6
  forEachMemberRecursively, forEachRef,
7
- forAllQueries, forAllElements, hasBoolAnnotation, getArtifactDatabaseNameOf,
7
+ forAllQueries, forAllElements, hasAnnotationValue, getArtifactDatabaseNameOf,
8
8
  getElementDatabaseNameOf, isBuiltinType, applyTransformations,
9
- isPersistedOnDatabase, getNormalizedQuery, isAspect, getResultingName,
10
- getServiceNames
9
+ isPersistedOnDatabase, getNormalizedQuery, isAspect, walkCsnPath,
11
10
  } = require('../model/csnUtils');
12
11
  const { makeMessageFunction } = require('../base/messages');
13
12
  const transformUtils = require('./transformUtilsNew');
@@ -17,13 +16,17 @@ const { checkCSNVersion } = require('../json/csnVersion');
17
16
  const validate = require('../checks/validator');
18
17
  const { addLocalizationViewsWithJoins, addLocalizationViews } = require('../transform/localized');
19
18
  const timetrace = require('../utils/timetrace');
20
- const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./sql/constraints');
19
+ const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
21
20
  const { createDict } = require('../utils/objectUtils');
22
- const handleExists = require('./sql/transformExists');
23
- const { usesMixinAssociation, getMixinAssocOfQueryIfPublished } = require('./sql/helpers');
24
- const replaceAssociationsInGroupByOrderBy = require('./sql/groupByOrderBy');
21
+ const handleExists = require('./db/transformExists');
22
+ const { usesMixinAssociation, getMixinAssocOfQueryIfPublished } = require('./db/helpers');
23
+ const replaceAssociationsInGroupByOrderBy = require('./db/groupByOrderBy');
25
24
  const _forEachDefinition = require('../model/csnUtils').forEachDefinition;
26
- const assertUnique = require('./sql/assertUnique');
25
+ const flattening = require('./db/flattening');
26
+ const expansion = require('./db/expansion');
27
+ const assertUnique = require('./db/assertUnique');
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
@@ -105,15 +100,13 @@ 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 draftSuffix = isDeprecatedEnabled(options, 'generatedEntityNameWithUnderscore') ? '_drafts' : '.drafts';
109
103
  const columnClearer = [];
110
104
  // copy the model as we don't want to change the input model
111
105
  timetrace.start('HANA transformation');
112
106
  /** @type {CSN.Model} */
113
107
  let csn = cloneCsn(inputModel, options);
114
108
 
115
- // All services of the model - needed for drafts
116
- const allServices = getServiceNames(inputModel);
109
+
117
110
 
118
111
  checkCSNVersion(csn, options);
119
112
 
@@ -123,14 +116,20 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
123
116
  /** @type {() => void} */
124
117
  let throwWithError;
125
118
  let artifactRef, inspectRef, queryOrMain, effectiveType, // csnRefs
126
- addDefaultTypeFacets, expandStructsInOnConditions, toFinalBaseType, getFinalBaseType; // transformUtils
119
+ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType, getFinalBaseType, // transformUtils
120
+ get$combined; // csnUtils
127
121
 
128
122
  bindCsnReference();
129
123
 
130
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
+ }
131
130
 
132
131
  const dialect = options.forHana && options.forHana.dialect || options.toSql && options.toSql.dialect;
133
- const doA2J = !options.forHana.keepStructsAssocs;
132
+ const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
134
133
  if (!doA2J)
135
134
  forEachDefinition(csn, handleMixinOnConditions);
136
135
 
@@ -139,34 +138,59 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
139
138
  error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, getFinalBaseType, isAspect
140
139
  });
141
140
 
142
- // Check if structured elements and managed associations are compared in an ON condition
141
+ // Check if structured elements and managed associations are compared in an expression
143
142
  // and expand these structured elements. This tuple expansion allows all other
144
- // subsequent procession steps (especially a2j) to see plain paths in ON conditions.
143
+ // subsequent procession steps (especially a2j) to see plain paths in expressions.
145
144
  // If errors are detected, throwWithError() will return from further processing
146
145
 
147
- forEachDefinition(csn, expandStructsInOnConditions);
146
+ expandStructsInExpression(csn, { drillRef: true });
148
147
 
149
148
  throwWithError();
150
149
 
151
150
  // FIXME: This does something very similar to cloneWithTransformations -> refactor?
152
151
  const transformCsn = transformUtils.transformModel;
153
152
 
154
- handleExists(csn, error);
153
+ handleExists(csn, options, error);
155
154
 
156
155
  // (001) Add a temporal where condition to views where applicable before assoc2join
157
156
  // assoc2join eventually rewrites the table aliases
158
157
  forEachDefinition(csn, addTemporalWhereConditionToView);
159
158
 
160
- // check unique constraints - further processing is done in rewriteUniqueConstraints`
159
+ // check unique constraints - further processing is done in rewriteUniqueConstraints
161
160
  assertUnique.prepare(csn, options, error, info);
162
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
+
163
168
  // Remove properties attached by validator - they do not "grow" as the model grows.
164
169
  cleanup();
165
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
+
166
188
  // (010) If requested, translate associations to joins
167
189
  if (doA2J)
168
190
  handleAssocToJoins();
169
191
 
192
+ bindCsnReference();
193
+
170
194
  const redoProjections = [];
171
195
  // Use the "raw" forEachDefinition here to ensure that the _ignore takes effect
172
196
  _forEachDefinition(csn, (artifact) => {
@@ -188,12 +212,14 @@ 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
- flattenStructStepsInRef, createForeignKeyElement, getForeignKeyArtifact,
221
+ flattenStructStepsInRef, getForeignKeyArtifact,
194
222
  isAssociationOperand, isDollarSelfOrProjectionOperand,
195
- createAndAddDraftAdminDataProjection, createScalarElement, createAssociationElement,
196
- addElement, copyAndAddElement, createAssociationPathComparison,
197
223
  extractValidFromToKeyElement, checkAssignment, checkMultipleAssignments,
198
224
  recurseElements
199
225
  } = transformUtils.getTransformers(csn, options, pathDelimiter);
@@ -201,7 +227,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
201
227
  const {
202
228
  getCsnDef,
203
229
  isAssocOrComposition,
204
- isComposition,
205
230
  isManagedAssociationElement,
206
231
  isStructured,
207
232
  addStringAnnotationTo,
@@ -245,13 +270,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
245
270
  },
246
271
  }, true);
247
272
 
248
- // (030) - For all elements, replace derived types by final base type
249
- forEachDefinition(csn, (artifact) => {
250
- forEachMemberRecursively(artifact, (member) => {
251
- toFinalBaseType(member);
252
- });
253
- });
254
-
255
273
  // (040) Ignore entities and views that are abstract or implemented
256
274
  // or carry the annotation cds.persistence.skip/exists
257
275
  // These entities are not removed from the csn, but flagged as "to be ignored"
@@ -263,56 +281,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
263
281
  // Temporal only in beta-mode
264
282
  forEachDefinition(csn, handleTemporalAnnotations);
265
283
 
266
- // Basic flattening of views and entities
267
- // These steps need happen in isolation so that
268
- // the following step can rely on the rest of the model having
269
- // a certain structure, i.e. structs unfolded
270
- if(!options.forHana.keepStructsAssocs) {
271
- const flatteningOfStructured = [];
272
- const adaptRefs = [];
273
- // A2J resolves paths in queries to their flattened version,
274
- // that is the foreign key of a managed assoc that will be generated in a later step
275
- // we need to adapt the refs after the foreign keys are generated
276
- const adaptQueryRefLater = [];
277
-
278
- const fkRefs = new WeakMap();
279
-
280
- applyTransformations(csn, {
281
- keys: (parent, prop, keys) => {
282
- keys.forEach(key => fkRefs.set(key.ref, true));
283
- },
284
- ref: (parent, prop, ref, path) => {
285
- // Do not process fk refs - no need to adapt
286
- if(fkRefs.has(ref))
287
- return;
288
-
289
- setProp(parent, '$path', [...path]);
290
- const lastRef = ref[ref.length-1];
291
- const fn = () => {
292
- const scopedPath = [...parent.$path];
293
- parent.ref = flattenStructStepsInRef(ref, scopedPath);
294
- // Explicitly set implicit alias for things that are now flattened - but only in columns
295
- if (parent.ref[ref.length - 1] != lastRef && insideColumns(scopedPath) && !parent.as)
296
- parent.as = lastRef;
297
- };
298
- // adapt queries later
299
- const enclosingArtifact = csn.definitions[path[1]];
300
- if(enclosingArtifact.query)
301
- adaptQueryRefLater.push(fn);
302
- else
303
- adaptRefs.push(fn);
304
- }
305
- }, [(definitions, artifactName, artifact) => flatteningOfStructured.push(() => flattenStructuredElements(artifact, artifactName))]);
306
-
307
- adaptRefs.forEach(fn => fn());
308
- flatteningOfStructured.forEach(fn => fn());
309
- handleManagedAssociationsAndCreateForeignKeys();
310
- // now the foreign key references in queries are resolvable
311
- adaptQueryRefLater.forEach(fn => fn());
312
- }else {
313
- handleManagedAssociationsAndCreateForeignKeys();
314
- }
315
-
284
+ handleManagedAssociationsAndCreateForeignKeys();
285
+
316
286
  function handleManagedAssociationsAndCreateForeignKeys() {
317
287
  forEachDefinition(csn, (art, artName) => handleManagedAssociationFKs(art, artName));
318
288
  forEachDefinition(csn, (art, artName) => createForeignKeyElements(art, artName));
@@ -368,18 +338,13 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
368
338
  * hence we do not generate the referential constraints for them.
369
339
  */
370
340
  const validOptionsForConstraint = () => {
371
- return (options.forHana.dialect === 'sqlite' || options.forHana.dialect === 'hana') &&
372
- !(options.toHana && options.toHana.names === 'hdbcds')
341
+ return (options.forHana.dialect === 'sqlite' || options.forHana.dialect === 'hana') && doA2J;
373
342
  }
374
343
  if(validOptionsForConstraint())
375
344
  createReferentialConstraints(csn, options);
376
345
  }
377
346
 
378
- // (015) Generate artificial draft shadow entities if requested
379
- // Note that this needs to happen after implicit redirection has been performed, because it checks
380
- // for all draft nodes (additional artifacts reachable via compositions) to be part of a service.
381
- // This is typically achieved only by means of implicit redirection.
382
- forEachDefinition(csn, generateDraft);
347
+ generateDrafts(csn, options, pathDelimiter, { info, warning, error });
383
348
 
384
349
  // Set the final constraint paths and produce hana tc indexes if required
385
350
  // See function comment for extensive information.
@@ -396,12 +361,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
396
361
  // Recursively apply transformCommon and attach @cds.persistence.name
397
362
  forEachDefinition(csn, recursivelyApplyCommon);
398
363
 
399
- // (20 a) If we keep associations as they are (hdbcds naming convention), we cannot have structured
400
- // view elements (we could enumerate the elements but we can't give them the names one would expect)
401
- // Check foreign keys of redirected associations
402
- // (200) Strip 'key' property from type elements
403
- forEachDefinition(csn, recursiveChecks);
404
-
405
364
  const checkConstraintIdentifiers = (artifact, artifactName, prop, path) => {
406
365
  assertConstraintIdentifierUniqueness(artifact, artifactName, path, error);
407
366
  };
@@ -430,7 +389,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
430
389
  /* Check Type Parameters (precision, scale, length ...) */
431
390
  checkTypeParameters,
432
391
  /* Filter out aspects/types/abstract entities containing managed compositions of anonymous aspects */
433
- ignoreNonPersistedArtifactsWithAnonymousAspectComposition
392
+ ignoreNonPersistedArtifactsWithAnonymousAspectComposition,
393
+ // (200) Strip 'key' property from type elements
394
+ removeKeyPropInType,
434
395
  ]);
435
396
 
436
397
  throwWithError();
@@ -445,7 +406,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
445
406
  '_ignore': function (parent, a, b, path){
446
407
  if(path.length > 2) {
447
408
  const tail = path[path.length-1];
448
- const parentParent = walkCsnPath(path.slice(0, -1));
409
+ const parentPath = path.slice(0, -1)
410
+ const parentParent = walkCsnPath(csn, parentPath);
449
411
  delete parentParent[tail];
450
412
  } else {
451
413
  delete parent._ignore;
@@ -484,7 +446,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
484
446
  * @param {string} artName
485
447
  */
486
448
  function createForeignKeyElements(art, artName) {
487
- if ((art.kind === 'entity' || art.kind === 'view') && !options.forHana.keepStructsAssocs) {
449
+ if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
488
450
  forAllElements(art, artName, (parent, elements, pathToElements) => {
489
451
  const elementsArray = [];
490
452
  forEachGeneric(parent, 'elements', (element, elemName) => {
@@ -535,8 +497,13 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
535
497
  function bindCsnReference(){
536
498
  ({ error, warning, info, throwWithError } = makeMessageFunction(csn, options, moduleName));
537
499
  ({ artifactRef, inspectRef, queryOrMain, effectiveType } = csnRefs(csn));
538
- ({ getFinalBaseType } = getUtils(csn));
539
- ({ addDefaultTypeFacets, expandStructsInOnConditions, toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter));
500
+ ({ getFinalBaseType, get$combined } = getUtils(csn));
501
+ ({ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter));
502
+ }
503
+
504
+ function bindCsnReferenceOnly(){
505
+ // invalidate caches for CSN ref API
506
+ ({ artifactRef, inspectRef, queryOrMain, effectiveType } = csnRefs(csn));
540
507
  }
541
508
 
542
509
  function handleMixinOnConditions(artifact, artifactName) {
@@ -561,25 +528,36 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
561
528
  }
562
529
  , [ 'definitions', artifactName, 'query' ]);
563
530
  function getResolvedMixinOnCondition(csn, mixinAssociation, query, assocName, path){
564
- let { inspectRef } = csnRefs(csn);
531
+ const { inspectRef } = csnRefs(csn);
532
+ const referencedThroughStar = query.SELECT.columns.some((column) => column === '*');
565
533
  return mixinAssociation.on
566
534
  .map((onConditionPart, i) => {
567
535
  let columnToReplace;
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
  }
580
- const replaceWith = query.SELECT ? query.SELECT.columns
581
- .find((column) => column.as && column.as === columnToReplace || column.ref && column.ref[0] === columnToReplace ) :
582
- null;
542
+ if (!columnToReplace)
543
+ return onConditionPart;
544
+
545
+ const replaceWith = query.SELECT.columns.find((column) =>
546
+ column.as && column.as === columnToReplace ||
547
+ column.ref && column.ref[0] === columnToReplace
548
+ );
549
+ if (!replaceWith && referencedThroughStar) {
550
+ // not explicitly in column list, check query sources
551
+ // get$combined also includes elements which are part of "excluding {}"
552
+ // this shouldn't be an issue here, as such references get rejected
553
+ const elementsOfQuerySources = get$combined(query);
554
+ Object.entries(elementsOfQuerySources).forEach(([id, element]) => {
555
+ // if the ref points to an element which is not explicitly exposed in the column list,
556
+ // but through the '*' operator -> replace the $projection / $self with the correct source entity
557
+ if(id === columnToReplace)
558
+ onConditionPart.ref[0] = element[0].parent;
559
+ });
560
+ }
583
561
 
584
562
  // No implicit CAST in on-condition
585
563
  if(replaceWith && replaceWith.cast) {
@@ -632,16 +610,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
632
610
  * @param {CSN.Artifact} artifact
633
611
  * @param {string} artifactName
634
612
  */
635
- function recursiveChecks(artifact, artifactName) {
613
+ function removeKeyPropInType(artifact, artifactName) {
636
614
  if (!artifact._ignore) {
637
- forEachMemberRecursively(artifact, (member, memberName, property, path) => {
638
- if (options.forHana.keepStructsAssocs &&
639
- artifact.query &&
640
- isStructured(member)) {
641
- error(null, path, `With "hdbcds" naming, structured elements can't be used in a view`);
642
- return;
643
- }
644
-
615
+ forEachMemberRecursively(artifact, (member) => {
645
616
  if (artifact.kind === 'type' && member.key)
646
617
  delete member.key;
647
618
  }, [ 'definitions', artifactName ]);
@@ -663,36 +634,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
663
634
  }, [ 'definitions', artifactName ]);
664
635
  }
665
636
 
666
- /**
667
- * @param {CSN.Artifact} artifact
668
- * @param {string} artifactName
669
- */
670
- function generateDraft(artifact, artifactName) {
671
- if ((artifact.kind === 'entity' || artifact.kind === 'view') && hasBoolAnnotation(artifact, '@odata.draft.enabled')) {
672
- // Ignore if not part of a service
673
- if (!isPartOfService(artifactName)) {
674
- warning(null, [ 'definitions', artifactName ], 'Ignoring annotation “@odata.draft.enabled” as the artifact is not part of a service');
675
- return;
676
- }
677
-
678
- // Determine the set of target draft nodes belonging to this draft root (the draft root
679
- // itself plus all its transitively composition-reachable targets)
680
- const draftNodes = Object.create(null);
681
- collectDraftNodesInto(artifact, artifactName, artifact, draftNodes);
682
- // Draft-enable all of them
683
- for (const name in draftNodes)
684
- generateDraftForHana(draftNodes[name], name, artifactName);
685
-
686
- // Redirect associations/compositions between draft shadow nodes
687
- for (const name in draftNodes) {
688
- const shadowNode = csn.definitions[`${ name }${draftSuffix}`];
689
- // Might not exist because of previous errors
690
- if (shadowNode)
691
- redirectDraftTargets(csn.definitions[`${ name }${draftSuffix}`], draftNodes);
692
- }
693
- }
694
- }
695
-
696
637
  /**
697
638
  * @param {CSN.Artifact} artifact
698
639
  * @param {string} artifactName
@@ -734,7 +675,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
734
675
  if (artifact.params) {
735
676
  // HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
736
677
  // SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
737
- error(null, path, `Associations are not allowed in entities with parameters`);
678
+ error(null, path, 'Unexpected association in parameterized view');
738
679
  }
739
680
  else if(artifact['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
740
681
  // UDF/CVs w/o params don't support 'WITH ASSOCIATIONS'
@@ -743,7 +684,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
743
684
  if (csn.definitions[member.target].params) {
744
685
  // HANA does not allow association targets with parameters or to UDFs/CVs w/o parameters:
745
686
  // SAP DBTech JDBC: [7]: feature not supported: cannot support create association to a parameterized view
746
- error(null, path, `Associations can't point to entities with parameters`);
687
+ error(null, path, 'Unexpected parameterized association target');
747
688
  }
748
689
  else if(csn.definitions[member.target]['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
749
690
  // HANA won't check the assoc target but when querying an association with target UDF, this is the error:
@@ -774,7 +715,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
774
715
  const elem = elements[elemName];
775
716
  // (140) Generate foreign key elements and ON-condition for managed associations
776
717
  // (unless explicitly asked to keep assocs unchanged)
777
- if (!options.forHana.keepStructsAssocs) {
718
+ if (doA2J) {
778
719
  if (isManagedAssociationElement(elem))
779
720
  transformManagedAssociation(parent, artifactName, elem, elemName);
780
721
  }
@@ -785,7 +726,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
785
726
 
786
727
  function fixBorkedElementsOfLocalized(elements, pathToElements){
787
728
  const pathToNonLocalized = ['definitions', pathToElements[1].replace('localized.',''), ...pathToElements.slice(2)];
788
- const nonLocalizedElements = walkCsnPath(pathToNonLocalized);
729
+ const nonLocalizedElements = walkCsnPath(csn, pathToNonLocalized);
789
730
 
790
731
 
791
732
  for(const elementName in elements){
@@ -832,7 +773,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
832
773
  else {
833
774
  for (const pname in artifact.params) {
834
775
  if (pname.match(/\W/g) || pname.match(/^\d/) || pname.match(/^_/)) { // parameter name must be regular SQL identifier
835
- 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`);
836
777
  }
837
778
  else if (options.forHana.names !== 'plain' && pname.toUpperCase() !== pname) { // not plain mode: param name must be all upper
838
779
  warning(null, [ 'definitions', artifactName, 'params', pname ], { name: options.forHana.names },
@@ -848,7 +789,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
848
789
  * @param {string} artifactName
849
790
  */
850
791
  function handleQueryish(artifact, artifactName) {
851
- const stripQueryish = artifact.query && hasBoolAnnotation(artifact, '@cds.persistence.table');
792
+ const stripQueryish = artifact.query && hasAnnotationValue(artifact, '@cds.persistence.table');
852
793
 
853
794
  if (stripQueryish) {
854
795
  artifact.kind = 'entity';
@@ -869,13 +810,13 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
869
810
  function handleCdsPersistence(artifact, artifactName) {
870
811
  if (artifact.kind === 'entity' || artifact.kind === 'view') {
871
812
  if (artifact.abstract
872
- || hasBoolAnnotation(artifact, '@cds.persistence.skip')
873
- || hasBoolAnnotation(artifact, '@cds.persistence.exists'))
813
+ || hasAnnotationValue(artifact, '@cds.persistence.skip')
814
+ || hasAnnotationValue(artifact, '@cds.persistence.exists'))
874
815
  artifact._ignore = true;
875
816
 
876
817
  // issue #3450 HANA CDS can not handle external artifacts which are part of a HANA CDS context
877
818
  if (options.forHana.names === 'quoted' &&
878
- hasBoolAnnotation(artifact, '@cds.persistence.exists')) {
819
+ hasAnnotationValue(artifact, '@cds.persistence.exists')) {
879
820
  const firstPath = artifactName.split('.')[0];
880
821
  const topParent = csn.definitions[firstPath];
881
822
  // namespaces, contexts and services become contexts in HANA CDS
@@ -886,9 +827,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
886
827
  }
887
828
 
888
829
  function handleAssocToJoins() {
830
+ // With flattening errors, it makes little sense to continue.
831
+ throwWithError();
889
832
  // the augmentor isn't able to deal with technical configurations and since assoc2join can ignore it we
890
833
  // simply make it invisible and copy it over to the result csn
891
834
  forEachDefinition(csn, art => art.technicalConfig && setProp(art, 'technicalConfig', art.technicalConfig));
835
+
892
836
  const newCsn = translateAssocsToJoinsCSN(csn, options);
893
837
 
894
838
  // restore all (non-enumerable) properties that wouldn't survive reaugmentation/compactification into the new compact model
@@ -910,7 +854,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
910
854
  const mixinElement = q.SELECT.mixin[mixinName];
911
855
  if (mixinElement._ignore && options.toSql) {
912
856
  columnClearer.push(() => {
913
- const query = walkCsnPath(p);
857
+ const query = walkCsnPath(csn, p);
914
858
  for(let i = query.columns.length-1; i > -1; i--){
915
859
  const col = query.columns[i];
916
860
  if(col && col.ref && col.ref[0] === mixinName){
@@ -925,7 +869,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
925
869
  }
926
870
  });
927
871
  csn = newCsn;
928
- bindCsnReference();
929
872
  }
930
873
 
931
874
  /**
@@ -984,6 +927,25 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
984
927
  }
985
928
  }
986
929
 
930
+ function hasFalsyTemporalAnnotations(SELECT, elements, from, to) {
931
+ let fromElement = elements[from.name];
932
+ let toElement = elements[to.name];
933
+
934
+ if(SELECT.columns) {
935
+ for(const col of SELECT.columns) {
936
+ if(col.ref) {
937
+ const implicitAlias = implicitAs(col.ref);
938
+ if(implicitAlias === from.name)
939
+ fromElement = elements[col.as || implicitAlias];
940
+ else if(implicitAlias === to.name)
941
+ toElement = elements[col.as || implicitAlias];
942
+ }
943
+ }
944
+ }
945
+ const val = fromElement && toElement && hasAnnotationValue(fromElement, '@cds.valid.from', false) && hasAnnotationValue(toElement, '@cds.valid.to', false);
946
+ return val;
947
+ }
948
+
987
949
  /**
988
950
  * Add a where condition to views that
989
951
  * - are annotated with @cds.valid.from and @cds.valid.to,
@@ -1009,31 +971,33 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1009
971
  if (from.length === 1 && to.length === 1) {
1010
972
  // and both are from the same origin
1011
973
  if (from[0].source === to[0].source && from[0].parent === to[0].parent) {
1012
- const fromPath = {
1013
- ref: [
1014
- from[0].parent,
1015
- from[0].name,
1016
- ],
1017
- };
974
+ if(!hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0])) {
975
+ const fromPath = {
976
+ ref: [
977
+ from[0].parent,
978
+ from[0].name,
979
+ ],
980
+ };
1018
981
 
1019
- const toPath = {
1020
- ref: [
1021
- to[0].parent,
1022
- to[0].name,
1023
- ],
1024
- };
982
+ const toPath = {
983
+ ref: [
984
+ to[0].parent,
985
+ to[0].name,
986
+ ],
987
+ };
1025
988
 
1026
989
 
1027
- const atFrom = { ref: [ '$at', 'from' ] };
1028
- const atTo = { ref: [ '$at', 'to' ] };
990
+ const atFrom = { ref: [ '$at', 'from' ] };
991
+ const atTo = { ref: [ '$at', 'to' ] };
1029
992
 
1030
- const cond = [ '(', fromPath, '<', atTo, 'and', toPath, '>', atFrom, ')' ];
993
+ const cond = [ '(', fromPath, '<', atTo, 'and', toPath, '>', atFrom, ')' ];
1031
994
 
1032
- if (normalizedQuery.query.SELECT.where) { // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
1033
- normalizedQuery.query.SELECT.where = [ '(', ...normalizedQuery.query.SELECT.where, ')', 'and', ...cond ];
1034
- }
1035
- else {
1036
- normalizedQuery.query.SELECT.where = cond;
995
+ if (normalizedQuery.query.SELECT.where) { // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
996
+ normalizedQuery.query.SELECT.where = [ '(', ...normalizedQuery.query.SELECT.where, ')', 'and', ...cond ];
997
+ }
998
+ else {
999
+ normalizedQuery.query.SELECT.where = cond;
1000
+ }
1037
1001
  }
1038
1002
  }
1039
1003
  else {
@@ -1050,127 +1014,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1050
1014
  }
1051
1015
  }
1052
1016
 
1053
-
1054
- /**
1055
- * Compute and return $combined for the given query.
1056
- *
1057
- * @param {CSN.Query} query
1058
- * @returns {object}
1059
- */
1060
- function get$combined(query) {
1061
- const sources = getSources(query);
1062
- return sources;
1063
-
1064
- /**
1065
- * Get the union of all elements from the from clause
1066
- * - descend into unions, following the lead query
1067
- * - merge all queries in case of joins
1068
- * - follow subqueries
1069
- *
1070
- * @param {CSN.Query} query Query to check
1071
- * @returns {object} Map of sources
1072
- */
1073
- function getSources(query) {
1074
- // Remark CW: better just a while along query.SET.args[0]
1075
- if (query.SET) {
1076
- if (query.SET.args[0].SELECT && query.SET.args[0].SELECT.elements)
1077
- return mergeElementsIntoMap(Object.create(null), query.SET.args[0].SELECT.elements, query.SET.args[0].$location);
1078
-
1079
- return getSources(query.SET.args[0]);
1080
- }
1081
- else if (query.SELECT) {
1082
- if (query.SELECT.from.args) {
1083
- return walkArgs(query.SELECT.from.args);
1084
- }
1085
- else if (query.SELECT.from.ref) {
1086
- const art = artifactRef(query.SELECT.from);
1087
- return mergeElementsIntoMap(Object.create(null), art.elements, art.$location,
1088
- query.SELECT.from.as || query.SELECT.from.ref[query.SELECT.from.ref.length - 1],
1089
- query.SELECT.from.ref[query.SELECT.from.ref.length - 1] || query.SELECT.from.as );
1090
- }
1091
- else if (query.SELECT.from.SET || query.SELECT.from.SELECT) {
1092
- return getSources(query.SELECT.from);
1093
- }
1094
- }
1095
-
1096
- function walkArgs(args) {
1097
- let elements = Object.create(null);
1098
- for (const arg of args) {
1099
- if (arg.args) {
1100
- elements = mergeElementMaps(elements, walkArgs(arg.args));
1101
- }
1102
- else if (arg.ref) {
1103
- const art = artifactRef(arg);
1104
- elements = mergeElementsIntoMap(elements, art.elements, art.$location, arg.as || arg.ref[arg.ref.length - 1], arg.ref[arg.ref.length - 1] || arg.as);
1105
- }
1106
- else if (arg.SELECT || arg.SET) {
1107
- elements = mergeElementMaps(elements, getSources(arg));
1108
- }
1109
- }
1110
-
1111
- return elements;
1112
- }
1113
-
1114
- return {};
1115
-
1116
- /**
1117
- * Merge two maps of elements together
1118
- *
1119
- * @param {object} mapA Map a - will be returned
1120
- * @param {object} mapB Map b - will not be returned
1121
- * @returns {object} mapA
1122
- */
1123
- function mergeElementMaps(mapA, mapB) {
1124
- for (const elementName in mapB) {
1125
- if (!mapA[elementName])
1126
- mapA[elementName] = [];
1127
-
1128
- mapB[elementName].forEach(e => mapA[elementName].push(e));
1129
- }
1130
-
1131
- return mapA;
1132
- }
1133
-
1134
- /**
1135
- * Merge elements into an existing map
1136
- *
1137
- * @param {any} existingMap map to merge into - will be returned
1138
- * @param {object} elements elements to merge into the map
1139
- * @param {CSN.Location} $location $location of the elements - where they come from
1140
- * @param {any} [parent] Name of the parent of the elements, alias before ref
1141
- * @param {any} [error_parent] Parent name to use for error messages, ref before alias
1142
- * @returns {object} existingMap
1143
- */
1144
- function mergeElementsIntoMap(existingMap, elements, $location, parent, error_parent) {
1145
- for (const elementName in elements) {
1146
- const element = elements[elementName];
1147
- if (!existingMap[elementName])
1148
- existingMap[elementName] = [];
1149
-
1150
-
1151
- existingMap[elementName].push({
1152
- element, name: elementName, source: $location, parent: getBaseName(parent), error_parent,
1153
- });
1154
- }
1155
-
1156
- return existingMap;
1157
- }
1158
- }
1159
- }
1160
- /**
1161
- * Return the name part of the artifact name - no namespace etc.
1162
- * @param {string|object} name Absolute name of the artifact
1163
- */
1164
- function getBaseName(name) {
1165
- if (!name)
1166
- return name;
1167
-
1168
- if (name.id)
1169
- return name.id.substring( name.id.lastIndexOf('.')+1 );
1170
-
1171
- return name.substring( name.lastIndexOf('.')+1 )
1172
- }
1173
-
1174
1017
  /**
1175
1018
  * Get all elements tagged with @cds.valid.from/to from the union of all entities of the from-clause.
1176
1019
  *
@@ -1185,10 +1028,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1185
1028
  if (!Array.isArray(elt))
1186
1029
  elt = [ elt ];
1187
1030
  elt.forEach((e) => {
1188
- if (hasBoolAnnotation(e.element, '@cds.valid.from'))
1031
+ if (hasAnnotationValue(e.element, '@cds.valid.from'))
1189
1032
  from.push(e);
1190
1033
 
1191
- if (hasBoolAnnotation(e.element, '@cds.valid.to'))
1034
+ if (hasAnnotationValue(e.element, '@cds.valid.to'))
1192
1035
  to.push(e);
1193
1036
  });
1194
1037
  }
@@ -1219,7 +1062,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1219
1062
  }
1220
1063
  function ignore(member, memberName, prop, path) {
1221
1064
  if (dialect === 'hana' && !member._ignore && member.target && isAssocOrComposition(member.type) && isUnreachableAssociationTarget(csn.definitions[member.target])) {
1222
- const targetAnnotation = hasBoolAnnotation(csn.definitions[member.target], '@cds.persistence.exists') ? '@cds.persistence.exists' : '@cds.persistence.skip';
1065
+ const targetAnnotation = hasAnnotationValue(csn.definitions[member.target], '@cds.persistence.exists') ? '@cds.persistence.exists' : '@cds.persistence.skip';
1223
1066
  info(null, path,
1224
1067
  { target: member.target, anno: targetAnnotation },
1225
1068
  'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)'
@@ -1234,7 +1077,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1234
1077
  * @returns {boolean}
1235
1078
  */
1236
1079
  function isUnreachableAssociationTarget(art) {
1237
- return !isPersistedOnDatabase(art) || hasBoolAnnotation(art, '@cds.persistence.exists');
1080
+ return !isPersistedOnDatabase(art) || hasAnnotationValue(art, '@cds.persistence.exists');
1238
1081
  }
1239
1082
 
1240
1083
  /**
@@ -1255,16 +1098,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1255
1098
  replaceEnumSymbolsByValues(obj, path);
1256
1099
  }
1257
1100
 
1258
- function walkCsnPath(path) {
1259
- /** @type {object} */
1260
- let obj = csn;
1261
- for(let i = 0; i < path.length; i++){
1262
- obj = obj[path[i]];
1263
- }
1264
-
1265
- return obj;
1266
- }
1267
-
1268
1101
  // Change the names of those builtin types that have different names in HANA.
1269
1102
  // (do that directly in the csn where the builtin types are defined, so that
1270
1103
  // all users of the types benefit from it). Also add the type parameter 'length'
@@ -1351,7 +1184,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1351
1184
  });
1352
1185
  }
1353
1186
  }
1354
- if (query && options.toHana) {
1187
+ if (query && options.transformation === 'hdbcds') {
1355
1188
  // check all queries/subqueries for mixin publishing inside of unions -> forbidden in hdbcds
1356
1189
  if (query.SELECT && query.SELECT.mixin && path.indexOf('SET') !== -1) {
1357
1190
  for (const elementName in elements) {
@@ -1419,17 +1252,14 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1419
1252
 
1420
1253
  // For associations - make sure that the foreign keys have the same "style"
1421
1254
  // If A.assoc => A.assoc_id, else if assoc => assoc_id or assoc as Assoc => Assoc_id
1422
- if (elem.keys && !options.forHana.keepStructsAssocs) {
1255
+ if (elem.keys && doA2J) {
1423
1256
  const assoc_col = columnMap[elemName];
1424
1257
  if (assoc_col && assoc_col.ref) {
1425
1258
  elem.keys.forEach((key) => {
1426
1259
  const ref = cloneCsn(assoc_col.ref, options);
1427
1260
  ref[ref.length - 1] = [ ref[ref.length - 1] ].concat(key.as || key.ref).join(pathDelimiter);
1428
- // Keep the $env of the parent column
1429
- // so that we can still resolve the ref later on
1430
1261
  const result = {
1431
1262
  ref,
1432
- $env: assoc_col.$env,
1433
1263
  };
1434
1264
  if (assoc_col.as)
1435
1265
  result.as = key.$generatedFieldName;
@@ -1446,7 +1276,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1446
1276
  }
1447
1277
  // Add flattened structured things preserving aliases and refs with/without table alias
1448
1278
  // If we add them when we get to them in "elements", we cannot know what table alias was used...
1449
- if (isStructured(elem) && !options.forHana.keepStructsAssocs) {
1279
+ if (isStructured(elem) && doA2J) {
1450
1280
  const col = columnMap[elemName];
1451
1281
  const originalName = col.ref[col.ref.length - 1];
1452
1282
  const flatElements = flattenStructuredElement(elem, originalName, [], path);
@@ -1471,21 +1301,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1471
1301
  if (!elem.on && !elem._ignore)
1472
1302
  hasNonAssocElements = true;
1473
1303
 
1474
-
1475
- // (230 b) If we keep associations as they are (hdbcds naming convention), we cannot have managed associations
1476
- // as view elements (their foreign keys cannot be addressed in the view)
1477
- if (options.forHana.keepStructsAssocs &&
1478
- query &&
1479
- isAssocOrComposition(elem.type) &&
1480
- !elem.on) {
1481
- error(null, [ 'definitions', artName, 'elements', elemName ], `With "hdbcds" naming, managed association elements can't be used in a view`);
1482
- continue;
1483
- }
1484
-
1485
1304
  // (180 b) Create MIXINs for association elements in projections or views (those that are not mixins by themselves)
1486
1305
  // CDXCORE-585: Allow mixin associations to be used and published in parallel
1487
1306
  if (query !== undefined && elem.target) {
1488
- if(isUnion(path) && options.toHana){
1307
+ if(isUnion(path) && options.transformation === 'hdbcds'){
1489
1308
  if(isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J){
1490
1309
  if(elem.keys) {
1491
1310
  info(null, path, `Managed association "${elemName}", published in a UNION, will be ignored`)
@@ -1497,7 +1316,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1497
1316
  else {
1498
1317
  error(null, path, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`)
1499
1318
  }
1500
- } 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
1501
1320
  error(null, path, { name: elemName },
1502
1321
  'Association $(NAME) can\'t be published in a subquery')
1503
1322
  } else {
@@ -1579,8 +1398,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1579
1398
  if (query && !hasNonAssocElements) {
1580
1399
  // Complain if there are no elements other than unmanaged associations
1581
1400
  // Allow with plain
1582
- error(null, [ 'definitions', artName ],
1583
- 'For SAP HANA CDS or SQL, a view or projection must have at least one element that is not an unmanaged association');
1401
+ error(null, [ 'definitions', artName ], { $reviewed: true } ,
1402
+ 'Expecting view or projection to have at least one element that is not an unmanaged association');
1584
1403
  }
1585
1404
 
1586
1405
  if (isSelect) {
@@ -1627,13 +1446,20 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1627
1446
  if (!Enum) {
1628
1447
  // Not an enum at all
1629
1448
  // Looks like it is always run?! But message says HANA CDS?!
1630
- error(null, path, `Enum literal "#${ elem.default['#'] }" can only be used with an enum type in SAP HANA CDS`);
1449
+ error(null, path, {
1450
+ $reviewed: true,
1451
+ name: `#${elem.default['#']}`
1452
+ },
1453
+ 'Expecting enum literal $(NAME) to be used with an enum type');
1631
1454
  }
1632
1455
  else {
1633
1456
  // Try to get the corresponding enum symbol from the element's type
1634
1457
  const enumSymbol = Enum[elem.default['#']];
1635
1458
  if (!enumSymbol) {
1636
- error(null, path, `Enum literal "#${ elem.default['#'] }" not found in enumeration type`);
1459
+ error(null, path, {
1460
+ $reviewed: true,
1461
+ name: `#${elem.default['#']}`
1462
+ }, 'Enum literal $(NAME) is undefined in enumeration type');
1637
1463
  }
1638
1464
  else if (enumSymbol.val !== undefined) { // `val` may be `null`
1639
1465
  // Replace default with enum value
@@ -1703,23 +1529,41 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1703
1529
  const assoc = inspectRef(path.concat([ i + 2 ])).art;
1704
1530
  if (multipleExprs)
1705
1531
  result.push('(');
1532
+ const backlinkName = xprArgs[i + 2].ref[xprArgs[i + 2].ref.length - 1];
1706
1533
  result.push(...transformDollarSelfComparison(xprArgs[i + 2],
1707
1534
  assoc,
1708
- xprArgs[i + 2].ref[xprArgs[i + 2].ref.length - 1],
1535
+ backlinkName,
1709
1536
  elem, elemName, art, artName, path.concat([ i ])
1710
1537
  ));
1711
1538
  if (multipleExprs)
1712
1539
  result.push(')');
1713
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
+ }
1714
1549
  }
1715
1550
  else if (isDollarSelfOrProjectionOperand(xprArgs[i + 2]) && isAssociationOperand(xprArgs[i], path.concat([ i ]))) {
1716
1551
  const assoc = inspectRef(path.concat([ i ])).art;
1717
1552
  if (multipleExprs)
1718
1553
  result.push('(');
1719
- 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 ])));
1720
1556
  if (multipleExprs)
1721
1557
  result.push(')');
1722
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
+ }
1723
1567
  }
1724
1568
  // Otherwise take one (!) token unchanged
1725
1569
  else {
@@ -1796,8 +1640,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1796
1640
  assoc.keys.forEach((k) => {
1797
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)
1798
1642
  // With to.hdbcds in conjunction with hdbcds naming, we need to NOT use the alias - else we get deployment errors
1799
- const keyName = k.as && !(options.toHana && options.toHana.names === 'hdbcds') ? [k.as] : k.ref;
1800
- 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] }` ];
1801
1645
  // FIXME: _artifact to the args ???
1802
1646
  const a = [
1803
1647
  {
@@ -1852,243 +1696,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1852
1696
  }
1853
1697
  }
1854
1698
 
1855
- // Collect all artifacts that are transitively reachable via compositions from 'artifact' into 'draftNodes'.
1856
- // 'rootArtifact' is the root artifact where composition traversal started.
1857
- // Check that no artifact other than the root node has '@odata.draft.enabled'
1858
- function collectDraftNodesInto(artifact, artifactName, rootArtifact, draftNodes) {
1859
- // Collect the artifact itself
1860
- draftNodes[artifactName] = artifact;
1861
- // Follow all composition targets in elements of 'artifact'
1862
- for (const elemName in artifact.elements) {
1863
- const elem = artifact.elements[elemName];
1864
- if (elem.target && isComposition(elem.type)) {
1865
- const draftNode = getCsnDef(elem.target);
1866
- const draftNodeName = elem.target;
1867
- // Sanity check
1868
- if (!draftNode)
1869
- throw new Error(`Expecting target to be resolved: ${ JSON.stringify(elem, null, 2) }`);
1870
-
1871
- // Ignore composition if not part of a service
1872
- if (!isPartOfService(draftNodeName)) {
1873
-
1874
- warning(null, [ 'definitions', artifactName, 'elements', elemName ], { target: draftNodeName },
1875
- 'Ignoring draft node for composition target $(TARGET) because it is not part of a service');
1876
- continue;
1877
- }
1878
- // Barf if a draft node other than the root has @odata.draft.enabled itself
1879
- if (draftNode != rootArtifact && hasBoolAnnotation(draftNode, '@odata.draft.enabled')) {
1880
- error(null, [ 'definitions', artifactName, 'elements', elemName ], `Composition in draft-enabled entity can't lead to another entity with “@odata.draft.enabled”`);
1881
- delete draftNodes[draftNodeName];
1882
- continue;
1883
- }
1884
- // Recurse unless already known
1885
- if (!hasBoolAnnotation(draftNode, '@odata.draft.enabled', false) && !draftNodes[draftNodeName])
1886
- collectDraftNodesInto(draftNode, draftNodeName, rootArtifact, draftNodes);
1887
- }
1888
- }
1889
- }
1890
-
1891
- /**
1892
- * Generate all that is required in HANA CDS for draft enablement of 'artifact'.
1893
- *
1894
- * @param {CSN.Artifact} artifact
1895
- * @param {string} artifactName
1896
- * @param {string} draftRootName
1897
- */
1898
- function generateDraftForHana(artifact, artifactName, draftRootName) {
1899
- // Sanity check
1900
- if (!isPartOfService(artifactName))
1901
- throw new Error(`Expecting artifact to be part of a service: ${ JSON.stringify(artifact) }`);
1902
-
1903
-
1904
- // The name of the draft shadow entity we should generate
1905
- const draftsArtifactName = `${ artifactName }${draftSuffix}`;
1906
-
1907
- // extract keys for UUID inspection
1908
- const keys = [];
1909
- forEachGeneric( artifact, 'elements', (elt) => {
1910
- if (elt.key && elt.key === true && !elt.virtual)
1911
- keys.push(elt);
1912
- });
1913
-
1914
- // In contrast to EDM, the DB entity may have more than one technical keys but should have idealy exactly one key of type cds.UUID
1915
- if (keys.length !== 1)
1916
- warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have exactly one key element');
1917
-
1918
- const uuidCount = keys.reduce((uuidCount, k) => ((k.type === 'cds.String' && k.$renamed === 'cds.UUID' && k.length === 36) ? uuidCount+1 : uuidCount), 0);
1919
- if (uuidCount === 0)
1920
- warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have one key element of type “cds.UUID”');
1921
-
1922
-
1923
- const matchingService = getMatchingService(artifactName);
1924
- // Generate the DraftAdministrativeData projection into the service, unless there is already one
1925
- const draftAdminDataProjectionName = `${ matchingService }.` + 'DraftAdministrativeData';
1926
- let draftAdminDataProjection = csn.definitions[draftAdminDataProjectionName];
1927
- if (!draftAdminDataProjection) {
1928
- draftAdminDataProjection = createAndAddDraftAdminDataProjection(matchingService, true);
1929
-
1930
- if (!draftAdminDataProjection.projection.columns && draftAdminDataProjection.elements.DraftUUID) {
1931
- draftAdminDataProjection.projection.columns = Object.keys(draftAdminDataProjection.elements).map(e => e === 'DraftUUID' ? { key: true, ref: ['DraftAdministrativeData', e]} : {ref: ['DraftAdministrativeData', e]});
1932
- }
1933
- }
1934
-
1935
- // Barf if it is not an entity or not what we expect
1936
- if (draftAdminDataProjection.kind !== 'entity' || !draftAdminDataProjection.elements['DraftUUID']) {
1937
- // See draftAdminDataProjection which is defined in `csn.definitions`.
1938
- const path = [ 'definitions', draftAdminDataProjectionName ];
1939
- error(null, path, { name: draftAdminDataProjectionName },
1940
- 'Generated entity $(NAME) conflicts with existing artifact');
1941
- }
1942
-
1943
- const persistenceName = getResultingName(csn, options.forHana.names, draftsArtifactName);
1944
- // Duplicate the artifact as a draft shadow entity
1945
- if (csn.definitions[persistenceName]) {
1946
- const definingDraftRoot = csn.definitions[persistenceName].$draftRoot;
1947
- if (!definingDraftRoot)
1948
- error(null, [ 'definitions', artifactName ], { name: persistenceName },
1949
- 'Generated entity name $(NAME) conflicts with existing entity');
1950
-
1951
- else
1952
- error(null, [ 'definitions', draftRootName ], { name: persistenceName },
1953
- `Entity $(NAME) already generated by draft root "${ definingDraftRoot }"`);
1954
-
1955
- return;
1956
- }
1957
- const draftsArtifact = {
1958
- kind: 'entity',
1959
- elements: Object.create(null),
1960
- };
1961
-
1962
- // Add draft shadow entity to the csn
1963
- csn.definitions[draftsArtifactName] = draftsArtifact;
1964
-
1965
- setProp(draftsArtifact, '$draftRoot', draftRootName);
1966
- if(artifact.$location)
1967
- setProp(draftsArtifact, '$location', artifact.$location);
1968
-
1969
- // Copy all elements
1970
- for (const elemName in artifact.elements) {
1971
- const origElem = artifact.elements[elemName];
1972
- let elem = undefined;
1973
- if(isDeprecatedEnabled(options, 'renderVirtualElements') && origElem.virtual)
1974
- elem = copyAndAddElement(origElem, draftsArtifact, draftsArtifactName, elemName)[elemName];
1975
- else if(!origElem.virtual)
1976
- elem = copyAndAddElement(origElem, draftsArtifact, draftsArtifactName, elemName)[elemName];
1977
- if(elem) {
1978
- // Remove "virtual" - cap/issues 4956
1979
- if(elem.virtual) {
1980
- delete elem.virtual;
1981
- }
1982
- // explicitly set nullable if not key and not unmanaged association
1983
- if (!elem.key && !elem.on)
1984
- elem.notNull = false;
1985
- }
1986
- }
1987
-
1988
- // Generate the additional elements into the draft-enabled artifact
1989
-
1990
- // key IsActiveEntity : Boolean default true
1991
- const isActiveEntity = createScalarElement('IsActiveEntity', 'cds.Boolean', false);
1992
- // Use artifactName and not draftsArtifactName because otherwise we may point to the generated
1993
- // entity in CSN and won't get a proper location (draftsArtifact has inherited all
1994
- // elements from the original artifact).
1995
- addElement(isActiveEntity, draftsArtifact, artifactName);
1996
-
1997
- // HasActiveEntity : Boolean default false
1998
- const hasActiveEntity = createScalarElement('HasActiveEntity', 'cds.Boolean', false);
1999
- addElement(hasActiveEntity, draftsArtifact, artifactName);
2000
-
2001
- // HasDraftEntity : Boolean default false;
2002
- const hasDraftEntity = createScalarElement('HasDraftEntity', 'cds.Boolean', false);
2003
- addElement(hasDraftEntity, draftsArtifact, artifactName);
2004
-
2005
- // DraftAdministrativeData : Association to one DraftAdministrativeData not null;
2006
- const draftAdministrativeData = createAssociationElement('DraftAdministrativeData', draftAdminDataProjectionName, true);
2007
- draftAdministrativeData.DraftAdministrativeData.cardinality = {
2008
- max: 1,
2009
- };
2010
- draftAdministrativeData.DraftAdministrativeData.notNull = true;
2011
- addElement(draftAdministrativeData, draftsArtifact, artifactName);
2012
- // Note that we may need to do the HANA transformation steps for managed associations
2013
- // (foreign key field generation, generatedFieldName, creating ON-condition) by hand,
2014
- // because the corresponding transformation steps have already been done on all artifacts
2015
- // when we come here). Only for 'keepStructsAssocs' this is not required.
2016
- /**
2017
- * The given association has a key named DraftUUID
2018
- *
2019
- * @param {CSN.Association} association Assoc to check
2020
- * @returns {object}
2021
- */
2022
- function getDraftUUIDKey(association) {
2023
- if (association.keys) {
2024
- const filtered = association.keys.filter(o => (o.ref && !o.as && o.ref.length === 1 && o.ref[0] === 'DraftUUID') || (o.as && o.as === 'DraftUUID'));
2025
- if (filtered.length === 1)
2026
- return filtered[0];
2027
-
2028
- else if (filtered.length > 1)
2029
- return filtered.filter(o => o.as && o.as === 'DraftUUID');
2030
- }
2031
-
2032
- return undefined;
2033
- }
2034
-
2035
- function getNameForRef(obj) {
2036
- if (obj.as)
2037
- return obj.as;
2038
-
2039
- return obj.ref[obj.ref.length - 1];
2040
- }
2041
-
2042
- const draftUUIDKey = getDraftUUIDKey(draftAdministrativeData.DraftAdministrativeData);
2043
- if (!options.forHana.keepStructsAssocs && draftUUIDKey) {
2044
- const path = [ 'definitions', draftsArtifactName, 'elements', 'DraftAdministrativeData', 'keys', 0 ];
2045
- createForeignKeyElement(draftAdministrativeData.DraftAdministrativeData, 'DraftAdministrativeData', draftUUIDKey, draftsArtifact, draftsArtifactName, path);
2046
- draftAdministrativeData.DraftAdministrativeData.on = createAssociationPathComparison('DraftAdministrativeData',
2047
- getNameForRef(draftUUIDKey),
2048
- '=',
2049
- `DraftAdministrativeData${ pathDelimiter }DraftUUID`);
2050
- // The notNull has been transferred to the foreign key field and must be removed on the association
2051
- delete draftAdministrativeData.DraftAdministrativeData.notNull;
2052
-
2053
- // The association is now unmanaged, i.e. actually it should no longer have foreign keys
2054
- // at all. But the processing of backlink associations below expects to have them, so
2055
- // we don't delete them (but mark them as implicit so that toCdl does not render them)
2056
- // draftAdministrativeData.DraftAdministrativeData.implicitForeignKeys = true;
2057
- }
2058
- }
2059
-
2060
- // Redirect all association/composition targets in 'artifact' that point to targets in
2061
- // the dictionary 'draftNodes' to their corresponding draft shadow artifacts.
2062
- function redirectDraftTargets(artifact, draftNodes) {
2063
- for (const elemName in artifact.elements) {
2064
- const elem = artifact.elements[elemName];
2065
- if (elem.target) {
2066
- const targetArt = getCsnDef(elem.target);
2067
- // Nothing to do if target is not a draft node
2068
- if (!draftNodes[elem.target])
2069
- continue;
2070
-
2071
- // Redirect the composition/association in this draft shadow entity to the target draft shadow entity
2072
- // console.error(`Redirecting target of ${elemName} in ${artifact.name.absolute} to ${target.name.absolute + '_drafts'}`);
2073
- const { shadowTarget, shadowTargetName } = getDraftShadowEntityFor(targetArt, elem.target);
2074
- // Might not exist because of previous errors
2075
- if (shadowTarget)
2076
- elem.target = shadowTargetName;
2077
- // FIXME: Strictly speaking, we would also need to replace the foreign keys' _artifact links,
2078
- // but since their content is identical anyway, we simply omit that for now.
2079
- }
2080
- }
2081
-
2082
- // Returns the corresponding draft shadow artifact for draft node 'draftNode'.
2083
- function getDraftShadowEntityFor(draftNode, draftNodeName) {
2084
- // Sanity check
2085
- if (!draftNodes[draftNodeName])
2086
- throw new Error(`Not a draft node: ${ draftNodeName }`);
2087
-
2088
- return { shadowTarget: csn.definitions[`${ draftNodeName }${draftSuffix}`], shadowTargetName: `${ draftNodeName }${draftSuffix}` };
2089
- }
2090
- }
2091
-
2092
1699
  /**
2093
1700
  * @todo: XSN - Implementation most likely too naive, can we rely on query.SELECT.mixin?
2094
1701
  *
@@ -2109,10 +1716,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2109
1716
  return true;
2110
1717
  }
2111
1718
 
2112
- function insideColumns(path) {
2113
- return path.length >= 3 && path[path.length - 3] === 'SELECT' && path[path.length - 2] === 'columns';
2114
- }
2115
-
2116
1719
  /**
2117
1720
  * @param {CSN.Artifact} artifact
2118
1721
  * @param {string} artifactName
@@ -2137,20 +1740,20 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2137
1740
  for (const name in parameters) {
2138
1741
  const param = parameters[name];
2139
1742
  if (!node[param] && absolute !== 'cds.hana.ST_POINT' && absolute !== 'cds.hana.ST_GEOMETRY')
2140
- error(null, path, `Actual value for type parameter '${ param }' missing in reference to type '${ absolute }'`);
1743
+ error('missing-type-parameter', path, { name: param, id: absolute, $reviewed: false });
2141
1744
  }
2142
1745
  switch (absolute) {
2143
1746
  case 'cds.String':
2144
1747
  case 'cds.Binary':
2145
1748
  case 'cds.hana.VARCHAR': {
2146
- checkTypeParamValue(node, 'length', 'positiveInteger', { min: 1, max: 5000 }, path);
1749
+ checkTypeParamValue(node, 'length', { min: 1, max: 5000 }, path);
2147
1750
  break;
2148
1751
  }
2149
1752
  case 'cds.Decimal': {
2150
1753
  // Don't check with "plain"?
2151
1754
  if (node.precision || node.scale) {
2152
- checkTypeParamValue(node, 'precision', 'positiveInteger', { max: 38 }, path);
2153
- checkTypeParamValue(node, 'scale', 'positiveInteger', { max: node.precision }, path);
1755
+ checkTypeParamValue(node, 'precision', { max: 38 }, path);
1756
+ checkTypeParamValue(node, 'scale', { max: node.precision }, path);
2154
1757
  }
2155
1758
  break;
2156
1759
  }
@@ -2158,12 +1761,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2158
1761
  case 'cds.hana.BINARY':
2159
1762
  case 'cds.hana.NCHAR':
2160
1763
  case 'cds.hana.CHAR': {
2161
- checkTypeParamValue(node, 'length', 'positiveInteger', { min: 1, max: 2000 }, path);
1764
+ checkTypeParamValue(node, 'length', { min: 1, max: 2000 }, path);
2162
1765
  break;
2163
1766
  }
2164
1767
  case 'cds.hana.ST_POINT':
2165
1768
  case 'cds.hana.ST_GEOMETRY': {
2166
- checkTypeParamValue(node, 'srid', 'positiveInteger', { max: Number.MAX_SAFE_INTEGER }, path);
1769
+ checkTypeParamValue(node, 'srid', { max: Number.MAX_SAFE_INTEGER }, path);
2167
1770
  break;
2168
1771
  }
2169
1772
  }
@@ -2171,32 +1774,26 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2171
1774
 
2172
1775
  // Check that the value of the type property `paramName` (e.g. length, precision, scale ...) is of `expectedType`
2173
1776
  // (which can currently only be 'positiveInteger') and (optional) the value is in a given range
2174
- function checkTypeParamValue(node, paramName, expectedType, range = null, path = null) {
1777
+ function checkTypeParamValue(node, paramName, range = null, path = null) {
2175
1778
  const paramValue = node[paramName];
2176
1779
  if (paramValue == undefined) {
2177
- if(options.toSql || artifact.query || !["cds.Binary","cds.hana.BINARY", 'cds.hana.NCHAR','cds.hana.CHAR'].includes(node.type)) {
1780
+ if(options.toSql || artifact.query || !['cds.Binary','cds.hana.BINARY', 'cds.hana.NCHAR','cds.hana.CHAR'].includes(node.type)) {
2178
1781
  return true;
2179
1782
  } else {
2180
- return error(null, path, `Actual value for type parameter '${ paramName }' missing in reference to type '${ node.type }'`);
1783
+ return error('missing-type-parameter', path, { name: paramName, id: node.type, $reviewed: false });
2181
1784
  }
2182
1785
  }
2183
- switch (expectedType) {
2184
- case 'positiveInteger':
2185
- if (!(Number.isInteger(paramValue) && paramValue >= 0)) {
2186
- error(null, path, `Actual parameter '${ paramName }' for '${ node.type }' must be positive integer`);
2187
- return false;
2188
- }
2189
- break;
2190
- default:
2191
- throw 'Unknown "expectedType"';
2192
- }
2193
1786
  if (range) {
2194
1787
  if (isMaxParameterLengthRestricted(node.type) && range.max && paramValue > range.max) {
2195
- error(null, path, `Actual parameter '${ paramName }' for '${ node.type }' is larger than allowed (max: ${ range.max })`);
1788
+ error(null, path,
1789
+ { prop: paramName, type: node.type, number: range.max, $reviewed: false },
1790
+ 'Expecting parameter $(PROP) for type $(TYPE) to not exceed $(NUMBER)');
2196
1791
  return false;
2197
1792
  }
2198
1793
  if (range.min && paramValue < range.min) {
2199
- error(null, path, `Actual parameter '${ paramName }' for '${ node.type }' is smaller than allowed (min: ${ range.min })`);
1794
+ error(null, path,
1795
+ { prop: paramName, type: node.type, number: range.min, $reviewed: false },
1796
+ 'Expecting parameter $(PROP) for type $(TYPE) to be greater than or equal to $(NUMBER)');
2200
1797
  return false;
2201
1798
  }
2202
1799
  }
@@ -2215,152 +1812,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2215
1812
  return !(options.toSql && type === 'cds.String' && (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain'));
2216
1813
  }
2217
1814
 
2218
- /**
2219
- * Check if the given artifact is part of a service.
2220
- *
2221
- * @param {string} artifactName Absolute name of the artifact
2222
- * @returns {boolean}
2223
- */
2224
- function isPartOfService(artifactName) {
2225
- for (const serviceName of allServices) {
2226
- if (artifactName.startsWith(`${ serviceName }.`))
2227
- return true;
2228
- }
2229
-
2230
- return false;
2231
- }
2232
-
2233
- /**
2234
- * Get the service name containing the artifact.
2235
- *
2236
- * @param {string} artifactName Absolute name of the artifact
2237
- * @returns {boolean|string} Name of the service or false if no match is found.
2238
- */
2239
- function getMatchingService(artifactName) {
2240
- const matches = [];
2241
- for (const serviceName of allServices) {
2242
- if (artifactName.startsWith(`${ serviceName }.`))
2243
- matches.push(serviceName);
2244
- }
2245
- if (matches.length === 0)
2246
- return false;
2247
- else
2248
- return matches.sort((a, b) => a.length - b.length)[0];
2249
- }
2250
-
2251
- /**
2252
- * Get not just the leafs, but all the branches of a structured element
2253
- *
2254
- * @param {object} element Structured element
2255
- * @param {string} elementName Name of the structured element
2256
- * @returns {object} Returns a dictionary, where the key is the flat name of the branch and the value is an array of element-steps.
2257
- */
2258
- function getBranches(element, elementName){
2259
- const branches = {};
2260
- const subbranchNames = [];
2261
- const subbranchElements = [];
2262
- walkElements(element, elementName);
2263
- function walkElements(e, name){
2264
- if(isBuiltinType(e)){
2265
- branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
2266
- } else {
2267
- const eType = effectiveType(e)
2268
- const subelements = e.elements || eType.elements;
2269
- if(subelements){
2270
- subbranchElements.push(e);
2271
- subbranchNames.push(name);
2272
- for(let [subelementName, subelement] of Object.entries(subelements)){
2273
- walkElements(subelement, subelementName);
2274
- }
2275
- subbranchNames.pop();
2276
- subbranchElements.pop();
2277
- } else {
2278
- branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
2279
- }
2280
-
2281
- }
2282
- }
2283
- return branches;
2284
- }
2285
-
2286
- /**
2287
- * Flatten structures
2288
- *
2289
- * @param {CSN.Artifact} art Artifact
2290
- * @param {string} artName Artifact Name
2291
- */
2292
- function flattenStructuredElements(art, artName) {
2293
- if ((art.kind === 'entity' || art.kind === 'view') && !options.forHana.keepStructsAssocs) {
2294
- forAllElements(art, artName, (parent, elements, pathToElements) => {
2295
- const elementsArray = [];
2296
- for (const elemName in elements) {
2297
- const pathToElement = pathToElements.concat([elemName])
2298
- const elem = parent.elements[elemName];
2299
- elementsArray.push([elemName, elem]);
2300
- if (isStructured(elem)) {
2301
- // Ignore the structured element, replace it by its flattened form
2302
- // TODO: use $ignore - _ is for links
2303
- elem._ignore = true;
2304
-
2305
- const branches = getBranches(elem, elemName);
2306
- const flatElems = flattenStructuredElement(elem, elemName, [], pathToElement);
2307
-
2308
- for (const flatElemName in flatElems) {
2309
- if (parent.elements[flatElemName])
2310
- error(null, pathToElement, `"${ artName }.${ elemName }": Flattened struct element name conflicts with existing element: "${ flatElemName }"`);
2311
-
2312
- const flatElement = flatElems[flatElemName];
2313
-
2314
- // Check if we have a valid notNull chain
2315
- const branch = branches[flatElemName];
2316
- if(flatElement.notNull !== false && !branch.some(s => !s.notNull)){
2317
- flatElement.notNull = true;
2318
- }
2319
-
2320
- if(flatElement.type && isAssocOrComposition(flatElement.type) && flatElement.on){
2321
- // Make refs resolvable by fixing the first ref step
2322
- for (let i = 0; i < flatElement.on.length; i++) {
2323
- const onPart = flatElement.on[i];
2324
- if (onPart.ref) {
2325
- const firstRef = flatElement.on[i].ref[0];
2326
-
2327
- /*
2328
- when element is defined in the current name resolution scope, like
2329
- entity E {
2330
- key x: Integer;
2331
- s : {
2332
- y : Integer;
2333
- a3 : association to E on a3.x = y;
2334
- }
2335
- }
2336
- We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
2337
- */
2338
- const prefix = flatElement._flatElementNameWithDots.split('.').slice(0,-1).join(pathDelimiter);
2339
- const possibleFlatName = prefix + pathDelimiter + firstRef;
2340
-
2341
- if (flatElems[possibleFlatName])
2342
- flatElement.on[i].ref[0] = possibleFlatName;
2343
- }
2344
- }
2345
- }
2346
- elementsArray.push([flatElemName, flatElement]);
2347
- // Still add them - otherwise we might not detect collisions between generated elements.
2348
- parent.elements[flatElemName] = flatElement;
2349
- }
2350
- }
2351
- }
2352
- // Don't fake consistency of the model by adding empty elements {}
2353
- if(elementsArray.length === 0)
2354
- return;
2355
-
2356
- parent.elements = elementsArray.reduce((previous, [name, element]) => {
2357
- previous[name] = element;
2358
- return previous;
2359
- }, Object.create(null));
2360
- });
2361
- }
2362
- }
2363
-
2364
1815
  /**
2365
1816
  * Flatten and create the foreign key elements of managed associaitons
2366
1817
  *
@@ -2368,7 +1819,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2368
1819
  * @param {string} artName
2369
1820
  */
2370
1821
  function handleManagedAssociationFKs(art, artName) {
2371
- if ((art.kind === 'entity' || art.kind === 'view') && !options.forHana.keepStructsAssocs) {
1822
+ if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
2372
1823
  forAllElements(art, artName, (parent, elements, pathToElements) => {
2373
1824
  if(artName.startsWith('localized.') && pathToElements.length > 3) {
2374
1825
  // In subqueries, the elements of localized views are missing all the important bits and pieces...
@@ -2512,7 +1963,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2512
1963
  function flattenIndexes(art, artName) {
2513
1964
  // Flatten structs in indexes (unless explicitly asked to keep structs)
2514
1965
  const tc = art.technicalConfig;
2515
- if ((art.kind === 'entity' || art.kind === 'view') && !options.forHana.keepStructsAssocs) {
1966
+ if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
2516
1967
  if (tc && tc[dialect]) {
2517
1968
  // Secondary and fulltext indexes
2518
1969
  for (const name in tc[dialect].indexes) {
@@ -2579,7 +2030,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2579
2030
  function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
2580
2031
  for (const elemName in artifact.elements) {
2581
2032
  const elem = artifact.elements[elemName];
2582
- if (!options.forHana.keepStructsAssocs) {
2033
+ if (doA2J) {
2583
2034
  // The association is an unmanaged on
2584
2035
  if (!elem.keys && elem.target && elem.on) {
2585
2036
  forEachRef(elem.on, (ref, refOwner, path) => {
@@ -2592,7 +2043,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2592
2043
  const link = links[i];
2593
2044
  // We found the latest managed assoc path step
2594
2045
  if (link.art && link.art.target && link.art.keys) {
2595
- // Doesn't work when ref-where or similar is used
2046
+ // Doesn't work when ref-target (filter condition) or similar is used
2596
2047
  if (!ref.slice(i).some(refElement => typeof refElement !== 'string')) {
2597
2048
  // We join the managed assoc with everything following it
2598
2049
  const sourceElementName = ref.slice(i).join(pathDelimiter);