@sap/cds-compiler 2.10.4 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/bin/cdsc.js +42 -25
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +4 -0
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +9 -23
  7. package/lib/api/options.js +12 -4
  8. package/lib/api/validate.js +23 -2
  9. package/lib/backends.js +9 -8
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/message-registry.js +10 -2
  12. package/lib/base/messages.js +23 -9
  13. package/lib/base/model.js +5 -4
  14. package/lib/base/optionProcessorHelper.js +56 -22
  15. package/lib/checks/selectItems.js +4 -0
  16. package/lib/checks/unknownMagic.js +6 -3
  17. package/lib/compiler/assert-consistency.js +7 -0
  18. package/lib/compiler/base.js +65 -0
  19. package/lib/compiler/builtins.js +28 -1
  20. package/lib/compiler/checks.js +2 -1
  21. package/lib/compiler/definer.js +58 -91
  22. package/lib/compiler/index.js +16 -4
  23. package/lib/compiler/propagator.js +5 -2
  24. package/lib/compiler/resolver.js +93 -34
  25. package/lib/compiler/shared.js +29 -202
  26. package/lib/compiler/utils.js +173 -0
  27. package/lib/edm/annotations/genericTranslation.js +1 -1
  28. package/lib/edm/csn2edm.js +3 -2
  29. package/lib/edm/edmPreprocessor.js +31 -36
  30. package/lib/edm/edmUtils.js +3 -3
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +17 -1
  33. package/lib/gen/language.tokens +79 -73
  34. package/lib/gen/languageLexer.interp +19 -1
  35. package/lib/gen/languageLexer.js +779 -731
  36. package/lib/gen/languageLexer.tokens +71 -65
  37. package/lib/gen/languageParser.js +4668 -4072
  38. package/lib/json/from-csn.js +10 -10
  39. package/lib/json/to-csn.js +169 -34
  40. package/lib/language/antlrParser.js +11 -0
  41. package/lib/language/genericAntlrParser.js +72 -14
  42. package/lib/language/language.g4 +73 -0
  43. package/lib/main.d.ts +136 -17
  44. package/lib/main.js +3 -1
  45. package/lib/model/api.js +2 -2
  46. package/lib/model/csnRefs.js +108 -31
  47. package/lib/model/csnUtils.js +63 -29
  48. package/lib/model/enrichCsn.js +36 -9
  49. package/lib/model/revealInternalProperties.js +20 -4
  50. package/lib/modelCompare/compare.js +2 -1
  51. package/lib/optionProcessor.js +29 -18
  52. package/lib/render/DuplicateChecker.js +1 -1
  53. package/lib/render/toCdl.js +9 -3
  54. package/lib/render/toHdbcds.js +16 -36
  55. package/lib/render/toSql.js +23 -5
  56. package/lib/transform/db/constraints.js +278 -119
  57. package/lib/transform/db/draft.js +3 -2
  58. package/lib/transform/db/expansion.js +6 -4
  59. package/lib/transform/db/flattening.js +17 -1
  60. package/lib/transform/db/transformExists.js +61 -2
  61. package/lib/transform/db/views.js +438 -0
  62. package/lib/transform/forHanaNew.js +56 -435
  63. package/lib/transform/forOdataNew.js +9 -2
  64. package/lib/transform/localized.js +2 -0
  65. package/lib/transform/transformUtilsNew.js +10 -0
  66. package/lib/transform/translateAssocsToJoins.js +5 -13
  67. package/lib/utils/file.js +5 -3
  68. package/lib/utils/term.js +65 -42
  69. package/lib/utils/timetrace.js +48 -26
  70. package/package.json +1 -1
@@ -15,11 +15,10 @@ const { csnRefs, pathId, implicitAs } = require('../model/csnRefs');
15
15
  const { checkCSNVersion } = require('../json/csnVersion');
16
16
  const validate = require('../checks/validator');
17
17
  const { addLocalizationViewsWithJoins, addLocalizationViews } = require('../transform/localized');
18
- const timetrace = require('../utils/timetrace');
18
+ const { timetrace } = require('../utils/timetrace');
19
19
  const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
20
20
  const { createDict } = require('../utils/objectUtils');
21
21
  const handleExists = require('./db/transformExists');
22
- const { usesMixinAssociation, getMixinAssocOfQueryIfPublished } = require('./db/helpers');
23
22
  const replaceAssociationsInGroupByOrderBy = require('./db/groupByOrderBy');
24
23
  const _forEachDefinition = require('../model/csnUtils').forEachDefinition;
25
24
  const flattening = require('./db/flattening');
@@ -27,6 +26,7 @@ const expansion = require('./db/expansion');
27
26
  const assertUnique = require('./db/assertUnique');
28
27
  const generateDrafts = require('./db/draft');
29
28
  const enrichUniversalCsn = require('./universalCsnEnricher');
29
+ const { getViewTransformer } = require('./db/views');
30
30
 
31
31
  // By default: Do not process non-entities/views
32
32
  function forEachDefinition(csn, cb) {
@@ -100,7 +100,6 @@ function forEachDefinition(csn, cb) {
100
100
  * @param {string} moduleName The calling compiler module name, e.g. `to.hdi` or `to.hdbcds`.
101
101
  */
102
102
  function transformForHanaWithCsn(inputModel, options, moduleName) {
103
- const columnClearer = [];
104
103
  // copy the model as we don't want to change the input model
105
104
  timetrace.start('HANA transformation');
106
105
  /** @type {CSN.Model} */
@@ -115,7 +114,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
115
114
  let error, warning, info; // message functions
116
115
  /** @type {() => void} */
117
116
  let throwWithError;
118
- let artifactRef, inspectRef, queryOrMain, effectiveType, // csnRefs
117
+ let artifactRef, inspectRef, effectiveType, // csnRefs
119
118
  addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType, getFinalBaseType, // transformUtils
120
119
  get$combined; // csnUtils
121
120
 
@@ -331,7 +330,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
331
330
  // because otherwise we would produce wrong ON-conditions for the keys involved. Sigh ...
332
331
  forEachDefinition(csn, transformSelfInBacklinks);
333
332
 
334
- if(isBetaEnabled(options, 'foreignKeyConstraints') && options.forHana){
333
+ if(options.forHana){
335
334
  /**
336
335
  * Referential Constraints are only supported for sql-dialect "hana" and "sqlite".
337
336
  * For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
@@ -343,7 +342,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
343
342
  if(validOptionsForConstraint())
344
343
  createReferentialConstraints(csn, options);
345
344
  }
346
-
345
+ // no constraints for drafts
347
346
  generateDrafts(csn, options, pathDelimiter, { info, warning, error });
348
347
 
349
348
  // Set the final constraint paths and produce hana tc indexes if required
@@ -356,6 +355,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
356
355
  // Apply view-specific transformations
357
356
  // (160) Projections now finally become views
358
357
  // Replace managed association in group/order by with foreign keys
358
+ const transformEntityOrViewPass2 = getViewTransformer(csn, options, {error, info}, transformCommon);
359
359
  forEachDefinition(csn, transformViews);
360
360
 
361
361
  // Recursively apply transformCommon and attach @cds.persistence.name
@@ -384,8 +384,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
384
384
  checkConstraintIdentifiers,
385
385
  /* (250) Remove all namespaces from definitions */
386
386
  removeNamespaces,
387
- /* (190 b) Replace enum types by their final base type */
388
- replaceEnumsByBaseTypes,
389
387
  /* Check Type Parameters (precision, scale, length ...) */
390
388
  checkTypeParameters,
391
389
  /* Filter out aspects/types/abstract entities containing managed compositions of anonymous aspects */
@@ -403,6 +401,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
403
401
  }
404
402
 
405
403
  const killers = {
404
+ // Used to ignore actions etc from processing and remove associations/elements
406
405
  '_ignore': function (parent, a, b, path){
407
406
  if(path.length > 2) {
408
407
  const tail = path[path.length-1];
@@ -413,25 +412,20 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
413
412
  delete parent._ignore;
414
413
  }
415
414
  },
416
- '_art': killProp,
417
- '_effectiveType': killProp,
415
+ // Still used in flattenStructuredElements - in db/flattening.js
418
416
  '_flatElementNameWithDots': killProp,
419
- '_sources': killProp,
417
+ // Set when setting default string/binary length - used in copyTypeProperties and fixBorkedElementsOfLocalized
418
+ // to not copy the .length property if it was only set via default
420
419
  '$default': killProp,
421
- '$draftRoot': killProp,
422
- '$env': killProp,
423
- '$fksgenerated': killProp,
424
- '$lateFlattening': killProp,
425
- '$path': killProp,
420
+ // Set when we turn UUID into String, checked during generateDraftForHana
426
421
  '$renamed': killProp,
427
- '$key': killProp,
428
- '$generatedExists': killProp
422
+ // Set when we remove .key from temporal things, used in localized.js
423
+ '$key': killProp
429
424
  }
430
425
 
431
426
  applyTransformations(csn, killers, [], false);
432
427
 
433
428
  redoProjections.forEach(fn => fn());
434
- columnClearer.forEach(fn => fn());
435
429
 
436
430
  return csn;
437
431
 
@@ -496,14 +490,14 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
496
490
 
497
491
  function bindCsnReference(){
498
492
  ({ error, warning, info, throwWithError } = makeMessageFunction(csn, options, moduleName));
499
- ({ artifactRef, inspectRef, queryOrMain, effectiveType } = csnRefs(csn));
493
+ ({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
500
494
  ({ getFinalBaseType, get$combined } = getUtils(csn));
501
495
  ({ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter));
502
496
  }
503
497
 
504
498
  function bindCsnReferenceOnly(){
505
499
  // invalidate caches for CSN ref API
506
- ({ artifactRef, inspectRef, queryOrMain, effectiveType } = csnRefs(csn));
500
+ ({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
507
501
  }
508
502
 
509
503
  function handleMixinOnConditions(artifact, artifactName) {
@@ -619,20 +613,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
619
613
  }
620
614
  }
621
615
 
622
- /**
623
- * @param {CSN.Artifact} artifact
624
- * @param {string} artifactName
625
- */
626
- function replaceEnumsByBaseTypes(artifact, artifactName) {
627
- replaceEnumByBaseType(artifact);
628
- forEachMemberRecursively(artifact, (member) => {
629
- replaceEnumByBaseType(member);
630
- if (options.forHana.alwaysResolveDerivedTypes || options.forHana.names === 'plain') {
631
- toFinalBaseType(member);
632
- addDefaultTypeFacets(member);
633
- }
634
- }, [ 'definitions', artifactName ]);
635
- }
636
616
 
637
617
  /**
638
618
  * @param {CSN.Artifact} artifact
@@ -710,6 +690,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
710
690
  function handleAssociations(artifact, artifactName) {
711
691
  // Do things specific for entities and views (pass 1)
712
692
  if (artifact.kind === 'entity' || artifact.kind === 'view') {
693
+ const alreadyHandled = new WeakMap();
713
694
  forAllElements(artifact, artifactName, (parent, elements) => {
714
695
  for (const elemName in elements) {
715
696
  const elem = elements[elemName];
@@ -717,7 +698,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
717
698
  // (unless explicitly asked to keep assocs unchanged)
718
699
  if (doA2J) {
719
700
  if (isManagedAssociationElement(elem))
720
- transformManagedAssociation(parent, artifactName, elem, elemName);
701
+ transformManagedAssociation(parent, artifactName, elem, elemName, alreadyHandled);
721
702
  }
722
703
  }
723
704
  })
@@ -843,30 +824,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
843
824
  if (art.technicalConfig)
844
825
  newCsn.definitions[artName].technicalConfig = art.technicalConfig;
845
826
 
846
- const newArt = newCsn.definitions[artName];
847
-
848
- // No need to loop/check artifacts that won't reach the DB anyways
849
- if (art.query && newArt && newArt.query && isPersistedOnDatabase(newArt)) {
850
- // Loop through the newCSN and add possible new _ignore mixin to the kill list
851
- forAllQueries(newArt.query, (q, p) => {
852
- if (q.SELECT && q.SELECT.mixin) {
853
- for(let mixinName of Object.keys(q.SELECT.mixin)) {
854
- const mixinElement = q.SELECT.mixin[mixinName];
855
- if (mixinElement._ignore && options.toSql) {
856
- columnClearer.push(() => {
857
- const query = walkCsnPath(csn, p);
858
- for(let i = query.columns.length-1; i > -1; i--){
859
- const col = query.columns[i];
860
- if(col && col.ref && col.ref[0] === mixinName){
861
- query.columns.splice(i, 1);
862
- }
863
- }
864
- });
865
- }
866
- }
867
- }
868
- }, ['definitions', artName, 'query']);
869
- }
870
827
  });
871
828
  csn = newCsn;
872
829
  }
@@ -1052,10 +1009,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1052
1009
  if (isPersistedOnDatabase(artifact)) {
1053
1010
  // TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
1054
1011
  if (artifact.query) {
1055
- if (artifact.query.SELECT && artifact.query.SELECT.mixin)
1012
+ // If we do A2J, we don't need to check the mixin. Either it is used -> a join
1013
+ // or published -> handled via elements/members. Unused mixins are removed anyway.
1014
+ if (!doA2J && artifact.query.SELECT && artifact.query.SELECT.mixin)
1056
1015
  forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
1057
1016
 
1058
- else if (artifact.query.SET && artifact.query.SET.mixin)
1017
+ else if (!doA2J && artifact.query.SET && artifact.query.SET.mixin)
1059
1018
  forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
1060
1019
  }
1061
1020
  forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
@@ -1096,6 +1055,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1096
1055
 
1097
1056
  // (190 a) Replace enum symbols by their value (if found)
1098
1057
  replaceEnumSymbolsByValues(obj, path);
1058
+
1059
+ if (obj.enum)
1060
+ delete obj.enum;
1099
1061
  }
1100
1062
 
1101
1063
  // Change the names of those builtin types that have different names in HANA.
@@ -1129,309 +1091,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1129
1091
  // }
1130
1092
  // }
1131
1093
 
1132
- /**
1133
- * Strip of leading $self of the ref
1134
- * @param {object} col A column
1135
- *
1136
- * @returns {object}
1137
- */
1138
- function stripLeadingSelf(col) {
1139
- if (col.ref && col.ref.length > 1 && col.ref[0] === '$self')
1140
- col.ref = col.ref.slice(1);
1141
-
1142
-
1143
- return col;
1144
- }
1145
-
1146
- function isUnion(path){
1147
- const subquery = path[path.length-1];
1148
- const queryIndex = path[path.length-2]
1149
- const args = path[path.length-3];
1150
- const unionOperator = path[path.length-4];
1151
- return path.length > 3 && (subquery === 'SET' || subquery === 'SELECT') && typeof queryIndex === 'number' && queryIndex >= 0 && args === 'args' && unionOperator === 'SET';
1152
- }
1153
-
1154
- function transformEntityOrViewPass2(query, artifact, artName, path) {
1155
- const { elements } = queryOrMain(query, artifact);
1156
- let hasNonAssocElements = false;
1157
- const isSelect = query && query.SELECT;
1158
- let isProjection = !!artifact.projection;
1159
- const columnMap = Object.create(null);
1160
- let isSelectStar = false;
1161
- if (isSelect) {
1162
- if (!query.SELECT.columns) {
1163
- isProjection = true;
1164
- }
1165
- else {
1166
- query.SELECT.columns.forEach((col) => {
1167
- if (col === '*') {
1168
- isSelectStar = true;
1169
- }
1170
- else if (col.as) {
1171
- if (!columnMap[col.as])
1172
- columnMap[col.as] = col;
1173
- }
1174
- else if (col.ref) {
1175
- if (!columnMap[col.ref[col.ref.length - 1]])
1176
- columnMap[col.ref[col.ref.length - 1]] = col;
1177
- }
1178
- else if (col.func) {
1179
- columnMap[col.func] = col;
1180
- }
1181
- else if (!columnMap[col]) {
1182
- columnMap[col] = col;
1183
- }
1184
- });
1185
- }
1186
- }
1187
- if (query && options.transformation === 'hdbcds') {
1188
- // check all queries/subqueries for mixin publishing inside of unions -> forbidden in hdbcds
1189
- if (query.SELECT && query.SELECT.mixin && path.indexOf('SET') !== -1) {
1190
- for (const elementName in elements) {
1191
- const element = elements[elementName];
1192
- if (element.target) {
1193
- let colLocation;
1194
- for (let i = 0; i < query.SELECT.columns.length; i++) {
1195
- const col = query.SELECT.columns[i];
1196
- if (col.ref && col.ref.length === 1) {
1197
- if (!colLocation && col.ref[0] === elementName)
1198
- colLocation = i;
1199
-
1200
-
1201
- if (col.as === elementName)
1202
- colLocation = i;
1203
- }
1204
- }
1205
- if (colLocation) {
1206
- const matchingCol = query.SELECT.columns[colLocation];
1207
- const possibleMixinName = matchingCol.ref[0];
1208
- const isMixin = query.SELECT.mixin[possibleMixinName] !== undefined;
1209
- if (element.target && isMixin)
1210
- error(null, path.concat([ 'columns', colLocation ]),
1211
- `Element "${ elementName }" is a mixin association${ possibleMixinName !== elementName ? ` ("${ possibleMixinName }")` : '' } and can't be published in a UNION`);
1212
- }
1213
- }
1214
- }
1215
- }
1216
- }
1217
-
1218
- // Second walk through the entity elements: Deal with associations (might also result in new elements)
1219
-
1220
- // Will be initialized JIT inside the elements-loop
1221
- let $combined;
1222
-
1223
- for (const elemName in elements) {
1224
- const elem = elements[elemName];
1225
- if (isSelect) {
1226
- if (!columnMap[elemName]) {
1227
- // Prepend an alias if present
1228
- let alias = (isProjection || isSelectStar) &&
1229
- (query.SELECT.from.as || (query.SELECT.from.ref && implicitAs(query.SELECT.from.ref)));
1230
- // In case of * and no explicit alias
1231
- // find the source of the col by looking at $combined and prepend it
1232
- if (isSelectStar && !alias && !isProjection) {
1233
- if (!$combined)
1234
- $combined = get$combined(query);
1235
-
1236
-
1237
- const matchingCombined = $combined[elemName];
1238
- // Internal errors - this should never happen!
1239
- if (matchingCombined.length > 1) { // should already be caught by compiler
1240
- throw new Error(`Ambiguous name - can't be resolved: ${ elemName }. Found in: ${ matchingCombined.map(o => o.parent) }`);
1241
- }
1242
- else if (matchingCombined.length === 0) { // no clue how this could happen? Invalid CSN?
1243
- throw new Error(`No matching entry found in UNION of all elements for: ${ elemName }`);
1244
- }
1245
- alias = matchingCombined[0].parent;
1246
- }
1247
- if (alias)
1248
- columnMap[elemName] = { ref: [ alias, elemName ] };
1249
- else
1250
- columnMap[elemName] = { ref: [ elemName ] };
1251
- }
1252
-
1253
- // For associations - make sure that the foreign keys have the same "style"
1254
- // If A.assoc => A.assoc_id, else if assoc => assoc_id or assoc as Assoc => Assoc_id
1255
- if (elem.keys && doA2J) {
1256
- const assoc_col = columnMap[elemName];
1257
- if (assoc_col && assoc_col.ref) {
1258
- elem.keys.forEach((key) => {
1259
- const ref = cloneCsn(assoc_col.ref, options);
1260
- ref[ref.length - 1] = [ ref[ref.length - 1] ].concat(key.as || key.ref).join(pathDelimiter);
1261
- const result = {
1262
- ref,
1263
- };
1264
- if (assoc_col.as)
1265
- result.as = key.$generatedFieldName;
1266
-
1267
-
1268
- if (assoc_col.key)
1269
- result.key = true;
1270
-
1271
-
1272
- const colName = result.as || ref[ref.length - 1];
1273
- columnMap[colName] = result;
1274
- });
1275
- }
1276
- }
1277
- // Add flattened structured things preserving aliases and refs with/without table alias
1278
- // If we add them when we get to them in "elements", we cannot know what table alias was used...
1279
- if (isStructured(elem) && doA2J) {
1280
- const col = columnMap[elemName];
1281
- const originalName = col.ref[col.ref.length - 1];
1282
- const flatElements = flattenStructuredElement(elem, originalName, [], path);
1283
- const aliasedFlatElements = originalName !== elemName ? Object.keys(flattenStructuredElement(elem, elemName, [], path)) : [];
1284
-
1285
- Object.keys(flatElements).forEach((flatElemName, index ) => {
1286
- const clone = cloneCsn(col, options);
1287
- // For the ref, use the "original"
1288
- if (clone.ref)
1289
- clone.ref[clone.ref.length - 1] = flatElemName;
1290
-
1291
- // If the column was aliased, use the alias-prefix for the flattened element
1292
- if (originalName !== elemName)
1293
- clone.as = aliasedFlatElements[index];
1294
-
1295
- // Insert into map, giving precedence to the alias
1296
- columnMap[clone.as || flatElemName] = clone;
1297
- });
1298
- }
1299
- }
1300
- // Views must have at least one element that is not an unmanaged assoc
1301
- if (!elem.on && !elem._ignore)
1302
- hasNonAssocElements = true;
1303
-
1304
- // (180 b) Create MIXINs for association elements in projections or views (those that are not mixins by themselves)
1305
- // CDXCORE-585: Allow mixin associations to be used and published in parallel
1306
- if (query !== undefined && elem.target) {
1307
- if(isUnion(path) && options.transformation === 'hdbcds'){
1308
- if(isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J){
1309
- if(elem.keys) {
1310
- info(null, path, `Managed association "${elemName}", published in a UNION, will be ignored`)
1311
- } else {
1312
- info(null, path, `Association "${elemName}", published in a UNION, will be ignored`)
1313
- }
1314
- elem._ignore = true;
1315
- }
1316
- else {
1317
- error(null, path, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`)
1318
- }
1319
- } else if(path.length > 4 && options.transformation === 'hdbcds'){ // path.length > 4 -> is a subquery
1320
- error(null, path, { name: elemName },
1321
- 'Association $(NAME) can\'t be published in a subquery')
1322
- } else {
1323
- /* Old implementation:
1324
- const isNotMixinByItself = !(elem.value && elem.value.path && elem.value.path.length == 1 && art.query && art.query.mixin && art.query.mixin[elem.value.path[0].id]);
1325
- */
1326
- const isNotMixinByItself = checkIsNotMixinByItself(query, columnMap, elem, elemName);
1327
- const {mixinElement, mixinName } = getMixinAssocOfQueryIfPublished(query, elem, elemName);
1328
- if (isNotMixinByItself || mixinElement !== undefined) {
1329
- // If the mixin is only published and not used, only display the __ clone. Ignore the "original".
1330
- if (mixinElement !== undefined && !usesMixinAssociation(query, elem, elemName)){
1331
- mixinElement._ignore = true;
1332
- }
1333
-
1334
- delete elem._typeIsExplicit;
1335
- // Create an unused alias name for the MIXIN - use 3 _ to avoid collision with usings
1336
- let mixinElemName = `___${ mixinName || elemName }`;
1337
- while (elements[mixinElemName])
1338
- mixinElemName = `_${ mixinElemName }`;
1339
-
1340
- // Copy the association element to the MIXIN clause under its alias name
1341
- // (shallow copy is sufficient, just fix name and value)
1342
- const mixinElem = Object.assign({}, elem);
1343
- // Perform common transformations on the newly generated MIXIN element (won't be reached otherwise)
1344
- transformCommon(mixinElem, mixinElemName);
1345
- // TODO: Can we rely on query.SELECT.mixin to check for mixins?
1346
- // Yes, we can - only SELECT can have mixin. But:
1347
- // - UNION
1348
- // - JOINS
1349
- // - Subqueries
1350
- // Are currently (and in the old transformer) not handled!
1351
- if (query.SELECT && !query.SELECT.mixin)
1352
- query.SELECT.mixin = Object.create(null);
1353
-
1354
- // Let the original association element use the newly generated MIXIN name as value and alias
1355
- delete elem.viaAll;
1356
-
1357
- // Clone 'on'-condition, pre-pending '$projection' to paths where appropriate,
1358
- // and fixing the association alias just created
1359
-
1360
- if (mixinElem.on) {
1361
- mixinElem.on = cloneWithTransformations(mixinElem.on, {
1362
- ref: (ref) => {
1363
- // Clone the path, without any transformations
1364
- const clonedPath = cloneWithTransformations(ref, {});
1365
- // Prepend '$projection' to the path, unless the first path step is the (mixin) element itself or starts with '$')
1366
- if (clonedPath[0] == elemName) {
1367
- clonedPath[0] = mixinElemName;
1368
- }
1369
- else if (!(clonedPath[0] && clonedPath[0].startsWith('$'))) {
1370
- const projectionId = '$projection';
1371
- clonedPath.unshift(projectionId);
1372
- }
1373
- return clonedPath;
1374
- },
1375
- func: (func) => {
1376
- // Unfortunately, function names are disguised as paths, so we would prepend a '$projection'
1377
- // above (no way to distinguish that in the callback for 'path' above). We can only pluck it
1378
- // off again here ... sigh
1379
- if (func.ref && func.ref[0] && func.ref[0] === '$projection')
1380
- func.ref = func.ref.slice(1);
1381
-
1382
- return func;
1383
- },
1384
- });
1385
- }
1386
-
1387
- if (!mixinElem._ignore)
1388
- columnMap[elemName] = { ref: [ mixinElemName ], as: elemName };
1389
-
1390
- if (query.SELECT) {
1391
- query.SELECT.mixin[mixinElemName] = mixinElem;
1392
- }
1393
- }
1394
- }
1395
- }
1396
- }
1397
-
1398
- if (query && !hasNonAssocElements) {
1399
- // Complain if there are no elements other than unmanaged associations
1400
- // Allow with plain
1401
- error(null, [ 'definitions', artName ], { $reviewed: true } ,
1402
- 'Expecting view or projection to have at least one element that is not an unmanaged association');
1403
- }
1404
-
1405
- if (isSelect) {
1406
- // Workaround for bugzilla 176495 FIXME FIXME FIXME: is this really still needed?
1407
- // If a select item of a cdx view contains an expression, the result type cannot be computed
1408
- // but must be explicitly specified. This is important for the OData channel, which doesn't
1409
- // work if the type is missing (for HANA channel an explicit type is not required, as HANA CDS
1410
- // can compute the result type).
1411
- // Due to bug in HANA CDS, providing explicit type 'LargeString' or 'LargeBinary' causes a
1412
- // diserver crash. Until a fix in HANA CDS is available, we allow to suppress the explicit
1413
- // type in the HANA channel via an annotation.
1414
- Object.keys(columnMap).forEach((value) => {
1415
- const elem = elements[value];
1416
- if (elem && elem['@cds.workaround.noExplicitTypeForHANA'])
1417
- delete columnMap[value].cast;
1418
- });
1419
-
1420
- query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem]._ignore).map(key => stripLeadingSelf(columnMap[key]));
1421
- // If following an association, explicitly set the implicit alias
1422
- // due to an issue with HANA
1423
- for (let i = 0; i < query.SELECT.columns.length; i++) {
1424
- const col = query.SELECT.columns[i];
1425
- if (!col.as && col.ref && col.ref.length > 1) {
1426
- const { links } = inspectRef(path.concat([ 'columns', i ]));
1427
- if (links && links.slice(0, -1).some(({ art }) => isAssocOrComposition(art && art.type || '')))
1428
- col.as = col.ref[col.ref.length - 1];
1429
- }
1430
- }
1431
- delete query.SELECT.excluding; // just to make the output of the new transformer the same as the old
1432
- }
1433
- }
1434
-
1435
1094
 
1436
1095
  // If 'elem' has a default that is an enum constant, replace that by its value. Complain
1437
1096
  // if not found or not an enum type,
@@ -1448,7 +1107,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1448
1107
  // Looks like it is always run?! But message says HANA CDS?!
1449
1108
  error(null, path, {
1450
1109
  $reviewed: true,
1451
- name: `#${elem.default['#']}`
1110
+ name: `#${ elem.default['#'] }`
1452
1111
  },
1453
1112
  'Expecting enum literal $(NAME) to be used with an enum type');
1454
1113
  }
@@ -1458,7 +1117,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1458
1117
  if (!enumSymbol) {
1459
1118
  error(null, path, {
1460
1119
  $reviewed: true,
1461
- name: `#${elem.default['#']}`
1120
+ name: `#${ elem.default['#'] }`
1462
1121
  }, 'Enum literal $(NAME) is undefined in enumeration type');
1463
1122
  }
1464
1123
  else if (enumSymbol.val !== undefined) { // `val` may be `null`
@@ -1475,30 +1134,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1475
1134
  }
1476
1135
  }
1477
1136
 
1478
- // If 'node' has an enum type, change node's type to be the enum's base type
1479
- // and strip off the 'enum' property.
1480
- function replaceEnumByBaseType(node) {
1481
- if (node.items)
1482
- replaceEnumByBaseType(node.items);
1483
-
1484
- // (190 b) Replace enum types by their final base type (must happen after 190 a)
1485
- /* Old implementation:
1486
- if (node && node._finalType && (node.enum || node._finalType.enum)) {
1487
- node.type = node._finalType.type
1488
- // node.type = node._finalType.type._artifact._finalType.type;
1489
- if (node._finalType.length) {
1490
- node.length = node._finalType.length;
1491
- }
1492
- setProp(node, '_finalType', node.type._artifact);
1493
- delete node.enum;
1494
- }
1495
- */
1496
- if (node && node.enum) {
1497
- // toFinalBaseType(node);
1498
- // addDefaultTypeFacets(node);
1499
- delete node.enum;
1500
- }
1501
- }
1502
1137
 
1503
1138
  // If the association element 'elem' of 'art' is a backlink association, massage its ON-condition
1504
1139
  // (in place) so that it
@@ -1538,14 +1173,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1538
1173
  if (multipleExprs)
1539
1174
  result.push(')');
1540
1175
  i += 3;
1541
- // remember name of backlink, important for foreign key constraints
1542
- if(elem.$selfOnCondition)
1543
- elem.$selfOnCondition.backlinkName += `_${ backlinkName }`;
1544
- else {
1545
- setProp(elem, '$selfOnCondition', {
1546
- backlinkName
1547
- }) // important for the foreign key constraints
1548
- }
1176
+ attachBacklinkInformation(backlinkName);
1549
1177
  }
1550
1178
  else if (isDollarSelfOrProjectionOperand(xprArgs[i + 2]) && isAssociationOperand(xprArgs[i], path.concat([ i ]))) {
1551
1179
  const assoc = inspectRef(path.concat([ i ])).art;
@@ -1556,14 +1184,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1556
1184
  if (multipleExprs)
1557
1185
  result.push(')');
1558
1186
  i += 3;
1559
- // remember name of backlink, important for foreign key constraints
1560
- if(elem.$selfOnCondition)
1561
- elem.$selfOnCondition.backlinkName += `_${ backlinkName }`;
1562
- else {
1563
- setProp(elem, '$selfOnCondition', {
1564
- backlinkName
1565
- }) // important for the foreign key constraints
1566
- }
1187
+ attachBacklinkInformation(backlinkName);
1567
1188
  }
1568
1189
  // Otherwise take one (!) token unchanged
1569
1190
  else {
@@ -1583,6 +1204,24 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1583
1204
  }
1584
1205
  }
1585
1206
  return result;
1207
+
1208
+ /**
1209
+ * The knowledge whether an association was an `<up_>` association in a
1210
+ * `$self = <comp>.<up_>` comparison, is important for the foreign key constraints.
1211
+ * By the time we generate them, such on-conditions are already transformed
1212
+ * --> no more `$self` in the on-conditions, that is why we need to remember it here.
1213
+ *
1214
+ * @param {string} backlinkName name of `<up_>` in a `$self = <comp>.<up_>` comparison
1215
+ */
1216
+ function attachBacklinkInformation(backlinkName) {
1217
+ if (elem.$selfOnCondition)
1218
+ elem.$selfOnCondition.up_.push(backlinkName);
1219
+ else {
1220
+ setProp(elem, '$selfOnCondition', {
1221
+ up_: [backlinkName]
1222
+ });
1223
+ }
1224
+ }
1586
1225
  }
1587
1226
 
1588
1227
  elem.on = processExpressionArgs(elem.on, pathToOn);
@@ -1696,26 +1335,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1696
1335
  }
1697
1336
  }
1698
1337
 
1699
- /**
1700
- * @todo: XSN - Implementation most likely too naive, can we rely on query.SELECT.mixin?
1701
- *
1702
- * @param {CSN.Query} query
1703
- * @param {object} columnMap
1704
- * @param {CSN.Artifact} columnMap
1705
- * @param {string} elementName
1706
- */
1707
- function checkIsNotMixinByItself(query, columnMap, element, elementName) {
1708
- if (query && query.SELECT && query.SELECT.mixin) {
1709
- const col = columnMap[elementName];
1710
-
1711
- const realName = col.ref[col.ref.length - 1];
1712
- // If the element is not part of the mixin => True
1713
- return query.SELECT.mixin[realName] == undefined;
1714
- }
1715
- // the artifact does not define any mixins, the element cannot be a mixin
1716
- return true;
1717
- }
1718
-
1719
1338
  /**
1720
1339
  * @param {CSN.Artifact} artifact
1721
1340
  * @param {string} artifactName
@@ -2050,11 +1669,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2050
1669
  const source = findSource(links, i - 1) || artifact;
2051
1670
  // allow specifying managed assoc on the source side
2052
1671
  const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
2053
-
2054
1672
  if(fks && fks.length >= 1){
2055
1673
  const fk = fks[0];
2056
- if(source && source.elements[fk.$generatedFieldName])
2057
- refOwner.ref = [ ...ref.slice(0, i), fk.$generatedFieldName ];
1674
+ const managedAssocStepName = refOwner.ref[i];
1675
+ const fkName = `${ managedAssocStepName }${ pathDelimiter }${ fk.as }`;
1676
+ if(source && source.elements[fkName])
1677
+ refOwner.ref = [ ...ref.slice(0, i), fkName ];
2058
1678
  }
2059
1679
  }
2060
1680
  }
@@ -2093,12 +1713,13 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2093
1713
  * @param {string} artifactName
2094
1714
  * @param {Object} elem The association to process
2095
1715
  * @param {string} elemName
1716
+ * @param {WeakMap} alreadyHandled To cache which elements were already processed
2096
1717
  * @returns {void}
2097
1718
  */
2098
- function transformManagedAssociation(artifact, artifactName, elem, elemName) {
1719
+ function transformManagedAssociation(artifact, artifactName, elem, elemName, alreadyHandled) {
2099
1720
  // No need to run over this - we already did, possibly because it was referenced in the ON-Condition
2100
1721
  // of another association - see a few lines lower
2101
- if (elem.$fksgenerated)
1722
+ if (alreadyHandled.has(elem))
2102
1723
  return;
2103
1724
  // Generate foreign key elements for managed associations, and assemble an ON-condition with them
2104
1725
  const onCondParts = [];
@@ -2115,10 +1736,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2115
1736
  elemName,
2116
1737
  ].concat(foreignKey.ref),
2117
1738
  };
2118
-
1739
+ const fkName = `${ elemName }${ pathDelimiter }${ foreignKey.as }`;
2119
1740
  const fKeyArg = {
2120
1741
  ref: [
2121
- foreignKey.$generatedFieldName,
1742
+ fkName,
2122
1743
  ],
2123
1744
  };
2124
1745
 
@@ -2157,7 +1778,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
2157
1778
  elem.implicitForeignKeys = true;
2158
1779
  */
2159
1780
  // Remember that we already processed this
2160
- setProp(elem, '$fksgenerated', true);
1781
+ alreadyHandled.set(elem, true);
2161
1782
  }
2162
1783
  }
2163
1784