@sap/cds-compiler 2.11.4 → 2.13.8

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 (133) hide show
  1. package/CHANGELOG.md +159 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +22 -23
  4. package/bin/cdsse.js +2 -2
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +25 -6
  7. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  8. package/doc/NameResolution.md +21 -16
  9. package/lib/api/main.js +30 -63
  10. package/lib/api/options.js +5 -5
  11. package/lib/api/validate.js +0 -5
  12. package/lib/backends.js +15 -23
  13. package/lib/base/dictionaries.js +0 -8
  14. package/lib/base/error.js +26 -0
  15. package/lib/base/keywords.js +7 -17
  16. package/lib/base/location.js +9 -4
  17. package/lib/base/message-registry.js +52 -2
  18. package/lib/base/messages.js +16 -26
  19. package/lib/base/model.js +2 -62
  20. package/lib/base/optionProcessorHelper.js +246 -183
  21. package/lib/checks/.eslintrc.json +2 -0
  22. package/lib/checks/actionsFunctions.js +2 -1
  23. package/lib/checks/annotationsOData.js +1 -1
  24. package/lib/checks/cdsPersistence.js +2 -1
  25. package/lib/checks/enricher.js +17 -1
  26. package/lib/checks/foreignKeys.js +4 -4
  27. package/lib/checks/invalidTarget.js +3 -1
  28. package/lib/checks/managedInType.js +4 -4
  29. package/lib/checks/managedWithoutKeys.js +3 -1
  30. package/lib/checks/queryNoDbArtifacts.js +1 -3
  31. package/lib/checks/selectItems.js +4 -4
  32. package/lib/checks/sql-snippets.js +94 -0
  33. package/lib/checks/types.js +1 -1
  34. package/lib/checks/validator.js +12 -7
  35. package/lib/compiler/assert-consistency.js +10 -6
  36. package/lib/compiler/base.js +0 -1
  37. package/lib/compiler/builtins.js +8 -6
  38. package/lib/compiler/checks.js +46 -12
  39. package/lib/compiler/cycle-detector.js +1 -1
  40. package/lib/compiler/define.js +1103 -0
  41. package/lib/compiler/extend.js +983 -0
  42. package/lib/compiler/finalize-parse-cdl.js +231 -0
  43. package/lib/compiler/index.js +33 -14
  44. package/lib/compiler/kick-start.js +190 -0
  45. package/lib/compiler/moduleLayers.js +4 -4
  46. package/lib/compiler/populate.js +1226 -0
  47. package/lib/compiler/propagator.js +113 -47
  48. package/lib/compiler/resolve.js +1433 -0
  49. package/lib/compiler/shared.js +76 -38
  50. package/lib/compiler/tweak-assocs.js +529 -0
  51. package/lib/compiler/utils.js +204 -33
  52. package/lib/edm/.eslintrc.json +5 -0
  53. package/lib/edm/annotations/genericTranslation.js +38 -25
  54. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  55. package/lib/edm/csn2edm.js +10 -9
  56. package/lib/edm/edm.js +19 -20
  57. package/lib/edm/edmPreprocessor.js +166 -95
  58. package/lib/edm/edmUtils.js +127 -34
  59. package/lib/gen/Dictionary.json +92 -43
  60. package/lib/gen/language.checksum +1 -1
  61. package/lib/gen/language.interp +11 -1
  62. package/lib/gen/language.tokens +86 -82
  63. package/lib/gen/languageLexer.interp +18 -1
  64. package/lib/gen/languageLexer.js +925 -847
  65. package/lib/gen/languageLexer.tokens +78 -74
  66. package/lib/gen/languageParser.js +5434 -4298
  67. package/lib/json/from-csn.js +59 -17
  68. package/lib/json/to-csn.js +143 -71
  69. package/lib/language/antlrParser.js +3 -3
  70. package/lib/language/docCommentParser.js +3 -3
  71. package/lib/language/genericAntlrParser.js +144 -54
  72. package/lib/language/language.g4 +424 -203
  73. package/lib/language/multiLineStringParser.js +536 -0
  74. package/lib/main.d.ts +472 -61
  75. package/lib/main.js +38 -11
  76. package/lib/model/api.js +3 -1
  77. package/lib/model/csnRefs.js +321 -204
  78. package/lib/model/csnUtils.js +224 -263
  79. package/lib/model/enrichCsn.js +97 -40
  80. package/lib/model/revealInternalProperties.js +27 -6
  81. package/lib/model/sortViews.js +2 -1
  82. package/lib/modelCompare/compare.js +17 -12
  83. package/lib/optionProcessor.js +7 -6
  84. package/lib/render/DuplicateChecker.js +1 -1
  85. package/lib/render/manageConstraints.js +36 -33
  86. package/lib/render/toCdl.js +174 -275
  87. package/lib/render/toHdbcds.js +201 -115
  88. package/lib/render/toRename.js +7 -10
  89. package/lib/render/toSql.js +149 -75
  90. package/lib/render/utils/common.js +22 -8
  91. package/lib/render/utils/sql.js +10 -7
  92. package/lib/render/utils/stringEscapes.js +111 -0
  93. package/lib/sql-identifier.js +1 -1
  94. package/lib/transform/.eslintrc.json +5 -0
  95. package/lib/transform/braceExpression.js +4 -2
  96. package/lib/transform/db/.eslintrc.json +2 -0
  97. package/lib/transform/db/applyTransformations.js +35 -12
  98. package/lib/transform/db/assertUnique.js +1 -1
  99. package/lib/transform/db/associations.js +187 -0
  100. package/lib/transform/db/cdsPersistence.js +150 -0
  101. package/lib/transform/db/constraints.js +61 -56
  102. package/lib/transform/db/expansion.js +50 -29
  103. package/lib/transform/db/flattening.js +552 -105
  104. package/lib/transform/db/groupByOrderBy.js +3 -1
  105. package/lib/transform/db/temporal.js +236 -0
  106. package/lib/transform/db/transformExists.js +94 -28
  107. package/lib/transform/db/views.js +5 -4
  108. package/lib/transform/draft/.eslintrc.json +38 -0
  109. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  110. package/lib/transform/draft/odata.js +227 -0
  111. package/lib/transform/forHanaNew.js +94 -801
  112. package/lib/transform/forOdataNew.js +22 -175
  113. package/lib/transform/localized.js +36 -32
  114. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  115. package/lib/transform/odata/referenceFlattener.js +95 -89
  116. package/lib/transform/odata/structureFlattener.js +1 -1
  117. package/lib/transform/odata/toFinalBaseType.js +86 -12
  118. package/lib/transform/odata/typesExposure.js +5 -5
  119. package/lib/transform/odata/utils.js +2 -2
  120. package/lib/transform/transformUtilsNew.js +47 -33
  121. package/lib/transform/translateAssocsToJoins.js +10 -27
  122. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  123. package/lib/transform/universalCsn/coreComputed.js +170 -0
  124. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  125. package/lib/transform/universalCsn/utils.js +63 -0
  126. package/lib/utils/file.js +2 -1
  127. package/lib/utils/objectUtils.js +30 -0
  128. package/lib/utils/timetrace.js +8 -2
  129. package/package.json +1 -1
  130. package/share/messages/README.md +26 -0
  131. package/lib/compiler/definer.js +0 -2340
  132. package/lib/compiler/resolver.js +0 -2988
  133. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -1,19 +1,18 @@
1
1
  'use strict';
2
2
 
3
3
  const { setProp, isBetaEnabled } = require('../base/model');
4
- const { getUtils, cloneCsn, forEachGeneric,
5
- forEachMember,
6
- forEachMemberRecursively, forEachRef, getNamespace, getResultingName,
7
- forAllQueries, forAllElements, hasAnnotationValue, getArtifactDatabaseNameOf,
8
- getElementDatabaseNameOf, isBuiltinType, applyTransformations,
9
- isPersistedOnDatabase, getNormalizedQuery, isAspect, walkCsnPath,
4
+ const { getUtils, cloneCsn,
5
+ forEachMemberRecursively, forAllQueries, applyTransformationsOnNonDictionary,
6
+ getArtifactDatabaseNameOf, getElementDatabaseNameOf, isBuiltinType, applyTransformations,
7
+ isAspect, walkCsnPath,
10
8
  } = require('../model/csnUtils');
11
9
  const { makeMessageFunction } = require('../base/messages');
12
10
  const transformUtils = require('./transformUtilsNew');
13
11
  const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');
14
- const { csnRefs, pathId, implicitAs } = require('../model/csnRefs');
12
+ const { csnRefs, pathId } = require('../model/csnRefs');
15
13
  const { checkCSNVersion } = require('../json/csnVersion');
16
14
  const validate = require('../checks/validator');
15
+ const { rejectManagedAssociationsAndStructuresForHdbcdsNames } = require('../checks/selectItems');
17
16
  const { addLocalizationViewsWithJoins, addLocalizationViews } = require('../transform/localized');
18
17
  const { timetrace } = require('../utils/timetrace');
19
18
  const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
@@ -24,9 +23,13 @@ const _forEachDefinition = require('../model/csnUtils').forEachDefinition;
24
23
  const flattening = require('./db/flattening');
25
24
  const expansion = require('./db/expansion');
26
25
  const assertUnique = require('./db/assertUnique');
27
- const generateDrafts = require('./db/draft');
28
- const enrichUniversalCsn = require('./universalCsnEnricher');
26
+ const generateDrafts = require('./draft/db');
27
+ const enrichUniversalCsn = require('./universalCsn/universalCsnEnricher');
29
28
  const { getViewTransformer } = require('./db/views');
29
+ const cdsPersistence = require('./db/cdsPersistence');
30
+ const temporal = require('./db/temporal');
31
+ const associations = require('./db/associations')
32
+ const { ModelError } = require("../base/error");
30
33
 
31
34
  // By default: Do not process non-entities/views
32
35
  function forEachDefinition(csn, cb) {
@@ -111,19 +114,19 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
111
114
 
112
115
  const pathDelimiter = (options.forHana.names === 'hdbcds') ? '.' : '_';
113
116
 
114
- let error, warning, info; // message functions
117
+ let message, error, warning, info; // message functions
115
118
  /** @type {() => void} */
116
119
  let throwWithError;
117
- let artifactRef, inspectRef, effectiveType, // csnRefs
118
- addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType, getFinalBaseType, // transformUtils
119
- get$combined; // csnUtils
120
+ let artifactRef, inspectRef, effectiveType, get$combined,
121
+ getFinalBaseType, // csnUtils (csnRefs)
122
+ addDefaultTypeFacets, expandStructsInExpression; // transformUtils
120
123
 
121
124
  bindCsnReference();
122
125
 
123
126
  throwWithError(); // reclassify and throw in case of non-configurable errors
124
-
127
+
125
128
  if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
126
- enrichUniversalCsn(csn, options);
129
+ enrichUniversalCsn(csn, options);
127
130
  bindCsnReference();
128
131
  }
129
132
 
@@ -134,7 +137,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
134
137
 
135
138
  // Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
136
139
  const cleanup = validate.forHana(csn, {
137
- error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, getFinalBaseType, isAspect
140
+ message, error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, getFinalBaseType, isAspect
138
141
  });
139
142
 
140
143
  // Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
@@ -145,6 +148,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
145
148
  // subsequent procession steps (especially a2j) to see plain paths in expressions.
146
149
  // If errors are detected, throwWithError() will return from further processing
147
150
 
151
+ // If this function is ever undefined, we have a bug in our logic.
152
+ // @ts-ignore
148
153
  expandStructsInExpression(csn, { drillRef: true });
149
154
 
150
155
  throwWithError();
@@ -155,7 +160,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
155
160
 
156
161
  // (001) Add a temporal where condition to views where applicable before assoc2join
157
162
  // assoc2join eventually rewrites the table aliases
158
- forEachDefinition(csn, addTemporalWhereConditionToView);
163
+ forEachDefinition(csn, temporal.getViewDecorator(csn, {info}));
159
164
 
160
165
  // check unique constraints - further processing is done in rewriteUniqueConstraints
161
166
  assertUnique.prepare(csn, options, error, info);
@@ -215,52 +220,23 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
215
220
 
216
221
  const {
217
222
  flattenStructuredElement,
218
- flattenStructStepsInRef, getForeignKeyArtifact,
223
+ flattenStructStepsInRef,
219
224
  isAssociationOperand, isDollarSelfOrProjectionOperand,
220
- extractValidFromToKeyElement, checkAssignment, checkMultipleAssignments,
221
- recurseElements
222
225
  } = transformUtils.getTransformers(csn, options, pathDelimiter);
223
226
 
224
227
  const {
225
228
  getCsnDef,
226
229
  isAssocOrComposition,
227
- isManagedAssociationElement,
228
- isStructured,
229
230
  addStringAnnotationTo,
230
231
  cloneWithTransformations,
231
- getContextOfArtifact,
232
232
  } = getUtils(csn);
233
233
 
234
234
  // (000) Rename primitive types, make UUID a String
235
235
  transformCsn(csn, {
236
- type: (val, node, key, path) => {
237
- // Resolve type-of chains
238
- function fn() {
239
- // val can be undefined: books as myBooks : redirected to Model.MyBooks
240
- if (val && val.ref) {
241
- const { art } = inspectRef(path);
242
- if (art && art.type) {
243
- val = art.type;
244
- // This is somehow needed to update the ref so that inspectRef sees it
245
- node[key] = val;
246
- if (val.ref)
247
- fn();
248
- }
249
- else {
250
- // Doesn't seem to ever ocurr
251
- }
252
- }
253
- }
254
- fn();
236
+ type: (val, node, key) => {
255
237
  renamePrimitiveTypesAndUuid(val, node, key);
256
238
  addDefaultTypeFacets(node);
257
239
  },
258
- cast: (val) => {
259
- if (options.forHana.names === 'plain' || options.toSql )
260
- toFinalBaseType(val);
261
- renamePrimitiveTypesAndUuid(val.type, val, 'type');
262
- addDefaultTypeFacets(val);
263
- },
264
240
  // HANA/SQLite do not support array-of - turn into CLOB/Text
265
241
  items: (val, node) => {
266
242
  node.type = 'cds.LargeString';
@@ -271,36 +247,32 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
271
247
  // (040) Ignore entities and views that are abstract or implemented
272
248
  // or carry the annotation cds.persistence.skip/exists
273
249
  // These entities are not removed from the csn, but flagged as "to be ignored"
274
- forEachDefinition(csn, handleCdsPersistence);
250
+ forEachDefinition(csn, cdsPersistence.getAnnoProcessor());
275
251
 
276
252
 
277
253
  // (050) Check @cds.valid.from/to only on entity
278
254
  // Views are checked in (001), unbalanced valid.from/to's or mismatching origins
279
- // Temporal only in beta-mode
280
- forEachDefinition(csn, handleTemporalAnnotations);
281
-
282
- handleManagedAssociationsAndCreateForeignKeys();
283
-
284
- function handleManagedAssociationsAndCreateForeignKeys() {
285
- forEachDefinition(csn, (art, artName) => handleManagedAssociationFKs(art, artName));
286
- forEachDefinition(csn, (art, artName) => createForeignKeyElements(art, artName));
287
- }
255
+ forEachDefinition(csn, temporal.getAnnotationHandler(csn, options, pathDelimiter, {error}));
288
256
 
289
- forEachDefinition(csn, flattenIndexes);
290
- // Basic handling of associations in views and entities
291
- forEachDefinition(csn, handleAssociations);
257
+ // eliminate the doA2J in the functions 'handleManagedAssociationFKs' and 'createForeignKeyElements'
258
+ doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, pathDelimiter, true, { allowArtifact: artifact => (artifact.kind === 'entity') });
259
+
260
+ doA2J && forEachDefinition(csn, flattenIndexes);
261
+ // Managed associations get an on-condition - in views and entities
262
+ doA2J && associations.attachOnConditions(csn, pathDelimiter);
292
263
 
293
264
  // (045) Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',
294
265
  // and make them entities
295
- forEachDefinition(csn, handleQueryish);
266
+ forEachDefinition(csn, cdsPersistence.getPersistenceTableProcessor(csn, options, {error}));
296
267
 
297
268
  // Allow using managed associations as steps in on-conditions to access their fks
298
269
  // To be done after handleAssociations, since then the foreign keys of the managed assocs
299
270
  // are part of the elements
300
- forEachDefinition(csn, handleManagedAssocStepsInOnCondition);
271
+ if (doA2J)
272
+ forEachDefinition(csn, associations.getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter));
301
273
 
302
274
  // Create convenience views for localized entities/views.
303
- // To be done after handleManagedAssocStepsInOnCondition because associations are
275
+ // To be done after getManagedAssocStepsInOnConditionFinalizer because associations are
304
276
  // handled and before handleDBChecks which removes the localized attribute.
305
277
  // Association elements of localized convenience views do not have hidden properties
306
278
  // like $managed set, so we cannot do this earlier on.
@@ -309,6 +281,14 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
309
281
  else
310
282
  addLocalizationViews(csn, options);
311
283
 
284
+ !doA2J && forEachDefinition(csn, (definition, artName, prop, path) => {
285
+ if (definition.query) {
286
+ // reject managed association and structure publishing for to-hdbcds.hdbcds
287
+ const that = { csnUtils: getUtils(csn), options, error };
288
+ rejectManagedAssociationsAndStructuresForHdbcdsNames.call(that, definition, path)
289
+ }
290
+ });
291
+
312
292
  // For generating DB stuff:
313
293
  // - table-entity with parameters: not allowed
314
294
  // - view with parameters: ok on HANA, not allowed otherwise
@@ -329,18 +309,14 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
329
309
  // because otherwise we would produce wrong ON-conditions for the keys involved. Sigh ...
330
310
  forEachDefinition(csn, transformSelfInBacklinks);
331
311
 
332
- if(options.forHana){
333
- /**
334
- * Referential Constraints are only supported for sql-dialect "hana" and "sqlite".
335
- * For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
336
- * hence we do not generate the referential constraints for them.
337
- */
338
- const validOptionsForConstraint = () => {
339
- return (options.forHana.dialect === 'sqlite' || options.forHana.dialect === 'hana') && doA2J;
340
- }
341
- if(validOptionsForConstraint())
342
- createReferentialConstraints(csn, options);
343
- }
312
+ /**
313
+ * Referential Constraints are only supported for sql-dialect "hana" and "sqlite".
314
+ * For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
315
+ * hence we do not generate the referential constraints for them.
316
+ */
317
+ if((options.sqlDialect === 'hana' || options.sqlDialect === 'sqlite') && doA2J)
318
+ createReferentialConstraints(csn, options);
319
+
344
320
  // no constraints for drafts
345
321
  generateDrafts(csn, options, pathDelimiter, { info, warning, error });
346
322
 
@@ -348,8 +324,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
348
324
  // See function comment for extensive information.
349
325
  assertUnique.rewrite(csn, options, pathDelimiter);
350
326
 
351
- // Associations that point to thins marked with @cds.persistence.skip are removed
352
- forEachDefinition(csn, ignoreAssociationToSkippedTarget);
327
+ // Associations that point to things marked with @cds.persistence.skip are removed
328
+ forEachDefinition(csn, cdsPersistence.getAssocToSkippedIgnorer(csn, options, {info}));
353
329
 
354
330
  // Apply view-specific transformations
355
331
  // (160) Projections now finally become views
@@ -363,30 +339,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
363
339
  const checkConstraintIdentifiers = (artifact, artifactName, prop, path) => {
364
340
  assertConstraintIdentifierUniqueness(artifact, artifactName, path, error);
365
341
  };
366
- const removeNamespaces = (artifact, artifactName) => {
367
- if (artifact.kind === 'namespace')
368
- delete csn.definitions[artifactName];
369
- };
370
- const ignoreNonPersistedArtifactsWithAnonymousAspectComposition = (artifact) => {
371
- if(artifact.kind === 'type' || artifact.kind === 'aspect' || artifact.kind === 'entity' && artifact.abstract){
372
- if(artifact.elements && Object.keys(artifact.elements).some((elementName) => {
373
- const element = artifact.elements[elementName];
374
- return !element.target && element.targetAspect && typeof element.targetAspect !== 'string';
375
- })) {
376
- artifact._ignore = true;
377
- }
378
- }
379
- };
380
342
 
381
343
  forEachDefinition(csn, [
382
344
  /* assert that there will be no conflicting unique- and foreign key constraint identifiers */
383
345
  checkConstraintIdentifiers,
384
- /* (250) Remove all namespaces from definitions */
385
- removeNamespaces,
386
346
  /* Check Type Parameters (precision, scale, length ...) */
387
347
  checkTypeParameters,
388
- /* Filter out aspects/types/abstract entities containing managed compositions of anonymous aspects */
389
- ignoreNonPersistedArtifactsWithAnonymousAspectComposition,
390
348
  // (200) Strip 'key' property from type elements
391
349
  removeKeyPropInType,
392
350
  ]);
@@ -426,7 +384,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
426
384
  '$key': killProp
427
385
  }
428
386
 
429
- applyTransformations(csn, killers, [], false);
387
+ applyTransformations(csn, killers, [], { skipIgnore: false});
430
388
 
431
389
  redoProjections.forEach(fn => fn());
432
390
 
@@ -434,68 +392,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
434
392
 
435
393
  /* ----------------------------------- Functions start here -----------------------------------------------*/
436
394
 
437
- /**
438
- * Create the foreign key elements for managed associations.
439
- * Create them in-place, right after the corresponding association.
440
- *
441
- *
442
- * @param {CSN.Artifact} art
443
- * @param {string} artName
444
- */
445
- function createForeignKeyElements(art, artName) {
446
- if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
447
- forAllElements(art, artName, (parent, elements, pathToElements) => {
448
- const elementsArray = [];
449
- forEachGeneric(parent, 'elements', (element, elemName) => {
450
- elementsArray.push([elemName, element]);
451
- if (isManagedAssociationElement(element)) {
452
- if (element.keys) {
453
- for(let i = 0; i < element.keys.length; i++){
454
- const foreignKey = element.keys[i];
455
- const path = [...pathToElements, elemName, 'keys', i];
456
- foreignKey.ref = flattenStructStepsInRef(foreignKey.ref, path);
457
- const [fkName, fkElem] = getForeignKeyArtifact(element, elemName, foreignKey, path);
458
- if(parent.elements[fkName]) {
459
- error(null, [...pathToElements, elemName], { name: fkName, art: elemName },
460
- 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
461
- } else {
462
- elementsArray.push([fkName, fkElem]);
463
- }
464
- applyCachedAlias(foreignKey);
465
- // join ref array as the struct / assoc steps are not necessary anymore
466
- foreignKey.ref = [foreignKey.ref.join(pathDelimiter)]
467
- }
468
- }
469
- }
470
- });
471
-
472
- // Don't fake consistency of the model by adding empty elements {}
473
- if(elementsArray.length === 0)
474
- return;
475
-
476
- parent.elements = elementsArray.reduce((previous, [name, element]) => {
477
- previous[name] = element;
478
- return previous;
479
- }, Object.create(null));
480
-
481
- })
482
- }
483
-
484
- function applyCachedAlias(foreignKey) {
485
- // If we have a $ref use that - it resolves aliased FKs correctly
486
- if (foreignKey.$ref) {
487
- foreignKey.ref = foreignKey.$ref;
488
- delete foreignKey.$ref;
489
- }
490
- }
491
- }
492
-
493
-
494
395
  function bindCsnReference(){
495
- ({ error, warning, info, throwWithError } = makeMessageFunction(csn, options, moduleName));
496
- ({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
497
- ({ getFinalBaseType, get$combined } = getUtils(csn));
498
- ({ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter));
396
+ ({ error, warning, info, message, throwWithError } = makeMessageFunction(csn, options, moduleName));
397
+ ({ artifactRef, inspectRef, effectiveType, getFinalBaseType, get$combined } = getUtils(csn));
398
+ ({ addDefaultTypeFacets, expandStructsInExpression } = transformUtils.getTransformers(csn, options, pathDelimiter));
499
399
  }
500
400
 
501
401
  function bindCsnReferenceOnly(){
@@ -522,8 +422,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
522
422
  }
523
423
  })
524
424
  }
525
- }
526
- , [ 'definitions', artifactName, 'query' ]);
425
+ }, [ 'definitions', artifactName, 'query' ]);
426
+
527
427
  function getResolvedMixinOnCondition(csn, mixinAssociation, query, assocName, path){
528
428
  const { inspectRef } = csnRefs(csn);
529
429
  const referencedThroughStar = query.SELECT.columns.some((column) => column === '*');
@@ -574,7 +474,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
574
474
  function transformViews(artifact, artifactName) {
575
475
  if (!artifact._ignore) {
576
476
  // Do things specific for entities and views (pass 2)
577
- if ((artifact.kind === 'entity' || artifact.kind === 'view') && artifact.query) {
477
+ if ((artifact.kind === 'entity') && artifact.query) {
578
478
  forAllQueries(artifact.query, (q, p) => {
579
479
  transformEntityOrViewPass2(q, artifact, artifactName, p)
580
480
  replaceAssociationsInGroupByOrderBy(q, options, inspectRef, error, p);
@@ -589,7 +489,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
589
489
  */
590
490
  function recursivelyApplyCommon(artifact, artifactName) {
591
491
  if (!artifact._ignore) {
592
- if (![ 'service', 'context', 'namespace', 'annotation', 'action', 'function' ].includes(artifact.kind))
492
+ if (artifact.kind !== 'service' && artifact.kind !== 'context')
593
493
  addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.forHana.names, csn), artifact);
594
494
 
595
495
  forEachMemberRecursively(artifact, (member, memberName, property, path) => {
@@ -608,9 +508,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
608
508
  * @param {string} artifactName
609
509
  */
610
510
  function removeKeyPropInType(artifact, artifactName) {
611
- if (!artifact._ignore) {
511
+ if (!artifact._ignore && artifact.kind === 'type') {
612
512
  forEachMemberRecursively(artifact, (member) => {
613
- if (artifact.kind === 'type' && member.key)
513
+ if (member.key)
614
514
  delete member.key;
615
515
  }, [ 'definitions', artifactName ]);
616
516
  }
@@ -624,7 +524,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
624
524
  function transformSelfInBacklinks(artifact, artifactName, dummy, path) {
625
525
  // Fixme: For toHana mixins must be transformed, for toSql -d hana
626
526
  // mixin elements must be transformed, why can't toSql also use mixins?
627
- doit(artifact.elements, path.concat([ 'elements' ]));
527
+ if(artifact.kind === 'entity' || artifact.query || (options.toHana && options.toHana.names === 'hdbcds' && artifact.kind == 'type'))
528
+ doit(artifact.elements, path.concat([ 'elements' ]));
628
529
  if (artifact.query && artifact.query.SELECT && artifact.query.SELECT.mixin)
629
530
  doit(artifact.query.SELECT.mixin, path.concat([ 'query', 'SELECT', 'mixin' ]));
630
531
 
@@ -658,91 +559,38 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
658
559
  if (artifact.params) {
659
560
  // HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
660
561
  // SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
661
- error(null, path, 'Unexpected association in parameterized view');
562
+ message('def-unexpected-paramview-assoc', path, { '#': 'view' });
662
563
  }
663
564
  else if(artifact['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
664
565
  // UDF/CVs w/o params don't support 'WITH ASSOCIATIONS'
665
- error(null, path, `Associations are not allowed in entities annotated with @cds.persistence { udf, calcview }`);
566
+ const anno = artifact['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
567
+ message('def-unexpected-calcview-assoc', path, { '#': 'entity-persistence', anno });
666
568
  }
667
569
  if (csn.definitions[member.target].params) {
668
570
  // HANA does not allow association targets with parameters or to UDFs/CVs w/o parameters:
669
571
  // SAP DBTech JDBC: [7]: feature not supported: cannot support create association to a parameterized view
670
- error(null, path, 'Unexpected parameterized association target');
572
+ message('def-unexpected-paramview-assoc', path, { '#': 'target' });
671
573
  }
672
- else if(csn.definitions[member.target]['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
574
+ else if(csn.definitions[member.target]['@cds.persistence.udf'] || csn.definitions[member.target]['@cds.persistence.calcview']) {
673
575
  // HANA won't check the assoc target but when querying an association with target UDF, this is the error:
674
576
  // SAP DBTech JDBC: [259]: invalid table name: target object SYSTEM.UDF does not exist: line 3 col 6 (at pos 43)
675
577
  // CREATE TABLE F (id INTEGER NOT NULL);
676
578
  // CREATE FUNCTION UDF RETURNS TABLE (ID INTEGER) LANGUAGE SQLSCRIPT SQL SECURITY DEFINER AS BEGIN RETURN SELECT ID FROM F; END;
677
579
  // CREATE TABLE Y ( id INTEGER NOT NULL, toUDF_id INTEGER) WITH ASSOCIATIONS (MANY TO ONE JOIN UDF AS toUDF ON (toUDF.id = toUDF_id));
678
580
  // CREATE VIEW U AS SELECT id, toUDF.a FROM Y;
679
- error(null, path, `Associations can't point to entities annotated with @cds.persistence { udf, calcview }`);
581
+ const anno = csn.definitions[member.target]['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
582
+ message('def-unexpected-calcview-assoc', path, { '#': 'target-persistence', anno });
680
583
  }
681
584
  }
682
585
  }, [ 'definitions', artifactName ]);
683
586
  }
684
587
 
685
- /**
686
- *
687
- * Generate foreign keys for managed associations
688
- * Forbid aliases for foreign keys
689
- *
690
- * @param {CSN.Artifact} artifact
691
- * @param {string} artifactName
692
- */
693
- function handleAssociations(artifact, artifactName) {
694
- // Do things specific for entities and views (pass 1)
695
- if (artifact.kind === 'entity' || artifact.kind === 'view') {
696
- const alreadyHandled = new WeakMap();
697
- forAllElements(artifact, artifactName, (parent, elements) => {
698
- for (const elemName in elements) {
699
- const elem = elements[elemName];
700
- // (140) Generate foreign key elements and ON-condition for managed associations
701
- // (unless explicitly asked to keep assocs unchanged)
702
- if (doA2J) {
703
- if (isManagedAssociationElement(elem))
704
- transformManagedAssociation(parent, artifactName, elem, elemName, alreadyHandled);
705
- }
706
- }
707
- })
708
- }
709
- }
710
-
711
- function fixBorkedElementsOfLocalized(elements, pathToElements){
712
- const pathToNonLocalized = ['definitions', pathToElements[1].replace('localized.',''), ...pathToElements.slice(2)];
713
- const nonLocalizedElements = walkCsnPath(csn, pathToNonLocalized);
714
-
715
-
716
- for(const elementName in elements){
717
- const element = elements[elementName];
718
- const reference = nonLocalizedElements[elementName];
719
-
720
- // if the declared element is an enum, these values are with priority
721
- if (!element.enum && reference.enum)
722
- Object.assign(element, { enum: reference.enum });
723
- if (!element.length && reference.length && !reference.$default)
724
- Object.assign(element, { length: reference.length });
725
- if (!element.precision && reference.precision)
726
- Object.assign(element, { precision: reference.precision });
727
- if (!element.scale && reference.scale)
728
- Object.assign(element, { scale: reference.scale });
729
- if (!element.srid && reference.srid)
730
- Object.assign(element, { srid: reference.srid });
731
- if (!element.keys && reference.keys)
732
- Object.assign(element, { keys: cloneCsn(reference.keys, options)})
733
- if (!element.type && reference.type)
734
- Object.assign(element, { type: reference.type})
735
- if (!element.on && reference.on && !reference.keys)
736
- Object.assign(element, {on: cloneCsn(reference.on, options)})
737
- }
738
- }
739
-
740
588
  /**
741
589
  * @param {CSN.Artifact} artifact
742
590
  * @param {string} artifactName
743
591
  */
744
592
  function handleChecksForWithParameters(artifact, artifactName) {
745
- if (!artifact._ignore && artifact.params && (artifact.kind === 'entity' || artifact.kind === 'view')) {
593
+ if (!artifact._ignore && artifact.params && (artifact.kind === 'entity')) {
746
594
  if (!artifact.query) { // table entity with params
747
595
  // Allow with plain
748
596
  error(null, [ 'definitions', artifactName ], { '#': options.toSql ? 'sql' : 'std' }, {
@@ -768,65 +616,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
768
616
  }
769
617
  }
770
618
 
771
- /**
772
- * @param {CSN.Artifact} artifact
773
- * @param {string} artifactName
774
- */
775
- function handleQueryish(artifact, artifactName) {
776
- const stripQueryish = artifact.query && hasAnnotationValue(artifact, '@cds.persistence.table');
777
-
778
- if (stripQueryish) {
779
- artifact.kind = 'entity';
780
- delete artifact.query;
781
- }
782
-
783
- recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
784
- // All elements must have a type for this to work
785
- if (stripQueryish && !member._ignore && !member.kind && !member.type)
786
- error(null, path, 'Expecting element to have a type if view is annotated with “@cds.persistence.table“');
787
- });
788
- }
789
-
790
- /**
791
- * @param {CSN.Artifact} artifact
792
- * @param {string} artifactName
793
- */
794
- function handleCdsPersistence(artifact, artifactName) {
795
- if (artifact.kind === 'entity' || artifact.kind === 'view') {
796
- if (artifact.abstract
797
- || hasAnnotationValue(artifact, '@cds.persistence.skip')
798
- || hasAnnotationValue(artifact, '@cds.persistence.exists'))
799
- artifact._ignore = true;
800
-
801
- const namingMode = options.forHana && options.forHana.names;
802
- // issue #3450 HANA CDS can not handle external artifacts which are part of a HANA CDS context
803
- if (hasAnnotationValue(artifact, '@cds.persistence.exists') &&
804
- options.transformation === 'hdbcds' &&
805
- (namingMode === 'quoted' || namingMode === 'hdbcds')) {
806
- let hanaCDSContextName;
807
- if(namingMode === 'hdbcds') {
808
- // for hdbcds names we only create a context if you defined a context/service in your cdl model
809
- hanaCDSContextName = getContextOfArtifact(artifactName);
810
- }
811
- else {
812
- // for quoted naming mode, we create a context if you either defined a context/service
813
- // or a namespace in your cdl model
814
- hanaCDSContextName = getContextOfArtifact(artifactName) || getNamespace(csn, artifactName);
815
- }
816
- if (hanaCDSContextName) {
817
- warning('anno-unstable-hdbcds', [ 'definitions', artifactName ],
818
- {
819
- id: getResultingName(csn, options.forHana.names, artifactName),
820
- name: getResultingName(csn, options.forHana.names, hanaCDSContextName),
821
- anno: 'cds.persistence.exists',
822
- },
823
- 'Do not use $(ANNO) on an entity named $(ID) in SAP HANA CDS when we also create a SAP HANA CDS context named $(NAME)'
824
- );
825
- }
826
- }
827
- }
828
- }
829
-
830
619
  function handleAssocToJoins() {
831
620
  // With flattening errors, it makes little sense to continue.
832
621
  throwWithError();
@@ -848,216 +637,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
848
637
  csn = newCsn;
849
638
  }
850
639
 
851
- /**
852
- * @param {CSN.Artifact} artifact
853
- * @param {string} artifactName
854
- */
855
- function handleTemporalAnnotations(artifact, artifactName) {
856
- const validFrom = [];
857
- const validTo = [];
858
- const validKey = [];
859
-
860
- recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
861
- const [ f, t, k ] = extractValidFromToKeyElement(member, path);
862
- validFrom.push(...f);
863
- validTo.push(...t);
864
- validKey.push(...k);
865
- });
866
-
867
- if (artifact.kind === 'entity' && !artifact.query) {
868
- validFrom.forEach(obj => checkAssignment('@cds.valid.from', obj.element, obj.path, artifact));
869
- validTo.forEach(obj => checkAssignment('@cds.valid.to', obj.element, obj.path, artifact));
870
- validKey.forEach(obj => checkAssignment('@cds.valid.key', obj.element, obj.path, artifact));
871
- checkMultipleAssignments(validFrom, '@cds.valid.from', artifact, artifactName);
872
- checkMultipleAssignments(validTo, '@cds.valid.to', artifact, artifactName, true);
873
- checkMultipleAssignments(validKey, '@cds.valid.key', artifact, artifactName);
874
- }
875
-
876
- // if there is an cds.valid.key, make this the only primary key
877
- // otherwise add all cds.valid.from to primary key tuple
878
- if (validKey.length) {
879
- if (!validFrom.length || !validTo.length)
880
- error(null, [ 'definitions', artifactName ],
881
- 'Expecting “@cds.valid.from” and “@cds.valid.to” if “@cds.valid.key” is used');
882
640
 
883
- forEachMember(artifact, (member) => {
884
- if (member.key) {
885
- member.unique = true;
886
- delete member.key;
887
- // Remember that this element was a key in the original artifact.
888
- // This is needed for localized convenience view generation.
889
- setProp(member, '$key', true);
890
- }
891
- });
892
- validKey.forEach((member) => {
893
- member.element.key = true;
894
- });
895
-
896
- validFrom.forEach((member) => {
897
- member.element.unique = true;
898
- });
899
- }
900
- else {
901
- validFrom.forEach((member) => {
902
- member.element.key = true;
903
- });
904
- }
905
- }
906
-
907
- function hasFalsyTemporalAnnotations(SELECT, elements, from, to) {
908
- let fromElement = elements[from.name];
909
- let toElement = elements[to.name];
910
-
911
- if(SELECT.columns) {
912
- for(const col of SELECT.columns) {
913
- if(col.ref) {
914
- const implicitAlias = implicitAs(col.ref);
915
- if(implicitAlias === from.name)
916
- fromElement = elements[col.as || implicitAlias];
917
- else if(implicitAlias === to.name)
918
- toElement = elements[col.as || implicitAlias];
919
- }
920
- }
921
- }
922
- const val = fromElement && toElement && hasAnnotationValue(fromElement, '@cds.valid.from', false) && hasAnnotationValue(toElement, '@cds.valid.to', false);
923
- return val;
924
- }
925
-
926
- /**
927
- * Add a where condition to views that
928
- * - are annotated with @cds.valid.from and @cds.valid.to,
929
- * - have only one @cds.valid.from and @cds.valid.to,
930
- * - and both annotations come from the same entity
931
- *
932
- * If the view has one of the annotations but the other conditions are not met, an error will be raised.
933
- *
934
- * @param {CSN.Artifact} artifact
935
- * @param {string} artifactName
936
- */
937
- function addTemporalWhereConditionToView(artifact, artifactName) {
938
- const normalizedQuery = getNormalizedQuery(artifact);
939
- if (normalizedQuery && normalizedQuery.query && normalizedQuery.query.SELECT) {
940
- // BLOCKER: We need information to handle $combined
941
- // What we are trying to achieve by this:
942
- // Forbid joining/selecting from two or more temporal entities
943
- // Idea: Follow the query-tree and check each from
944
- // Collect all source-entities and compute our own $combined
945
- const $combined = get$combined(normalizedQuery.query);
946
- const [ from, to ] = getFromToElements($combined);
947
- // exactly one validFrom & validTo
948
- if (from.length === 1 && to.length === 1) {
949
- // and both are from the same origin
950
- if (from[0].source === to[0].source && from[0].parent === to[0].parent) {
951
- if(!hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0])) {
952
- const fromPath = {
953
- ref: [
954
- from[0].parent,
955
- from[0].name,
956
- ],
957
- };
958
-
959
- const toPath = {
960
- ref: [
961
- to[0].parent,
962
- to[0].name,
963
- ],
964
- };
965
-
966
-
967
- const atFrom = { ref: [ '$at', 'from' ] };
968
- const atTo = { ref: [ '$at', 'to' ] };
969
-
970
- const cond = [ '(', fromPath, '<', atTo, 'and', toPath, '>', atFrom, ')' ];
971
-
972
- if (normalizedQuery.query.SELECT.where) { // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
973
- normalizedQuery.query.SELECT.where = [ '(', ...normalizedQuery.query.SELECT.where, ')', 'and', ...cond ];
974
- }
975
- else {
976
- normalizedQuery.query.SELECT.where = cond;
977
- }
978
- }
979
- }
980
- else {
981
- info(null, [ 'definitions', artifactName ], `No temporal WHERE clause added as "${ from[0].error_parent }"."${ from[0].name }" and "${ to[0].error_parent }"."${ to[0].name }" are not of same origin`);
982
- }
983
- }
984
- else if (from.length > 0 || to.length > 0) {
985
- const missingAnnotation = from.length > to.length ? '@cds.valid.to' : '@cds.valid.from';
986
- info(null, [ 'definitions', artifactName ],
987
- { anno: missingAnnotation },
988
- 'No temporal WHERE clause added because $(ANNO) is missing'
989
- )
990
- }
991
- }
992
- }
993
-
994
- /**
995
- * Get all elements tagged with @cds.valid.from/to from the union of all entities of the from-clause.
996
- *
997
- * @param {any} combined union of all entities of the from-clause
998
- * @returns {Array[]} Array where first field is array of elements with @cds.valid.from, second field is array of elements with @cds.valid.to.
999
- */
1000
- function getFromToElements(combined) {
1001
- const from = [];
1002
- const to = [];
1003
- for (const name in combined) {
1004
- let elt = combined[name];
1005
- if (!Array.isArray(elt))
1006
- elt = [ elt ];
1007
- elt.forEach((e) => {
1008
- if (hasAnnotationValue(e.element, '@cds.valid.from'))
1009
- from.push(e);
1010
-
1011
- if (hasAnnotationValue(e.element, '@cds.valid.to'))
1012
- to.push(e);
1013
- });
1014
- }
1015
-
1016
- return [ from, to ];
1017
- }
1018
-
1019
- /**
1020
- * Associations that target a @cds.persistence.skip artifact must be removed
1021
- * from the persistence model
1022
- *
1023
- * @param {CSN.Artifact} artifact
1024
- * @param {string} artifactName
1025
- * @param {string} prop
1026
- * @param {CSN.Path} path
1027
- */
1028
- function ignoreAssociationToSkippedTarget(artifact, artifactName, prop, path) {
1029
- if (isPersistedOnDatabase(artifact)) {
1030
- // TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
1031
- if (artifact.query) {
1032
- // If we do A2J, we don't need to check the mixin. Either it is used -> a join
1033
- // or published -> handled via elements/members. Unused mixins are removed anyway.
1034
- if (!doA2J && artifact.query.SELECT && artifact.query.SELECT.mixin)
1035
- forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
1036
-
1037
- else if (!doA2J && artifact.query.SET && artifact.query.SET.mixin)
1038
- forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
1039
- }
1040
- forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
1041
- }
1042
- function ignore(member, memberName, prop, path) {
1043
- if (dialect === 'hana' && !member._ignore && member.target && isAssocOrComposition(member.type) && isUnreachableAssociationTarget(csn.definitions[member.target])) {
1044
- const targetAnnotation = hasAnnotationValue(csn.definitions[member.target], '@cds.persistence.exists') ? '@cds.persistence.exists' : '@cds.persistence.skip';
1045
- info(null, path,
1046
- { target: member.target, anno: targetAnnotation },
1047
- 'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)'
1048
- );
1049
- member._ignore = true;
1050
- }
1051
- }
1052
- }
1053
-
1054
- /**
1055
- * @param {CSN.Artifact} art
1056
- * @returns {boolean}
1057
- */
1058
- function isUnreachableAssociationTarget(art) {
1059
- return !isPersistedOnDatabase(art) || hasAnnotationValue(art, '@cds.persistence.exists');
1060
- }
1061
641
 
1062
642
  /**
1063
643
  * Remove `localized` from elements and replace Enum symbols by their values.
@@ -1280,7 +860,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1280
860
  else if (assoc.on)
1281
861
  return transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, assocName, elemName);
1282
862
 
1283
- throw new Error(`Expected either managed or unmanaged association in $self-comparison: ${ JSON.stringify(elem.on) }`);
863
+ throw new ModelError(`Expected either managed or unmanaged association in $self-comparison: ${ JSON.stringify(elem.on) }`);
1284
864
  }
1285
865
 
1286
866
  // For a condition `<elemName>.<assoc> = $self` in the ON-condition of element <elemName>,
@@ -1312,14 +892,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1312
892
  conditions.push([ a[0], '=', a[1] ]);
1313
893
  });
1314
894
 
1315
- const result = conditions.reduce((prev, current) => {
895
+ return conditions.reduce((prev, current) => {
1316
896
  if (prev.length === 0)
1317
897
  return [ ...current ];
1318
898
 
1319
899
  return [ ...prev, 'and', ...current ];
1320
900
  }, []);
1321
-
1322
- return result;
1323
901
  }
1324
902
 
1325
903
  // For a condition `<elemName>.<assoc> = $self` in the ON-condition of element <elemName>,
@@ -1337,22 +915,23 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1337
915
  const newOnCond = cloneWithTransformations(assoc.on, {
1338
916
  ref: (value) => cloneWithTransformations(value, {}),
1339
917
  });
1340
- // goes through the the newOnCond and transform all the 'path' elements
1341
- forEachRef(newOnCond, (ref) => {
1342
- if (ref[0] === assocName) // we are in the "path" from the forwarding assoc => need to remove the first part of the path
1343
- {
1344
- ref.shift();
1345
- } else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
1346
- // We could also have a $self infront of the assoc name - so we would need to shift twice
1347
- ref.shift();
1348
- ref.shift();
1349
- }
1350
- else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
1351
- ref.unshift(elemName);
1352
- // if there was a $self identifier in the forwarding association onCond
1353
- // we do not need it any more, as we prepended in the previous step the back association's id
1354
- if (ref[1] === '$self')
1355
- ref.splice(1, 1);
918
+ applyTransformationsOnNonDictionary({on: newOnCond}, 'on', {
919
+ ref: (parent, prop, ref) => {
920
+ if (ref[0] === assocName) // we are in the "path" from the forwarding assoc => need to remove the first part of the path
921
+ {
922
+ ref.shift();
923
+ } else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
924
+ // We could also have a $self infront of the assoc name - so we would need to shift twice
925
+ ref.shift();
926
+ ref.shift();
927
+ }
928
+ else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
929
+ ref.unshift(elemName);
930
+ // if there was a $self identifier in the forwarding association onCond
931
+ // we do not need it any more, as we prepended in the previous step the back association's id
932
+ if (ref[1] === '$self')
933
+ ref.splice(1, 1);
934
+ }
1356
935
  }
1357
936
  });
1358
937
  return newOnCond;
@@ -1455,148 +1034,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1455
1034
  return !(options.toSql && type === 'cds.String' && (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain'));
1456
1035
  }
1457
1036
 
1458
- /**
1459
- * Flatten and create the foreign key elements of managed associaitons
1460
- *
1461
- * @param {CSN.Artifact} art
1462
- * @param {string} artName
1463
- */
1464
- function handleManagedAssociationFKs(art, artName) {
1465
- if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
1466
- forAllElements(art, artName, (parent, elements, pathToElements) => {
1467
- if(artName.startsWith('localized.') && pathToElements.length > 3) {
1468
- // In subqueries, the elements of localized views are missing all the important bits and pieces...
1469
- fixBorkedElementsOfLocalized(elements, pathToElements);
1470
- }
1471
- forEachGeneric(parent, 'elements', (element, elemName) => {
1472
- if (isManagedAssociationElement(element)) {
1473
- if (element.keys) {
1474
- // replace foreign keys that are managed associations by their respective foreign keys
1475
- flattenFKs(element, elemName, [ ...pathToElements, elemName ]);
1476
- }
1477
- }
1478
- });
1479
- })
1480
- }
1481
- }
1482
-
1483
-
1484
- /**
1485
- * Flattens all foreign keys
1486
- *
1487
- * Structures will be resolved to individual elements with scalar types
1488
- *
1489
- * Associations will be replaced by their respective foreign keys
1490
- *
1491
- * If a structure contains an assoc, this will also be resolved and vice versa
1492
- *
1493
- * @param {*} assoc
1494
- * @param {*} assocName
1495
- * @param {*} path
1496
- */
1497
- function flattenFKs(assoc, assocName, path) {
1498
- let finished = false;
1499
- while(!finished) {
1500
- const newKeys = [];
1501
- finished = processKeys(assoc, assocName, path, newKeys);
1502
- assoc.keys = newKeys;
1503
- }
1504
-
1505
- function processKeys(assoc, assocName, path, collector) {
1506
- let finished = true;
1507
- for (let i = 0; i < assoc.keys.length; i++) {
1508
- const pathToKey = path.concat([ 'keys', i ]);
1509
- const { art } = inspectRef(pathToKey);
1510
- const { ref } = assoc.keys[i];
1511
- if (isStructured(art)) {
1512
- finished = false;
1513
- // Mark this element to filter it later - not needed after expansion
1514
- setProp(assoc.keys[i], '$toDelete', true);
1515
- const flat = flattenStructuredElement(art, ref[ref.length - 1], [], pathToKey);
1516
- Object.keys(flat).forEach((flatElemName) => {
1517
- const key = assoc.keys[i];
1518
- const clone = cloneCsn(assoc.keys[i], options);
1519
- if (clone.as) {
1520
- const lastRef = clone.ref[clone.ref.length - 1];
1521
- // Cut off the last ref part from the beginning of the flat name
1522
- const flatBaseName = flatElemName.slice(lastRef.length);
1523
- // Join it to the existing table alias
1524
- clone.as += flatBaseName;
1525
- // do not loose the $ref for nested keys
1526
- if(key.$ref){
1527
- let aliasedLeaf = key.$ref[key.$ref.length - 1 ];
1528
- aliasedLeaf += flatBaseName;
1529
- setProp(clone, '$ref', key.$ref.slice(0, key.$ref.length - 1).concat(aliasedLeaf));
1530
- }
1531
- }
1532
- if (clone.ref) {
1533
- clone.ref[clone.ref.length - 1] = flatElemName;
1534
- // Now we need to properly flatten the whole ref
1535
- clone.ref = flattenStructStepsInRef(clone.ref, pathToKey);
1536
- }
1537
- if (!clone.as) {
1538
- clone.as = flatElemName;
1539
- // TODO: can we use $inferred? Does it have other weird side-effects?
1540
- setProp(clone, '$inferredAlias', true);
1541
- }
1542
- // Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
1543
- // Add the newly generated foreign keys to the end - they will be picked up later on
1544
- // Recursive solutions run into call stack issues
1545
- collector.push(clone);
1546
- });
1547
- }
1548
- else if (art.target) {
1549
- finished = false;
1550
- // Mark this element to filter it later - not needed after expansion
1551
- setProp(assoc.keys[i], '$toDelete', true);
1552
- // Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
1553
- // Add the newly generated foreign keys to the end - they will be picked up later on
1554
- // Recursive solutions run into call stack issues
1555
- art.keys.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i], ref)));
1556
- }
1557
- else if (assoc.keys[i].ref && !assoc.keys[i].as) {
1558
- setProp(assoc.keys[i], '$inferredAlias', true);
1559
- assoc.keys[i].as = assoc.keys[i].ref[assoc.keys[i].ref.length - 1];
1560
- collector.push(assoc.keys[i]);
1561
- } else {
1562
- collector.push(assoc.keys[i]);
1563
- }
1564
- }
1565
- return finished;
1566
- }
1567
- assoc.keys = assoc.keys.filter(o => !o.$toDelete);
1568
- }
1569
-
1570
- function cloneAndExtendRef(key, base, ref) {
1571
- const clone = cloneCsn(base, options);
1572
- if (key.ref) {
1573
- // We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
1574
- // Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
1575
- // Later on, after we know that these foreign key elements are created, we replace ref with this $ref
1576
- let $ref;
1577
- if(base.$ref){
1578
- // if a base $ref is provided, use it to correctly resolve association chains
1579
- const refChain = [base.$ref[base.$ref.length - 1]].concat(key.as || key.ref);
1580
- $ref = base.$ref.slice(0, base.$ref.length - 1).concat(refChain)
1581
- } else {
1582
- $ref = base.ref.concat( key.as || key.ref); // Keep along the aliases
1583
- }
1584
- setProp(clone, '$ref', $ref);
1585
- clone.ref = clone.ref.concat(key.ref);
1586
- }
1587
-
1588
- if (!clone.as && clone.ref && clone.ref.length > 0) {
1589
- clone.as = ref[ref.length - 1] + pathDelimiter + (key.as || key.ref.join(pathDelimiter));
1590
- // TODO: can we use $inferred? Does it have other weird side-effects?
1591
- setProp(clone, '$inferredAlias', true);
1592
- }
1593
- else {
1594
- clone.as += pathDelimiter + (key.as || key.ref.join(pathDelimiter));
1595
- }
1596
-
1597
- return clone;
1598
- }
1599
-
1600
1037
  /**
1601
1038
  * Flatten technical configuration stuff
1602
1039
  *
@@ -1606,7 +1043,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1606
1043
  function flattenIndexes(art, artName) {
1607
1044
  // Flatten structs in indexes (unless explicitly asked to keep structs)
1608
1045
  const tc = art.technicalConfig;
1609
- if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
1046
+ if (art.kind === 'entity') {
1610
1047
  if (tc && tc[dialect]) {
1611
1048
  // Secondary and fulltext indexes
1612
1049
  for (const name in tc[dialect].indexes) {
@@ -1660,150 +1097,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1660
1097
  }
1661
1098
  }
1662
1099
  }
1663
-
1664
- /**
1665
- * Loop over all elements and for all unmanaged associations translate
1666
- * <assoc base>.<managed assoc>.<fk> to <assoc base>.<managed assoc>_<fk>
1667
- *
1668
- * Or in other words: Allow using the foreign keys of managed associations in on-conditions
1669
- *
1670
- * @param {CSN.Artifact} artifact Artifact to check
1671
- * @param {string} artifactName Name of the artifact
1672
- */
1673
- function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
1674
- for (const elemName in artifact.elements) {
1675
- const elem = artifact.elements[elemName];
1676
- if (doA2J) {
1677
- // The association is an unmanaged on
1678
- if (!elem.keys && elem.target && elem.on) {
1679
- forEachRef(elem.on, (ref, refOwner, path) => {
1680
- // [<assoc base>.]<managed assoc>.<field>
1681
- if (ref.length > 1) {
1682
- const { links } = inspectRef(path);
1683
- if (links) {
1684
- // eslint-disable-next-line for-direction
1685
- for (let i = links.length - 1; i >= 0; i--) {
1686
- const link = links[i];
1687
- // We found the latest managed assoc path step
1688
- if (link.art && link.art.target && link.art.keys) {
1689
- // Doesn't work when ref-target (filter condition) or similar is used
1690
- if (!ref.slice(i).some(refElement => typeof refElement !== 'string')) {
1691
- // We join the managed assoc with everything following it
1692
- const sourceElementName = ref.slice(i).join(pathDelimiter);
1693
- const source = findSource(links, i - 1) || artifact;
1694
- // allow specifying managed assoc on the source side
1695
- const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
1696
- if(fks && fks.length >= 1){
1697
- const fk = fks[0];
1698
- const managedAssocStepName = refOwner.ref[i];
1699
- const fkName = `${ managedAssocStepName }${ pathDelimiter }${ fk.as }`;
1700
- if(source && source.elements[fkName])
1701
- refOwner.ref = [ ...ref.slice(0, i), fkName ];
1702
- }
1703
- }
1704
- }
1705
- }
1706
- }
1707
- }
1708
- }, [ 'definitions', artifactName, 'elements', elemName, 'on' ]);
1709
- }
1710
- }
1711
- }
1712
-
1713
- /**
1714
- * Find out where the managed association is
1715
- *
1716
- * @param {Array} links
1717
- * @param {Number} startIndex
1718
- * @returns {Object| undefined} CSN definition of the source of the managed association
1719
- *
1720
- */
1721
- function findSource(links, startIndex) {
1722
- for (let i = startIndex; i >= 0; i--) {
1723
- const link = links[i];
1724
- // We found the latest assoc step - now check where that points to
1725
- if (link.art && link.art.target)
1726
- return csn.definitions[link.art.target];
1727
- }
1728
-
1729
- return undefined;
1730
- }
1731
- }
1732
-
1733
- /**
1734
- * Create the foreign key elements for a managed association and build the on-condition
1735
- *
1736
- * @param {CSN.Artifact} artifact
1737
- * @param {string} artifactName
1738
- * @param {Object} elem The association to process
1739
- * @param {string} elemName
1740
- * @param {WeakMap} alreadyHandled To cache which elements were already processed
1741
- * @returns {void}
1742
- */
1743
- function transformManagedAssociation(artifact, artifactName, elem, elemName, alreadyHandled) {
1744
- // No need to run over this - we already did, possibly because it was referenced in the ON-Condition
1745
- // of another association - see a few lines lower
1746
- if (alreadyHandled.has(elem))
1747
- return;
1748
- // Generate foreign key elements for managed associations, and assemble an ON-condition with them
1749
- const onCondParts = [];
1750
- let join_with_and = false;
1751
- if(elem.keys.length === 0)
1752
- elem._ignore = true;
1753
- else {
1754
- for (let i = 0; i < elem.keys.length; i++) {
1755
- const foreignKey = elem.keys[i];
1756
-
1757
- // Assemble left hand side of 'assoc.key = fkey'
1758
- const assocKeyArg = {
1759
- ref: [
1760
- elemName,
1761
- ].concat(foreignKey.ref),
1762
- };
1763
- const fkName = `${ elemName }${ pathDelimiter }${ foreignKey.as }`;
1764
- const fKeyArg = {
1765
- ref: [
1766
- fkName,
1767
- ],
1768
- };
1769
-
1770
- if (join_with_and) { // more than one FK
1771
- onCondParts.push('and');
1772
- }
1773
-
1774
- onCondParts.push(
1775
- assocKeyArg
1776
- );
1777
- onCondParts.push('=');
1778
- onCondParts.push(fKeyArg);
1779
-
1780
- if (!join_with_and)
1781
- join_with_and = true;
1782
- }
1783
- elem.on = onCondParts;
1784
- }
1785
-
1786
- // If the managed association has a 'key' property => remove it as unmanaged assocs cannot be keys
1787
- // TODO: Are there other modifiers (like 'key') that are valid for managed, but not valid for unmanaged assocs?
1788
- if (elem.key)
1789
- delete elem.key;
1790
-
1791
-
1792
- // If the managed association has a 'not null' property => remove it
1793
- if (elem.notNull)
1794
- delete elem.notNull;
1795
-
1796
-
1797
- // The association is now unmanaged, i.e. actually it should no longer have foreign keys
1798
- // at all. But the processing of backlink associations below expects to have them, so
1799
- // we don't delete them (but mark them as implicit so that toCdl does not render them)
1800
- /* Skip for now - forHana adds this to elements, but it is not part of the resulting CSN
1801
- forHanaNew -> Somehow ends up in the CSN?!
1802
- elem.implicitForeignKeys = true;
1803
- */
1804
- // Remember that we already processed this
1805
- alreadyHandled.set(elem, true);
1806
- }
1807
1100
  }
1808
1101
 
1809
1102