@sap/cds-compiler 2.11.2 → 2.13.6

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