@sap/cds-compiler 2.5.0 → 2.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/CHANGELOG.md +191 -9
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +33 -3
  4. package/lib/api/main.js +29 -101
  5. package/lib/api/options.js +15 -11
  6. package/lib/api/validate.js +12 -8
  7. package/lib/backends.js +0 -81
  8. package/lib/base/keywords.js +32 -2
  9. package/lib/base/message-registry.js +63 -9
  10. package/lib/base/messages.js +63 -21
  11. package/lib/base/model.js +2 -3
  12. package/lib/checks/defaultValues.js +27 -2
  13. package/lib/checks/elements.js +1 -6
  14. package/lib/checks/foreignKeys.js +0 -6
  15. package/lib/checks/managedWithoutKeys.js +17 -0
  16. package/lib/checks/nonexpandableStructured.js +38 -0
  17. package/lib/checks/onConditions.js +9 -45
  18. package/lib/checks/queryNoDbArtifacts.js +25 -7
  19. package/lib/checks/selectItems.js +25 -2
  20. package/lib/checks/types.js +26 -2
  21. package/lib/checks/unknownMagic.js +38 -0
  22. package/lib/checks/utils.js +61 -0
  23. package/lib/checks/validator.js +60 -7
  24. package/lib/compiler/assert-consistency.js +16 -7
  25. package/lib/compiler/builtins.js +2 -0
  26. package/lib/compiler/checks.js +6 -4
  27. package/lib/compiler/definer.js +99 -42
  28. package/lib/compiler/index.js +73 -27
  29. package/lib/compiler/resolver.js +288 -157
  30. package/lib/compiler/shared.js +31 -11
  31. package/lib/edm/annotations/genericTranslation.js +182 -186
  32. package/lib/edm/csn2edm.js +103 -108
  33. package/lib/edm/edm.js +18 -21
  34. package/lib/edm/edmPreprocessor.js +361 -114
  35. package/lib/edm/edmUtils.js +103 -33
  36. package/lib/gen/Dictionary.json +22 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +12 -1
  39. package/lib/gen/language.tokens +57 -53
  40. package/lib/gen/languageLexer.interp +10 -1
  41. package/lib/gen/languageLexer.js +770 -744
  42. package/lib/gen/languageLexer.tokens +49 -46
  43. package/lib/gen/languageParser.js +4713 -4279
  44. package/lib/json/from-csn.js +103 -45
  45. package/lib/json/to-csn.js +296 -117
  46. package/lib/language/antlrParser.js +4 -3
  47. package/lib/language/errorStrategy.js +1 -0
  48. package/lib/language/genericAntlrParser.js +21 -12
  49. package/lib/language/language.g4 +99 -31
  50. package/lib/main.d.ts +81 -3
  51. package/lib/main.js +30 -7
  52. package/lib/model/api.js +78 -0
  53. package/lib/model/csnRefs.js +329 -142
  54. package/lib/model/csnUtils.js +235 -58
  55. package/lib/model/enrichCsn.js +18 -1
  56. package/lib/model/revealInternalProperties.js +2 -1
  57. package/lib/modelCompare/compare.js +37 -20
  58. package/lib/optionProcessor.js +9 -3
  59. package/lib/render/.eslintrc.json +4 -1
  60. package/lib/render/DuplicateChecker.js +8 -5
  61. package/lib/render/toCdl.js +112 -33
  62. package/lib/render/toHdbcds.js +134 -64
  63. package/lib/render/toSql.js +91 -38
  64. package/lib/render/utils/common.js +8 -13
  65. package/lib/render/utils/sql.js +3 -3
  66. package/lib/sql-identifier.js +6 -1
  67. package/lib/transform/db/assertUnique.js +5 -6
  68. package/lib/transform/db/constraints.js +29 -13
  69. package/lib/transform/db/draft.js +8 -6
  70. package/lib/transform/db/expansion.js +582 -0
  71. package/lib/transform/db/flattening.js +325 -0
  72. package/lib/transform/db/groupByOrderBy.js +2 -2
  73. package/lib/transform/db/transformExists.js +284 -63
  74. package/lib/transform/forHanaNew.js +98 -381
  75. package/lib/transform/forOdataNew.js +21 -22
  76. package/lib/transform/localized.js +37 -10
  77. package/lib/transform/odata/attachPath.js +19 -4
  78. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  79. package/lib/transform/odata/referenceFlattener.js +60 -39
  80. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  81. package/lib/transform/odata/structuralPath.js +72 -0
  82. package/lib/transform/odata/structureFlattener.js +19 -18
  83. package/lib/transform/odata/typesExposure.js +22 -12
  84. package/lib/transform/transformUtilsNew.js +134 -78
  85. package/lib/transform/translateAssocsToJoins.js +17 -14
  86. package/lib/transform/universalCsnEnricher.js +67 -0
  87. package/lib/utils/file.js +0 -11
  88. package/lib/utils/moduleResolve.js +6 -8
  89. package/package.json +1 -1
  90. package/lib/json/walker.js +0 -26
  91. package/lib/transform/sqlite +0 -0
  92. package/lib/utils/string.js +0 -17
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { makeMessageFunction } = require('../base/messages');
4
- const { isDeprecatedEnabled } = require('../base/model');
4
+ const { isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
5
5
  const transformUtils = require('./transformUtilsNew');
6
6
  const { getUtils,
7
7
  cloneCsn,
@@ -24,8 +24,9 @@ const expandStructKeysInAssociations = require('./odata/expandStructKeysInAssoci
24
24
  const expandToFinalBaseType = require('./odata/toFinalBaseType');
25
25
  const timetrace = require('../utils/timetrace');
26
26
  const { attachPath } = require('./odata/attachPath');
27
+ const enrichUniversalCsn = require('./universalCsnEnricher');
27
28
 
28
- const { addLocalizationViews, hasLocalizedConvenienceView, isInLocalizedNamespace } = require('./localized');
29
+ const { addLocalizationViews } = require('./localized');
29
30
 
30
31
  // Transformation for ODATA. Expects a CSN 'inputModel', processes it for ODATA.
31
32
  // The result should be suitable for consumption by EDMX processors (annotations and metadata)
@@ -89,7 +90,7 @@ function transform4odataWithCsn(inputModel, options) {
89
90
  addElement, createAction, assignAction,
90
91
  extractValidFromToKeyElement,
91
92
  checkAssignment, checkMultipleAssignments,
92
- recurseElements, setAnnotation, renameAnnotation,
93
+ recurseElements, setAnnotation, resetAnnotation, renameAnnotation,
93
94
  expandStructsInExpression
94
95
  } = transformers;
95
96
 
@@ -119,16 +120,17 @@ function transform4odataWithCsn(inputModel, options) {
119
120
  // @ts-ignore
120
121
  const isExternalServiceMember = (_art, name) => externalServices.includes(getServiceName(name));
121
122
 
123
+ if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
124
+ enrichUniversalCsn(csn, options);
122
125
 
123
- addLocalizationViews(csn, options);
124
126
  const keepLocalizedViews = isDeprecatedEnabled(options, 'createLocalizedViews');
125
- forEachDefinition(csn, (artifact, artifactName) => {
126
- if (hasLocalizedConvenienceView(csn, artifactName))
127
- artifact.$localized = true;
128
- else if (!keepLocalizedViews && isInLocalizedNamespace(artifactName))
129
- delete csn.definitions[artifactName];
130
- }, { skipArtifact: isExternalServiceMember });
131
127
 
128
+ function acceptLocalizedView(_name, parent) {
129
+ csn.definitions[parent].$localized = true;
130
+ return keepLocalizedViews && !isExternalServiceMember(undefined, parent);
131
+ }
132
+
133
+ addLocalizationViews(csn, options, acceptLocalizedView);
132
134
 
133
135
  validate.forOdata(csn, {
134
136
  error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect, isExternalServiceMember
@@ -159,7 +161,7 @@ function transform4odataWithCsn(inputModel, options) {
159
161
  // and expand these structured elements. This tuple expansion allows all other
160
162
  // subsequent procession steps (especially a2j) to see plain paths in expressions.
161
163
  // If errors are detected, throwWithError() will return from further processing
162
- forEachDefinition(csn, expandStructsInExpression, { skipArtifact: isExternalServiceMember });
164
+ expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
163
165
 
164
166
  // handles reference flattening
165
167
  let referenceFlattener = new ReferenceFlattener();
@@ -227,16 +229,13 @@ function transform4odataWithCsn(inputModel, options) {
227
229
  if (options.toOdata.names && !['service', 'context', 'namespace', 'annotation', 'action', 'function'].includes(def.kind))
228
230
  def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.toOdata.names, csn);
229
231
 
230
- forEachMemberRecursively(def, (member, memberName, propertyName) => {
232
+ forEachMemberRecursively(def, (member, memberName, propertyName, path) => {
231
233
  // Annotate elements, foreign keys, parameters, etc. with their DB names if requested
232
234
  // Only these are actually required and don't annotate virtual elements in entities or types
233
235
  // as they have no DB representation (although in views)
234
236
  if (options.toOdata.names && typeof member === 'object' && !['action', 'function'].includes(member.kind) && propertyName !== 'enum' && (!member.virtual || def.query)) {
235
237
  // If we have a 'preserved dotted name' (i.e. we are a result of flattening), use that for the @cds.persistence.name annotation
236
- if (member._flatElementNameWithDots) {
237
- memberName = member._flatElementNameWithDots;
238
- }
239
- member['@cds.persistence.name'] = getElementDatabaseNameOf(memberName, options.toOdata.names);
238
+ member['@cds.persistence.name'] = getElementDatabaseNameOf(referenceFlattener.getElementNameWithDots(path) || memberName, options.toOdata.names);
240
239
  }
241
240
 
242
241
  // Mark fields with @odata.on.insert/update as @Core.Computed
@@ -331,7 +330,7 @@ function transform4odataWithCsn(inputModel, options) {
331
330
  renameAnnotation(node, name, '@UI.Importance');
332
331
  let annotation = node['@UI.Importance'];
333
332
  if (annotation !== null)
334
- node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' }
333
+ node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' };
335
334
  }
336
335
 
337
336
  // Special case: '@readonly' becomes a triplet of capability restrictions for entities,
@@ -462,11 +461,11 @@ function transform4odataWithCsn(inputModel, options) {
462
461
  }
463
462
  // Generate the annotations describing the draft actions (only draft roots can be activated/edited)
464
463
  if (artifact == rootArtifact) {
465
- artifact['@Common.DraftRoot.ActivationAction'] = 'draftActivate';
466
- artifact['@Common.DraftRoot.EditAction'] = 'draftEdit';
467
- artifact['@Common.DraftRoot.PreparationAction'] = 'draftPrepare';
464
+ resetAnnotation(artifact, '@Common.DraftRoot.ActivationAction', 'draftActivate', info, ['definitions', draftAdminDataProjectionName]);
465
+ resetAnnotation(artifact, '@Common.DraftRoot.EditAction', 'draftEdit', info, ['definitions', draftAdminDataProjectionName]);
466
+ resetAnnotation(artifact, '@Common.DraftRoot.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
468
467
  } else {
469
- artifact['@Common.DraftNode.PreparationAction'] = 'draftPrepare';
468
+ resetAnnotation(artifact, '@Common.DraftNode.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
470
469
  }
471
470
 
472
471
  artifact.elements && Object.values(artifact.elements).forEach( elem => {
@@ -534,7 +533,7 @@ function transform4odataWithCsn(inputModel, options) {
534
533
 
535
534
  // Ignore if that is our own draft root
536
535
  if (draftNode != rootArtifact) {
537
- // Barf if the draft node has @odata.draft.enabled itself
536
+ // Report error when the draft node has @odata.draft.enabled itself
538
537
  if (hasAnnotationValue(draftNode, '@odata.draft.enabled', true)) {
539
538
  error(null, ['definitions', artifactName, 'elements', elemName], 'Composition in draft-enabled entity can\'t lead to another entity with “@odata.draft.enabled”');
540
539
  }
@@ -5,12 +5,14 @@ const { setProp } = require('../base/model');
5
5
  const { hasErrors } = require('../base/messages');
6
6
  const { cloneCsnDictionary } = require('../model/csnUtils');
7
7
  const { cleanSymbols } = require('../base/cleanSymbols.js');
8
+ const { rejectManagedAssociationsAndStructuresForHdbcsNames } = require('../checks/selectItems');
8
9
  const {
9
10
  cloneCsn,
10
11
  forEachDefinition,
11
12
  forEachGeneric,
12
13
  forAllQueries,
13
14
  sortCsnDefinitionsForTests,
15
+ getUtils,
14
16
  } = require('../model/csnUtils');
15
17
 
16
18
  /**
@@ -34,6 +36,13 @@ const _isViewForEntity = Symbol('_isViewForEntity'); // $inferred = 'LOCALIZED-H
34
36
  */
35
37
  const _targetFor = Symbol('_targetFor');
36
38
 
39
+ /**
40
+ * Callback function returning `true` if the localization view should be created.
41
+ * @callback acceptLocalizedView
42
+ * @param {string} viewName localization view name
43
+ * @param {string} originalName Artifact name of the original view
44
+ */
45
+
37
46
  /**
38
47
  * Create transitive localized convenience views
39
48
  *
@@ -60,8 +69,9 @@ const _targetFor = Symbol('_targetFor');
60
69
  * @param {CSN.Options} options
61
70
  * @param {boolean} useJoins If true, rewrite the "localized" association to a
62
71
  * join in direct convenience views.
72
+ * @param {acceptLocalizedView} [acceptLocalizedView] optional callback function returning true if the localized view name and its parent name provided as parameter should be created
63
73
  */
64
- function _addLocalizationViews(csn, options, useJoins) {
74
+ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = null) {
65
75
  // Don't try to create convenience views with errors.
66
76
  if (hasErrors(options.messages))
67
77
  return csn;
@@ -69,7 +79,7 @@ function _addLocalizationViews(csn, options, useJoins) {
69
79
  if (hasExistingLocalizationViews(csn, options))
70
80
  return csn;
71
81
 
72
- const { info } = makeMessageFunction(csn, options);
82
+ const { info, error } = makeMessageFunction(csn, options);
73
83
 
74
84
  const noCoalesce = (options.localizedLanguageFallback === 'none' ||
75
85
  options.localizedWithoutCoalesce);
@@ -77,7 +87,16 @@ function _addLocalizationViews(csn, options, useJoins) {
77
87
  createDirectConvenienceViews(); // 1
78
88
  createTransitiveConvenienceViews(); // 2 + 3
79
89
 
80
- forEachDefinition(csn, definition => cleanSymbols(definition, _hasLocalizedView, _isViewForEntity, _isViewForView, _targetFor));
90
+ forEachDefinition(csn, (definition, artName, prop, path) => {
91
+ cleanSymbols(definition, _hasLocalizedView, _isViewForEntity, _isViewForView, _targetFor)
92
+ if(definition.query) {
93
+ // reject managed association and structure publishing for to-hdbcds.hdbcds
94
+ const that = { csnUtils: getUtils(csn), options, error };
95
+ rejectManagedAssociationsAndStructuresForHdbcsNames.call(that, definition, path)
96
+ }
97
+ });
98
+
99
+
81
100
 
82
101
  sortCsnDefinitionsForTests(csn, options);
83
102
  return csn;
@@ -125,10 +144,16 @@ function _addLocalizationViews(csn, options, useJoins) {
125
144
 
126
145
  art[_hasLocalizedView] = viewName;
127
146
 
147
+ if(acceptLocalizedView && !acceptLocalizedView(viewName, artName))
148
+ return;
149
+
150
+ let view;
128
151
  if (art.query || art.projection)
129
- csn.definitions[viewName] = createLocalizedViewForView(art);
152
+ view = createLocalizedViewForView(art);
130
153
  else
131
- csn.definitions[viewName] = createLocalizedViewForEntity(art, artName, textElements);
154
+ view = createLocalizedViewForEntity(art, artName, textElements);
155
+
156
+ csn.definitions[viewName] = view;
132
157
 
133
158
  copyPersistenceAnnotations(csn.definitions[viewName], art);
134
159
  }
@@ -587,9 +612,10 @@ function _addLocalizationViews(csn, options, useJoins) {
587
612
  *
588
613
  * @param {CSN.Model} csn
589
614
  * @param {CSN.Options} options
615
+ * @param [acceptLocalizedView] optional callback function returning true if the localized view name and its parent name provided as parameter should be created
590
616
  */
591
- function addLocalizationViews(csn, options) {
592
- return _addLocalizationViews(csn, options, false);
617
+ function addLocalizationViews(csn, options, acceptLocalizedView = null) {
618
+ return _addLocalizationViews(csn, options, false, acceptLocalizedView);
593
619
  }
594
620
 
595
621
  /**
@@ -599,9 +625,10 @@ function addLocalizationViews(csn, options) {
599
625
  *
600
626
  * @param {CSN.Model} csn
601
627
  * @param {CSN.Options} options
628
+ * @param [acceptLocalizedView] optional callback function returning true if the localized view name and its parent name provided as parameter should be created
602
629
  */
603
- function addLocalizationViewsWithJoins(csn, options) {
604
- return _addLocalizationViews(csn, options, true);
630
+ function addLocalizationViewsWithJoins(csn, options, acceptLocalizedView = null) {
631
+ return _addLocalizationViews(csn, options, true, acceptLocalizedView);
605
632
  }
606
633
 
607
634
  /**
@@ -659,7 +686,7 @@ function copyPersistenceAnnotations(target, source) {
659
686
  // Do NOT copy ".exists" at the moment. ".exists" is not propagated
660
687
  // and this would lead to some localization views referencing not-existing
661
688
  // "localized.XYZ" views.
662
- if (anno.startsWith('@cds.persistence.skip'))
689
+ if (anno === '@cds.persistence.skip')
663
690
  target[anno] = source[anno];
664
691
  });
665
692
  }
@@ -24,6 +24,8 @@ const structuralNodeHandlers = {
24
24
  groupBy: traverseArray,
25
25
  having: traverseArray,
26
26
  xpr: traverseArray,
27
+ expand: traverseArray,
28
+ inline: traverseArray,
27
29
  }
28
30
 
29
31
  function attachPath(csn) {
@@ -38,6 +40,7 @@ function attachPathOnPartialCSN(csnPart, pathPrefix) {
38
40
  }
39
41
 
40
42
  function traverseRef(obj, path) {
43
+ if(!obj) return;
41
44
  setPath(obj, path);
42
45
  traverseArray(obj, path);
43
46
  }
@@ -48,7 +51,7 @@ function traverseArray(obj, path) {
48
51
  }
49
52
 
50
53
  function traverseDict(obj, path) {
51
- if(typeof obj !== 'object') return;
54
+ if(!obj || typeof obj !== 'object') return;
52
55
  forAllEnumerableProperties(obj, name => {
53
56
  const ipath = path.concat(name);
54
57
  setPath(obj[name], ipath);
@@ -56,17 +59,29 @@ function traverseDict(obj, path) {
56
59
  })
57
60
  }
58
61
 
62
+ function traverseDictArray(obj, path) {
63
+ if(!obj || typeof obj !== 'object') return;
64
+ forAllEnumerableProperties(obj, name => {
65
+ const ipath = path.concat(name);
66
+ setPath(obj[name], ipath);
67
+ traverseArray(obj[name], ipath);
68
+ })
69
+ }
70
+
59
71
  function traverseTyped(obj, path) {
60
- if(!obj) return;
72
+ if(!obj || typeof obj !== 'object') return;
61
73
  forAllEnumerableProperties(obj, name => {
62
74
  if(name[0]==='@') return; // skip annotations
63
75
  const func = structuralNodeHandlers[name];
64
- if(func) func(obj[name], path.concat(name));
76
+ if(func)
77
+ func(obj[name], path.concat(name));
78
+ else if(path[path.length-2] === 'columns')
79
+ traverseDictArray(obj[name], path.concat(name)); // for columns
65
80
  })
66
81
  }
67
82
 
68
83
  function setPath(obj, path) {
69
- if(typeof obj !== 'object') return;
84
+ if(!obj || typeof obj !== 'object') return;
70
85
  if(path.length>0)
71
86
  Object.defineProperty( obj, '$path', { value: path, configurable: true, writable: true, enumerable: false } );
72
87
  }
@@ -35,7 +35,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
35
35
  let generatedForeignKeyNamesForPath = Object.create(null); // map<path,[key-name]>
36
36
 
37
37
  sortedAssociations.forEach(item => {
38
- const { definitionName, elementName, element, parent, path } = item;
38
+ const { definitionName, structuralNodeName, elementName, element, parent, path } = item;
39
39
 
40
40
  if (csnUtils.isManagedAssociationElement(element) && element.keys) {
41
41
  if (flatKeys) // tackling the ref value in assoc.keys
@@ -44,7 +44,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
44
44
  fixCardinality(element);
45
45
  }
46
46
 
47
- let arrayOfGeneratedForeignKeyNames = generateForeignKeys(definitionName, elementName, element, parent, path);
47
+ let arrayOfGeneratedForeignKeyNames = generateForeignKeys(definitionName, structuralNodeName, elementName, element, parent, path);
48
48
  generatedForeignKeyNamesForPath[item.path.join('/')] = arrayOfGeneratedForeignKeyNames;
49
49
  })
50
50
 
@@ -111,7 +111,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
111
111
  /**
112
112
  * Generates foreign keys and returns their names as an array
113
113
  */
114
- function generateForeignKeys(definitionName, assocName, assoc, parent, path) {
114
+ function generateForeignKeys(definitionName, structuralNodeName, assocName, assoc, parent, path) {
115
115
  let foreignKeyElements = Object.create(null);
116
116
 
117
117
  // First, loop over the keys array of the association and generate the FKs.
@@ -134,13 +134,14 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
134
134
  if (parent.returns)
135
135
  parent = parent.returns.items || parent.returns;
136
136
 
137
- let currElementsNames = Object.keys(parent.elements);
137
+ const dictionary = parent[structuralNodeName];
138
+ let currElementsNames = Object.keys(parent[structuralNodeName]);
138
139
  for (const [foreignKeyName, foreignKey] of Object.entries(foreignKeyElements)) {
139
140
  copyAnnotations(assoc, foreignKey, true);
140
141
  // Insert artificial element into artifact, with all cross-links
141
- if (parent.elements[foreignKeyName]) {
142
- if (!(parent.elements[foreignKeyName]['@odata.foreignKey4'] || isDeepEqual(parent.elements[foreignKeyName], foreignKey))) {
143
- const path = parent.elements[foreignKeyName].$path;
142
+ if (dictionary[foreignKeyName]) {
143
+ if (!(dictionary[foreignKeyName]['@odata.foreignKey4'] || isDeepEqual(dictionary[foreignKeyName], foreignKey))) {
144
+ const path = dictionary[foreignKeyName].$path;
144
145
  error(null, path, { name: foreignKeyName, art: assocName }, 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
145
146
  }
146
147
  }
@@ -151,8 +152,8 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
151
152
  // if (flatKeys)
152
153
  currElementsNames.splice(assocIndex + 1, 0, ...Object.keys(foreignKeyElements));
153
154
 
154
- parent.elements = currElementsNames.reduce((previous, name) => {
155
- previous[name] = parent.elements[name] || foreignKeyElements[name];
155
+ parent[structuralNodeName] = currElementsNames.reduce((previous, name) => {
156
+ previous[name] = dictionary[name] || foreignKeyElements[name];
156
157
  return previous;
157
158
  }, Object.create(null));
158
159
 
@@ -180,7 +181,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
180
181
 
181
182
  function processStucturedKey(fkArtifact, assocName, foreignKeyRef) {
182
183
  const subStruct = fkArtifact.elements ? fkArtifact : csnUtils.getFinalBaseType(fkArtifact.type);
183
- const flatElements = flattenStructure(subStruct, subStruct.$path, csnUtils, options, error, undefined, fkArtifact.$path.slice(-1) || []).newFlatElements;
184
+ const flatElements = flattenStructure(subStruct.elements, subStruct.$path, csnUtils, options, error, undefined, fkArtifact.$path.slice(-1) || []).newFlatElements;
184
185
  for (const [flatElemName, flatElem] of Object.entries(flatElements)) {
185
186
  const foreignKeyElementName =
186
187
  `${assocName.replace(/\./g, '_')}_${foreignKeyRef.as ? flatElemName.replace(implicitAs(foreignKeyRef.ref), foreignKeyRef.as) : flatElemName}`;
@@ -1,6 +1,7 @@
1
1
  const { forEachRef } = require('../../model/csnUtils');
2
2
  const { setProp } = require('../../base/model');
3
3
  const { implicitAs } = require('../../model/csnRefs');
4
+ const { structuralPath } = require('./structuralPath');
4
5
 
5
6
  /** This class is used for generic reference flattening.
6
7
  * It provides the following functionality:
@@ -38,6 +39,7 @@ class ReferenceFlattener {
38
39
  this.structuredReference = {};
39
40
  this.generatedElementsForPath = {};
40
41
  this.elementTransitions = {};
42
+ this.elementNamesWithDots = {};
41
43
  }
42
44
 
43
45
  /**
@@ -72,8 +74,7 @@ class ReferenceFlattener {
72
74
  });
73
75
  if (paths)
74
76
  setProp(node, '$paths', paths);
75
- setProp(node, '$scope', resolved.scope);
76
- // cache also if structured or not
77
+ // cache if structured or not
77
78
  let structured = resolved.links.map(link => link.art ? (isStructured(link.art)) : undefined);
78
79
  this.structuredReference[path.join('/')] = structured;
79
80
  });
@@ -157,6 +158,14 @@ class ReferenceFlattener {
157
158
  return this.generatedElementsForPath[path.join('/')];
158
159
  }
159
160
 
161
+ setElementNameWithDots(path, elementNameWithDots) {
162
+ this.elementNamesWithDots[path.join('/')] = elementNameWithDots;
163
+ }
164
+
165
+ getElementNameWithDots(path) {
166
+ return this.elementNamesWithDots[path.join('/')];
167
+ }
168
+
160
169
  /**
161
170
  * Generic reference flattener as {@link ReferenceFlattener described}.
162
171
  *
@@ -167,35 +176,40 @@ class ReferenceFlattener {
167
176
  if (node.$paths) {
168
177
  let newRef = []; // flattened reference
169
178
  let flattenWithPrevious = false;
170
- let anythingFlattened = false; // will be set to true if at least one node is flatted
179
+ let lastFlattenedID = null; // The variable will be set to the index of the last flattened path
171
180
  node.$paths.forEach((path, i) => {
172
181
  if (path === undefined || !ref[i]) return;
173
- let spath = path.join('/')
182
+ let spath = path.join('/');
174
183
  let movedTo = this.elementTransitions[spath]; // detect element transition
175
184
  let flattened = this.flattenedElementPaths[spath];
176
185
  if (flattenWithPrevious) {
177
- newRef[newRef.length - 1] = newRef[newRef.length - 1] + '_' + ref[i];
178
- anythingFlattened = true;
186
+ newRef[newRef.length - 1] = newRef[newRef.length - 1] + '_' + (ref[i].id || ref[i]);
187
+ // if we have a filter or args in an assoc, it needs to be kept, therefore
188
+ // the id of the ref is updated with the flattened version
189
+ if (ref[i].id) {
190
+ ref[i].id = newRef[newRef.length - 1];
191
+ newRef[newRef.length - 1] = ref[i];
192
+ }
193
+ lastFlattenedID = i;
179
194
  } else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
180
195
  let movedToElementName = movedTo[movedTo.length-1];
181
196
  newRef.push(movedToElementName);
182
- anythingFlattened=true;
197
+ lastFlattenedID = i;
183
198
  } else {
184
199
  newRef.push(ref[i]);
185
200
  }
186
201
  flattenWithPrevious = flattened;
187
202
  });
188
- if (newRef !== undefined && anythingFlattened) { // do not replace unchanged references
203
+ if (newRef !== undefined && lastFlattenedID !== null) { // make sure the reference changed and only then replace it with the new one
189
204
  // check if this is a column and add alias if missing
190
- if (isColumnInSelect(ref.$path)) {
191
- if (!node.as) {
205
+ let structural = structuralPath(csn, path);
206
+ if (isColumnInSelectOrProjection(structural)) {
207
+ if (!node.as && lastFlattenedID === ref.length-1) // attach alias only if there is none and it is the last item in the reference that was flattened
192
208
  node.as = node.ref[node.ref.length - 1];
193
- }
194
209
  }
195
210
  setProp(newRef, '$path', path.concat('ref'));
196
211
  if (!node.as) {
197
- // FIXME: rather use scope from inspectRef to determin if path leads to a key
198
- if (ifKeysScope(path))
212
+ if (isPartOfKeysStructure(structural))
199
213
  node.as = implicitAs(node.ref);
200
214
  else
201
215
  setProp(node, 'as', implicitAs(node.ref))
@@ -208,13 +222,11 @@ class ReferenceFlattener {
208
222
 
209
223
  applyAliasesInOnCond(csn, inspectRef) {
210
224
  forEachRef(csn, (ref, node, path) => {
211
- // is there a better way to detect if we are dealing with on-cond?
212
- // TODO: please do not use inspectRef() results for "where am I?" info
213
- if (!['$self', 'parent'].includes(node.$scope)) return;
214
- // the above condition means: only handle references starting with
215
- // $self or element references outside queries
216
-
225
+ // Process only on-conditions of associations
226
+ let structural = structuralPath(csn, path);
227
+ if(!isOnCondition(structural)) return;
217
228
  let { links } = inspectRef(path);
229
+ if(!links) return; // $user not resolvable
218
230
  let keysOfPreviousStepWhenManagedAssoc = undefined;
219
231
 
220
232
  let aliasedRef = [...ref];
@@ -238,32 +250,41 @@ class ReferenceFlattener {
238
250
  }
239
251
  }
240
252
 
241
- // FIXME: rather use scope from inspectRef to determin if path leads to a key
242
- function ifKeysScope(path) {
243
- if (!path) return false;
244
- if (Number.isInteger(path[path.length - 1]) && path[path.length - 2] === 'keys' && path[path.length - 4] === 'elements')
245
- return true;
246
- return false;
253
+ /**
254
+ * Checks if the provided path is a column inside a key node
255
+ * by exploring the possibility of the structural path to ends with 'elements' and 'keys'.
256
+ *
257
+ * @param structural structural path to explore
258
+ * @returns {boolean} True if the provided path matched the requirements to be part of a key node.
259
+ */
260
+ function isPartOfKeysStructure(structural) {
261
+ return structural[structural.length-2] === 'elements'
262
+ && structural[structural.length-1] === 'keys';
247
263
  }
248
264
 
249
265
  /**
250
- * Checks if the provided path is a column inside a select node
251
- * by exploring the possibility of the path to end with ['SELECT','columns','<an integer>', <ref - not checked>].
266
+ * Checks if the provided path is a column inside a select or a projection node
267
+ * by exploring the possibility of the structural path to contain 'SELECT' or 'projection'
268
+ * and ends with 'columns' or 'columns' and 'expand'.
252
269
  *
253
- * @param {CSN.Path} path Path to explore
270
+ * @param structural structural path to explore
254
271
  * @returns {boolean} True if the provided path matched the requirements to be a select node.
255
272
  */
256
- function isColumnInSelect(path) {
257
- if (!path)
258
- return false;
259
- const len = path.length;
260
- if (path[len - 4] === 'SELECT' && path[len - 3] === 'columns') {
261
- // check if -2 element is an integer / array index
262
- const arrayIndex = parseInt(String(path[len - 2]), 10);
263
- if (!isNaN(arrayIndex))
264
- return true;
265
- }
266
- return false;
273
+ function isColumnInSelectOrProjection(structural) {
274
+ return (structural.includes('SELECT') || structural.includes('projection'))
275
+ && (structural[structural.length-1] === 'columns' || (structural[structural.length-2] === 'columns' && structural[structural.length-1] === 'expand'));
276
+ }
277
+
278
+ /**
279
+ * Checks if the provided path is a column inside an on-condition of an element
280
+ * by exploring the possibility of the structural path to ends with 'elements' and 'on'.
281
+ *
282
+ * @param structural structural path to explore
283
+ * @returns {boolean} True if the provided path matched the requirements to be part of an element's on-condition.
284
+ */
285
+ function isOnCondition(structural) {
286
+ return structural[structural.length-2] === 'elements'
287
+ && structural[structural.length-1] === 'on';
267
288
  }
268
289
 
269
290
  module.exports = ReferenceFlattener;
@@ -24,7 +24,7 @@ function buildDependenciesFor(csn, referenceFlattener, isExternalServiceMember)
24
24
  forEachDefinition(csn, (def, definitionName) => {
25
25
  /** @type {CSN.Path} */
26
26
  let root = ['definitions', definitionName];
27
- forEachMemberRecursively(def, (element, elementName, _prop, subpath, parent) => {
27
+ forEachMemberRecursively(def, (element, elementName, structuralNodeName, subpath, parent) => {
28
28
  let path = root.concat(subpath);
29
29
  // go only through managed associations and compositions
30
30
  if (isAssociationOrComposition(element) && element.keys && !element.on) { // check association FKs
@@ -44,7 +44,7 @@ function buildDependenciesFor(csn, referenceFlattener, isExternalServiceMember)
44
44
  elementDependencies.push(targetElementPath);
45
45
  }
46
46
  })
47
- dependencies[path.join("/")] = { definitionName, elementName, element, path, parent, dependencies: elementDependencies };
47
+ dependencies[path.join("/")] = { structuralNodeName, definitionName, elementName, element, path, parent, dependencies: elementDependencies };
48
48
  }
49
49
  }) // forEachMemberRecursively
50
50
  }, { skipArtifact: isExternalServiceMember }) // forEachDefinition
@@ -0,0 +1,72 @@
1
+ // The module traverses a given CSN using a specific path, collects structural node names and returns them.
2
+
3
+ const structuralNodeHandlers = {
4
+ definitions: traverseDict,
5
+ elements: traverseDict,
6
+ actions: traverseDict,
7
+ params: traverseDict,
8
+ items: traverseTyped,
9
+ enum: traverseDict,
10
+ returns: traverseTyped,
11
+ on: traverseArray,
12
+ keys: traverseArray,
13
+ ref: traverseArray,
14
+ query: traverseTyped,
15
+ SELECT: traverseTyped,
16
+ SET: traverseTyped,
17
+ args: traverseArray,
18
+ columns: traverseArray,
19
+ projection: traverseTyped,
20
+ from: traverseTyped,
21
+ mixin: traverseDict,
22
+ where: traverseArray,
23
+ orderBy: traverseArray,
24
+ groupBy: traverseArray,
25
+ having: traverseArray,
26
+ xpr: traverseArray,
27
+ expand: traverseArray,
28
+ inline: traverseArray,
29
+ cast: traverseTyped,
30
+ }
31
+
32
+ function structuralPath(csn, path) {
33
+ return traverseDict(csn.definitions, path, 1, ['definitions']);
34
+ }
35
+
36
+ function traverseArray(obj, path, index, typeStack) {
37
+ if(!Array.isArray(obj)) return typeStack;
38
+ const name = path[index];
39
+ const element = obj[name];
40
+ return traverseTyped(element, path, index+1, typeStack);
41
+ }
42
+
43
+ function traverseDict(obj, path, index, typeStack) {
44
+ if(typeof obj !== 'object') return typeStack;
45
+ const name = path[index];
46
+ if(name === undefined) return typeStack;
47
+ return traverseTyped(obj[name], path, index+1, typeStack);
48
+ }
49
+
50
+ function traverseDictArray(obj, path, index, typeStack) {
51
+ if(typeof obj !== 'object') return typeStack;
52
+ const name = path[index];
53
+ if(name === undefined) return typeStack;
54
+ return traverseArray(obj[name], path, index+1, typeStack);
55
+ }
56
+
57
+ function traverseTyped(obj, path, index, typeStack) {
58
+ if(!obj) return typeStack;
59
+ const name = path[index];
60
+ if(name === undefined) return typeStack;
61
+ if(name[0] === '@') return typeStack; // skip annotations
62
+ const func = structuralNodeHandlers[name];
63
+ if(func) return func(obj[name], path, index+1, typeStack.concat(name));
64
+ // not typed -> columns?
65
+ if(typeStack[typeStack.length-1] === 'columns')
66
+ return traverseDictArray(obj, path, index, typeStack);
67
+ return typeStack;
68
+ }
69
+
70
+ module.exports = {
71
+ structuralPath
72
+ }