@sap/cds-compiler 2.5.2 → 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 (102) hide show
  1. package/CHANGELOG.md +235 -9
  2. package/bin/cdsc.js +44 -27
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +37 -3
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +37 -123
  7. package/lib/api/options.js +27 -15
  8. package/lib/api/validate.js +34 -9
  9. package/lib/backends.js +9 -89
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +73 -11
  13. package/lib/base/messages.js +86 -30
  14. package/lib/base/model.js +6 -6
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/defaultValues.js +27 -2
  17. package/lib/checks/elements.js +1 -6
  18. package/lib/checks/foreignKeys.js +0 -6
  19. package/lib/checks/managedWithoutKeys.js +17 -0
  20. package/lib/checks/nonexpandableStructured.js +38 -0
  21. package/lib/checks/onConditions.js +9 -45
  22. package/lib/checks/queryNoDbArtifacts.js +25 -7
  23. package/lib/checks/selectItems.js +29 -2
  24. package/lib/checks/types.js +26 -2
  25. package/lib/checks/unknownMagic.js +41 -0
  26. package/lib/checks/utils.js +61 -0
  27. package/lib/checks/validator.js +60 -7
  28. package/lib/compiler/assert-consistency.js +23 -7
  29. package/lib/compiler/base.js +65 -0
  30. package/lib/compiler/builtins.js +30 -1
  31. package/lib/compiler/checks.js +8 -5
  32. package/lib/compiler/definer.js +157 -133
  33. package/lib/compiler/index.js +89 -31
  34. package/lib/compiler/propagator.js +5 -2
  35. package/lib/compiler/resolver.js +375 -185
  36. package/lib/compiler/shared.js +49 -202
  37. package/lib/compiler/utils.js +173 -0
  38. package/lib/edm/annotations/genericTranslation.js +183 -187
  39. package/lib/edm/csn2edm.js +104 -108
  40. package/lib/edm/edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +388 -146
  42. package/lib/edm/edmUtils.js +104 -34
  43. package/lib/gen/Dictionary.json +22 -0
  44. package/lib/gen/language.checksum +1 -1
  45. package/lib/gen/language.interp +28 -1
  46. package/lib/gen/language.tokens +79 -69
  47. package/lib/gen/languageLexer.interp +28 -1
  48. package/lib/gen/languageLexer.js +879 -805
  49. package/lib/gen/languageLexer.tokens +71 -62
  50. package/lib/gen/languageParser.js +5330 -4300
  51. package/lib/json/from-csn.js +110 -52
  52. package/lib/json/to-csn.js +434 -120
  53. package/lib/language/antlrParser.js +15 -3
  54. package/lib/language/errorStrategy.js +1 -0
  55. package/lib/language/genericAntlrParser.js +93 -26
  56. package/lib/language/language.g4 +172 -31
  57. package/lib/main.d.ts +216 -19
  58. package/lib/main.js +32 -7
  59. package/lib/model/api.js +78 -0
  60. package/lib/model/csnRefs.js +413 -149
  61. package/lib/model/csnUtils.js +286 -75
  62. package/lib/model/enrichCsn.js +50 -6
  63. package/lib/model/revealInternalProperties.js +22 -5
  64. package/lib/modelCompare/compare.js +39 -21
  65. package/lib/optionProcessor.js +35 -18
  66. package/lib/render/.eslintrc.json +4 -1
  67. package/lib/render/DuplicateChecker.js +9 -6
  68. package/lib/render/toCdl.js +121 -36
  69. package/lib/render/toHdbcds.js +148 -98
  70. package/lib/render/toSql.js +114 -43
  71. package/lib/render/utils/common.js +8 -13
  72. package/lib/render/utils/sql.js +3 -3
  73. package/lib/sql-identifier.js +6 -1
  74. package/lib/transform/db/assertUnique.js +5 -6
  75. package/lib/transform/db/constraints.js +281 -106
  76. package/lib/transform/db/draft.js +11 -8
  77. package/lib/transform/db/expansion.js +584 -0
  78. package/lib/transform/db/flattening.js +341 -0
  79. package/lib/transform/db/groupByOrderBy.js +2 -2
  80. package/lib/transform/db/transformExists.js +345 -65
  81. package/lib/transform/db/views.js +438 -0
  82. package/lib/transform/forHanaNew.js +131 -793
  83. package/lib/transform/forOdataNew.js +30 -24
  84. package/lib/transform/localized.js +39 -10
  85. package/lib/transform/odata/attachPath.js +19 -4
  86. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  87. package/lib/transform/odata/referenceFlattener.js +60 -39
  88. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  89. package/lib/transform/odata/structuralPath.js +72 -0
  90. package/lib/transform/odata/structureFlattener.js +19 -18
  91. package/lib/transform/odata/typesExposure.js +22 -12
  92. package/lib/transform/transformUtilsNew.js +144 -78
  93. package/lib/transform/translateAssocsToJoins.js +22 -27
  94. package/lib/transform/universalCsnEnricher.js +67 -0
  95. package/lib/utils/file.js +5 -14
  96. package/lib/utils/moduleResolve.js +6 -8
  97. package/lib/utils/term.js +65 -42
  98. package/lib/utils/timetrace.js +48 -26
  99. package/package.json +1 -1
  100. package/lib/json/walker.js +0 -26
  101. package/lib/transform/sqlite +0 -0
  102. 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,
@@ -22,10 +22,11 @@ const { flattenCSN } = require('./odata/structureFlattener');
22
22
  const generateForeignKeys = require('./odata/generateForeignKeyElements');
23
23
  const expandStructKeysInAssociations = require('./odata/expandStructKeysInAssociations');
24
24
  const expandToFinalBaseType = require('./odata/toFinalBaseType');
25
- const timetrace = require('../utils/timetrace');
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();
@@ -189,7 +191,11 @@ function transform4odataWithCsn(inputModel, options) {
189
191
  generateForeignKeys(csn, options, referenceFlattener, csnUtils, error, isExternalServiceMember);
190
192
 
191
193
  // Apply default type facets as set by options
192
- // Flatten on-conditions in unmanaged associations
194
+ // Flatten on-conditions in unmanaged associations
195
+ /* FIXME (HJB): Is this comment still correct? processOnCond only strips $self
196
+ We should not remove $self prefixes in structured OData to not
197
+ interfer with path resolution
198
+ */
193
199
  // This must be done before all the draft logic as all
194
200
  // composition targets are annotated with @odata.draft.enabled in this step
195
201
  forEachDefinition(csn, [ setDefaultTypeFacets, processOnCond ], { skipArtifact: isExternalServiceMember });
@@ -227,16 +233,13 @@ function transform4odataWithCsn(inputModel, options) {
227
233
  if (options.toOdata.names && !['service', 'context', 'namespace', 'annotation', 'action', 'function'].includes(def.kind))
228
234
  def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.toOdata.names, csn);
229
235
 
230
- forEachMemberRecursively(def, (member, memberName, propertyName) => {
236
+ forEachMemberRecursively(def, (member, memberName, propertyName, path) => {
231
237
  // Annotate elements, foreign keys, parameters, etc. with their DB names if requested
232
238
  // Only these are actually required and don't annotate virtual elements in entities or types
233
239
  // as they have no DB representation (although in views)
234
240
  if (options.toOdata.names && typeof member === 'object' && !['action', 'function'].includes(member.kind) && propertyName !== 'enum' && (!member.virtual || def.query)) {
235
241
  // 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);
242
+ member['@cds.persistence.name'] = getElementDatabaseNameOf(referenceFlattener.getElementNameWithDots(path) || memberName, options.toOdata.names);
240
243
  }
241
244
 
242
245
  // Mark fields with @odata.on.insert/update as @Core.Computed
@@ -331,7 +334,7 @@ function transform4odataWithCsn(inputModel, options) {
331
334
  renameAnnotation(node, name, '@UI.Importance');
332
335
  let annotation = node['@UI.Importance'];
333
336
  if (annotation !== null)
334
- node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' }
337
+ node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' };
335
338
  }
336
339
 
337
340
  // Special case: '@readonly' becomes a triplet of capability restrictions for entities,
@@ -462,11 +465,11 @@ function transform4odataWithCsn(inputModel, options) {
462
465
  }
463
466
  // Generate the annotations describing the draft actions (only draft roots can be activated/edited)
464
467
  if (artifact == rootArtifact) {
465
- artifact['@Common.DraftRoot.ActivationAction'] = 'draftActivate';
466
- artifact['@Common.DraftRoot.EditAction'] = 'draftEdit';
467
- artifact['@Common.DraftRoot.PreparationAction'] = 'draftPrepare';
468
+ resetAnnotation(artifact, '@Common.DraftRoot.ActivationAction', 'draftActivate', info, ['definitions', draftAdminDataProjectionName]);
469
+ resetAnnotation(artifact, '@Common.DraftRoot.EditAction', 'draftEdit', info, ['definitions', draftAdminDataProjectionName]);
470
+ resetAnnotation(artifact, '@Common.DraftRoot.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
468
471
  } else {
469
- artifact['@Common.DraftNode.PreparationAction'] = 'draftPrepare';
472
+ resetAnnotation(artifact, '@Common.DraftNode.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
470
473
  }
471
474
 
472
475
  artifact.elements && Object.values(artifact.elements).forEach( elem => {
@@ -534,7 +537,7 @@ function transform4odataWithCsn(inputModel, options) {
534
537
 
535
538
  // Ignore if that is our own draft root
536
539
  if (draftNode != rootArtifact) {
537
- // Barf if the draft node has @odata.draft.enabled itself
540
+ // Report error when the draft node has @odata.draft.enabled itself
538
541
  if (hasAnnotationValue(draftNode, '@odata.draft.enabled', true)) {
539
542
  error(null, ['definitions', artifactName, 'elements', elemName], 'Composition in draft-enabled entity can\'t lead to another entity with “@odata.draft.enabled”');
540
543
  }
@@ -570,6 +573,9 @@ function transform4odataWithCsn(inputModel, options) {
570
573
  // CDXCORE-481
571
574
  // (4.5) If the member is an association whose target has @cds.odata.valuelist annotate it
572
575
  // with @Common.ValueList.viaAssociation.
576
+ /*
577
+ FIXME (HJB): Comment outdated: Anno propagation to FKs is done in EdmPreprocessor
578
+ */
573
579
  // This must be done before foreign keys are calculated and the annotations are propagated
574
580
  // to them. This will make sure that association and all its foreign keys are annotated with
575
581
  // Common.ValueList in the final EDM.
@@ -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
  }
@@ -671,6 +698,8 @@ function copyPersistenceAnnotations(target, source) {
671
698
  * @param {CSN.Options} options
672
699
  */
673
700
  function hasExistingLocalizationViews(csn, options) {
701
+ if (!csn || !csn.definitions)
702
+ return false;
674
703
  const firstLocalizedView = Object.keys(csn.definitions).find(isInLocalizedNamespace);
675
704
  if (firstLocalizedView) {
676
705
  const { info } = makeMessageFunction(csn, options);
@@ -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
+ }