@sap/cds-compiler 2.7.0 → 2.11.2

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 (87) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/bin/cdsc.js +42 -25
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +10 -0
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +17 -33
  7. package/lib/api/options.js +25 -13
  8. package/lib/api/validate.js +33 -9
  9. package/lib/backends.js +9 -8
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +26 -2
  13. package/lib/base/messages.js +25 -9
  14. package/lib/base/model.js +5 -3
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/onConditions.js +5 -0
  17. package/lib/checks/selectItems.js +4 -0
  18. package/lib/checks/types.js +26 -2
  19. package/lib/checks/unknownMagic.js +41 -0
  20. package/lib/checks/validator.js +7 -2
  21. package/lib/compiler/assert-consistency.js +18 -5
  22. package/lib/compiler/base.js +65 -0
  23. package/lib/compiler/builtins.js +30 -1
  24. package/lib/compiler/checks.js +5 -2
  25. package/lib/compiler/definer.js +145 -120
  26. package/lib/compiler/index.js +16 -4
  27. package/lib/compiler/propagator.js +5 -2
  28. package/lib/compiler/resolver.js +207 -47
  29. package/lib/compiler/shared.js +47 -200
  30. package/lib/compiler/utils.js +173 -0
  31. package/lib/edm/annotations/genericTranslation.js +183 -187
  32. package/lib/edm/csn2edm.js +94 -98
  33. package/lib/edm/edm.js +16 -20
  34. package/lib/edm/edmPreprocessor.js +302 -115
  35. package/lib/edm/edmUtils.js +31 -12
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +28 -1
  38. package/lib/gen/language.tokens +79 -69
  39. package/lib/gen/languageLexer.interp +28 -1
  40. package/lib/gen/languageLexer.js +879 -805
  41. package/lib/gen/languageLexer.tokens +71 -62
  42. package/lib/gen/languageParser.js +5308 -4308
  43. package/lib/json/from-csn.js +59 -30
  44. package/lib/json/to-csn.js +354 -105
  45. package/lib/language/antlrParser.js +11 -0
  46. package/lib/language/errorStrategy.js +1 -0
  47. package/lib/language/genericAntlrParser.js +81 -14
  48. package/lib/language/language.g4 +163 -31
  49. package/lib/main.d.ts +136 -17
  50. package/lib/main.js +7 -1
  51. package/lib/model/api.js +78 -0
  52. package/lib/model/csnRefs.js +115 -32
  53. package/lib/model/csnUtils.js +71 -33
  54. package/lib/model/enrichCsn.js +36 -9
  55. package/lib/model/revealInternalProperties.js +20 -4
  56. package/lib/modelCompare/compare.js +2 -1
  57. package/lib/optionProcessor.js +33 -16
  58. package/lib/render/.eslintrc.json +3 -1
  59. package/lib/render/DuplicateChecker.js +1 -1
  60. package/lib/render/toCdl.js +60 -17
  61. package/lib/render/toHdbcds.js +122 -74
  62. package/lib/render/toSql.js +57 -32
  63. package/lib/render/utils/common.js +6 -10
  64. package/lib/sql-identifier.js +6 -1
  65. package/lib/transform/db/constraints.js +273 -119
  66. package/lib/transform/db/draft.js +9 -6
  67. package/lib/transform/db/expansion.js +19 -7
  68. package/lib/transform/db/flattening.js +31 -7
  69. package/lib/transform/db/transformExists.js +344 -66
  70. package/lib/transform/db/views.js +438 -0
  71. package/lib/transform/forHanaNew.js +65 -436
  72. package/lib/transform/forOdataNew.js +21 -10
  73. package/lib/transform/localized.js +2 -0
  74. package/lib/transform/odata/attachPath.js +19 -4
  75. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  76. package/lib/transform/odata/referenceFlattener.js +44 -38
  77. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  78. package/lib/transform/odata/structuralPath.js +72 -0
  79. package/lib/transform/odata/structureFlattener.js +13 -10
  80. package/lib/transform/odata/typesExposure.js +22 -12
  81. package/lib/transform/transformUtilsNew.js +55 -9
  82. package/lib/transform/translateAssocsToJoins.js +11 -17
  83. package/lib/transform/universalCsnEnricher.js +67 -0
  84. package/lib/utils/file.js +5 -3
  85. package/lib/utils/term.js +65 -42
  86. package/lib/utils/timetrace.js +48 -26
  87. package/package.json +1 -1
@@ -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,8 +22,9 @@ 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
29
  const { addLocalizationViews } = require('./localized');
29
30
 
@@ -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,6 +120,9 @@ 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);
125
+
122
126
  const keepLocalizedViews = isDeprecatedEnabled(options, 'createLocalizedViews');
123
127
 
124
128
  function acceptLocalizedView(_name, parent) {
@@ -187,7 +191,11 @@ function transform4odataWithCsn(inputModel, options) {
187
191
  generateForeignKeys(csn, options, referenceFlattener, csnUtils, error, isExternalServiceMember);
188
192
 
189
193
  // Apply default type facets as set by options
190
- // 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
+ */
191
199
  // This must be done before all the draft logic as all
192
200
  // composition targets are annotated with @odata.draft.enabled in this step
193
201
  forEachDefinition(csn, [ setDefaultTypeFacets, processOnCond ], { skipArtifact: isExternalServiceMember });
@@ -326,7 +334,7 @@ function transform4odataWithCsn(inputModel, options) {
326
334
  renameAnnotation(node, name, '@UI.Importance');
327
335
  let annotation = node['@UI.Importance'];
328
336
  if (annotation !== null)
329
- node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' }
337
+ node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' };
330
338
  }
331
339
 
332
340
  // Special case: '@readonly' becomes a triplet of capability restrictions for entities,
@@ -457,11 +465,11 @@ function transform4odataWithCsn(inputModel, options) {
457
465
  }
458
466
  // Generate the annotations describing the draft actions (only draft roots can be activated/edited)
459
467
  if (artifact == rootArtifact) {
460
- artifact['@Common.DraftRoot.ActivationAction'] = 'draftActivate';
461
- artifact['@Common.DraftRoot.EditAction'] = 'draftEdit';
462
- 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]);
463
471
  } else {
464
- artifact['@Common.DraftNode.PreparationAction'] = 'draftPrepare';
472
+ resetAnnotation(artifact, '@Common.DraftNode.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
465
473
  }
466
474
 
467
475
  artifact.elements && Object.values(artifact.elements).forEach( elem => {
@@ -529,7 +537,7 @@ function transform4odataWithCsn(inputModel, options) {
529
537
 
530
538
  // Ignore if that is our own draft root
531
539
  if (draftNode != rootArtifact) {
532
- // Barf if the draft node has @odata.draft.enabled itself
540
+ // Report error when the draft node has @odata.draft.enabled itself
533
541
  if (hasAnnotationValue(draftNode, '@odata.draft.enabled', true)) {
534
542
  error(null, ['definitions', artifactName, 'elements', elemName], 'Composition in draft-enabled entity can\'t lead to another entity with “@odata.draft.enabled”');
535
543
  }
@@ -565,6 +573,9 @@ function transform4odataWithCsn(inputModel, options) {
565
573
  // CDXCORE-481
566
574
  // (4.5) If the member is an association whose target has @cds.odata.valuelist annotate it
567
575
  // with @Common.ValueList.viaAssociation.
576
+ /*
577
+ FIXME (HJB): Comment outdated: Anno propagation to FKs is done in EdmPreprocessor
578
+ */
568
579
  // This must be done before foreign keys are calculated and the annotations are propagated
569
580
  // to them. This will make sure that association and all its foreign keys are annotated with
570
581
  // Common.ValueList in the final EDM.
@@ -698,6 +698,8 @@ function copyPersistenceAnnotations(target, source) {
698
698
  * @param {CSN.Options} options
699
699
  */
700
700
  function hasExistingLocalizationViews(csn, options) {
701
+ if (!csn || !csn.definitions)
702
+ return false;
701
703
  const firstLocalizedView = Object.keys(csn.definitions).find(isInLocalizedNamespace);
702
704
  if (firstLocalizedView) {
703
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:
@@ -73,8 +74,7 @@ class ReferenceFlattener {
73
74
  });
74
75
  if (paths)
75
76
  setProp(node, '$paths', paths);
76
- setProp(node, '$scope', resolved.scope);
77
- // cache also if structured or not
77
+ // cache if structured or not
78
78
  let structured = resolved.links.map(link => link.art ? (isStructured(link.art)) : undefined);
79
79
  this.structuredReference[path.join('/')] = structured;
80
80
  });
@@ -176,10 +176,10 @@ class ReferenceFlattener {
176
176
  if (node.$paths) {
177
177
  let newRef = []; // flattened reference
178
178
  let flattenWithPrevious = false;
179
- 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
180
180
  node.$paths.forEach((path, i) => {
181
181
  if (path === undefined || !ref[i]) return;
182
- let spath = path.join('/')
182
+ let spath = path.join('/');
183
183
  let movedTo = this.elementTransitions[spath]; // detect element transition
184
184
  let flattened = this.flattenedElementPaths[spath];
185
185
  if (flattenWithPrevious) {
@@ -190,27 +190,26 @@ class ReferenceFlattener {
190
190
  ref[i].id = newRef[newRef.length - 1];
191
191
  newRef[newRef.length - 1] = ref[i];
192
192
  }
193
- anythingFlattened = true;
193
+ lastFlattenedID = i;
194
194
  } else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
195
195
  let movedToElementName = movedTo[movedTo.length-1];
196
196
  newRef.push(movedToElementName);
197
- anythingFlattened=true;
197
+ lastFlattenedID = i;
198
198
  } else {
199
199
  newRef.push(ref[i]);
200
200
  }
201
201
  flattenWithPrevious = flattened;
202
202
  });
203
- 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
204
204
  // check if this is a column and add alias if missing
205
- if (isColumnInSelect(ref.$path)) {
206
- 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
207
208
  node.as = node.ref[node.ref.length - 1];
208
- }
209
209
  }
210
210
  setProp(newRef, '$path', path.concat('ref'));
211
211
  if (!node.as) {
212
- // FIXME: rather use scope from inspectRef to determin if path leads to a key
213
- if (ifKeysScope(path))
212
+ if (isPartOfKeysStructure(structural))
214
213
  node.as = implicitAs(node.ref);
215
214
  else
216
215
  setProp(node, 'as', implicitAs(node.ref))
@@ -223,13 +222,11 @@ class ReferenceFlattener {
223
222
 
224
223
  applyAliasesInOnCond(csn, inspectRef) {
225
224
  forEachRef(csn, (ref, node, path) => {
226
- // is there a better way to detect if we are dealing with on-cond?
227
- // TODO: please do not use inspectRef() results for "where am I?" info
228
- if (!['$self', 'parent'].includes(node.$scope)) return;
229
- // the above condition means: only handle references starting with
230
- // $self or element references outside queries
231
-
225
+ // Process only on-conditions of associations
226
+ let structural = structuralPath(csn, path);
227
+ if(!isOnCondition(structural)) return;
232
228
  let { links } = inspectRef(path);
229
+ if(!links) return; // $user not resolvable
233
230
  let keysOfPreviousStepWhenManagedAssoc = undefined;
234
231
 
235
232
  let aliasedRef = [...ref];
@@ -253,32 +250,41 @@ class ReferenceFlattener {
253
250
  }
254
251
  }
255
252
 
256
- // FIXME: rather use scope from inspectRef to determin if path leads to a key
257
- function ifKeysScope(path) {
258
- if (!path) return false;
259
- if (Number.isInteger(path[path.length - 1]) && path[path.length - 2] === 'keys' && path[path.length - 4] === 'elements')
260
- return true;
261
- 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';
262
263
  }
263
264
 
264
265
  /**
265
- * Checks if the provided path is a column inside a select node
266
- * 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'.
267
269
  *
268
- * @param {CSN.Path} path Path to explore
270
+ * @param structural structural path to explore
269
271
  * @returns {boolean} True if the provided path matched the requirements to be a select node.
270
272
  */
271
- function isColumnInSelect(path) {
272
- if (!path)
273
- return false;
274
- const len = path.length;
275
- if (path[len - 4] === 'SELECT' && path[len - 3] === 'columns') {
276
- // check if -2 element is an integer / array index
277
- const arrayIndex = parseInt(String(path[len - 2]), 10);
278
- if (!isNaN(arrayIndex))
279
- return true;
280
- }
281
- 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';
282
288
  }
283
289
 
284
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
+ }
@@ -74,17 +74,22 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
74
74
  if (definition.kind !== 'entity' && definition.kind !== 'view')
75
75
  return;
76
76
 
77
- let { newFlatElements } = flattenStructure(definition, definitionPath, csnUtils, options, error, referenceFlattener);
77
+ let { newFlatElements } = flattenStructure(definition.elements, definitionPath, csnUtils, options, error, referenceFlattener);
78
78
 
79
79
  attachPathOnPartialCSN(newFlatElements, definitionPath.concat('elements'));
80
-
81
80
  definition.elements = newFlatElements;
81
+
82
+ if (definition.params) {
83
+ let { newFlatElements } = flattenStructure(definition.params, definitionPath, csnUtils, options, error, referenceFlattener);
84
+ attachPathOnPartialCSN(newFlatElements, definitionPath.concat('params'));
85
+ definition.params = newFlatElements;
86
+ }
82
87
  } // flattenDefinition
83
88
 
84
89
  /**
85
90
  * Flattens structured element by calling element flattener for each structured child.
86
91
  * Returns a dictionary containing all the new elements for the given structure.
87
- * @param {*} struct the structure to flatten
92
+ * @param {*} dictionary to flatten
88
93
  * @param {CSN.Path} path the path of the structure in the CSN tree
89
94
  * @param {*} csnUtils
90
95
  * @param {Function} error Error message function
@@ -93,14 +98,14 @@ function flattenDefinition(definition, definitionPath, csnUtils, options, refere
93
98
  * @param {*} [newFlatElements]
94
99
  * @param {boolean} [isTopLevelElement] states if this is a top level element
95
100
  */
96
- function flattenStructure(struct, path, csnUtils, options, error, referenceFlattener = undefined, elementPathInStructure = [],
101
+ function flattenStructure(dictionary, path, csnUtils, options, error, referenceFlattener = undefined, elementPathInStructure = [],
97
102
  newFlatElements = Object.create(null), isTopLevelElement = true, isParentNotNull = false) {
98
103
 
99
- if (!isTopLevelElement) addPropsForPropagationFromElement(struct);
104
+ if (!isTopLevelElement) addPropsForPropagationFromElement(dictionary);
100
105
 
101
106
  let generatedNewFlatElementsNames = []; // holds the names of all new child elements of the structure
102
107
 
103
- struct.elements && Object.entries(struct.elements).forEach(([elementName, element]) => {
108
+ dictionary && Object.entries(dictionary).forEach(([elementName, element]) => {
104
109
  let currPath = path.concat('elements', elementName);
105
110
 
106
111
  if (isTopLevelElement) {
@@ -118,8 +123,8 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
118
123
  addPropsForPropagationFromElement(element);
119
124
 
120
125
  // if the child element is structured itself -> needs to be flattened
121
- const subStruct = element.elements ? element : csnUtils.getFinalBaseType(element.type);
122
- let result = flattenStructure(subStruct, currPath, csnUtils, options, error, referenceFlattener, elementPathInStructure.concat(elementName), newFlatElements, false, isNotNull());
126
+ const elements = element.elements || csnUtils.getFinalBaseType(element.type).elements;
127
+ let result = flattenStructure(elements, currPath, csnUtils, options, error, referenceFlattener, elementPathInStructure.concat(elementName), newFlatElements, false, isNotNull());
123
128
  generatedNewFlatElementsNames.push(...result.generatedNewFlatElementsNames); // accomulate names of produced elements
124
129
 
125
130
  } else { // when we do not need to flat, this is scalar or empty (cds-compiler#4337) -> needs to be registered in referenceFlattener
@@ -127,7 +132,6 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
127
132
  let elementNameWithDots = elementPathInStructure.concat(elementName).join('.');
128
133
  addNewElementToResult(element, newElementName, elementNameWithDots, currPath);
129
134
  }
130
-
131
135
  });
132
136
 
133
137
  if (referenceFlattener) {
@@ -135,7 +139,6 @@ function flattenStructure(struct, path, csnUtils, options, error, referenceFlatt
135
139
  }
136
140
  return { newFlatElements, generatedNewFlatElementsNames };
137
141
 
138
-
139
142
  // adds newly created element into the final dictionary of elements
140
143
  function addNewElementToResult(element, elementName, elementNameWithDots, path) {
141
144
  if (newFlatElements[elementName]) {
@@ -25,7 +25,6 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
25
25
  // collect in this variable all the newly exposed types
26
26
  const exposedStructTypes = [];
27
27
  const schemas = Object.create(null);
28
-
29
28
  // walk through the definitions of the given CSN and expose types where needed
30
29
  forEachDefinition(csn, (def, defName, propertyName, path) => {
31
30
  // we do expose types only for definition from inside services
@@ -33,11 +32,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
33
32
  if (serviceName) {
34
33
  if (['type', 'entity', 'view'].includes(def.kind)) {
35
34
  forEachMember(def, (element, elementName, propertyName, path) => {
36
- if (propertyName === 'elements' || propertyName === 'params') {
35
+ if (['elements', 'params'].includes(propertyName)) {
37
36
  const artificialtName = `${isMultiSchema ?
38
37
  defNameWithoutServiceOrContextName(defName, serviceName)
39
38
  : defNameWithoutServiceOrContextName(defName, serviceName).replace(/\./g, '_')}_${elementName}`;
40
- exposeTypeOf(element, elementName, defName, serviceName, artificialtName, path);
39
+ exposeTypeOf(element, element.key || propertyName === 'params', elementName, defName, serviceName, artificialtName, path);
41
40
  }
42
41
  }, path);
43
42
  }
@@ -64,11 +63,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
64
63
  * @param {string} artificialName
65
64
  * @param {CSN.Path} path
66
65
  */
67
- function exposeTypeOf(node, memberName, defName, service, artificialName, path) {
66
+ function exposeTypeOf(node, isKey, memberName, defName, service, artificialName, path) {
68
67
  if (isArrayed(node))
69
- exposeArrayOfTypeOf(node, memberName, defName, service, artificialName, path);
68
+ exposeArrayOfTypeOf(node, isKey, memberName, defName, service, artificialName, path);
70
69
  else if (csnUtils.isStructured(node))
71
- exposeStructTypeOf(node, memberName, defName, service, artificialName, path);
70
+ exposeStructTypeOf(node, isKey, memberName, defName, service, artificialName, path);
72
71
  }
73
72
 
74
73
  /**
@@ -90,12 +89,12 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
90
89
  function exposeTypesOfAction(action, actionName, defName, service, path) {
91
90
  if (action.returns) {
92
91
  const artificialName = `return_${actionName.replace(/\./g, '_')}`;
93
- exposeTypeOf(action.returns, actionName, defName, service, artificialName, path.concat(['returns']));
92
+ exposeTypeOf(action.returns, false, actionName, defName, service, artificialName, path.concat(['returns']));
94
93
  }
95
94
 
96
95
  action.params && Object.entries(action.params).forEach(([paramName, param]) => {
97
96
  const artificialName = `param_${actionName.replace(/\./g, '_')}_${paramName}`;
98
- exposeTypeOf(param, actionName, defName, service, artificialName, path.concat(['params', paramName]));
97
+ exposeTypeOf(param, false, actionName, defName, service, artificialName, path.concat(['params', paramName]));
99
98
  });
100
99
  }
101
100
 
@@ -108,9 +107,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
108
107
  * @param {String} service
109
108
  * @param {String} artificialName
110
109
  */
111
- function exposeStructTypeOf(node, memberName, defName, service, artificialName, path, parentName) {
110
+ function exposeStructTypeOf(node, isKey, memberName, defName, service, artificialName, path, parentName) {
111
+ if (node.items) exposeStructTypeOf(node.items, isKey, memberName, defName, service, artificialName, path);
112
112
 
113
- if (node.items) exposeStructTypeOf(node.items, memberName, defName, service, artificialName, path);
113
+ // start conservative, assume we're in a named type
114
+ let isAnonymous = false;
114
115
 
115
116
  if (isExposableStructure(node)) {
116
117
  let typeDef = node.type ? csnUtils.getCsnDef(node.type) : /* structure|anonymous type */ node;
@@ -124,6 +125,11 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
124
125
  // and the new target. Consequently, we now have both type and elements properties in this case, and the elements should be taken as a priority
125
126
  // as the correct target is there and no longer in the type definition
126
127
  let newTypeElements = (node.type && node.elements) ? node.elements : typeDef.elements;
128
+ // if node and typeDef are identical, we're anonymous
129
+ isAnonymous = node === typeDef;
130
+ // if we've left the anonymous world, we're no longer in a key def
131
+ if (!isAnonymous)
132
+ isKey = false;
127
133
 
128
134
  let newType = exposeStructType(newTypeFullName, newTypeElements, memberName, path);
129
135
  if (!newType) {
@@ -138,6 +144,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
138
144
  newType.elements && Object.entries(newType.elements).forEach(([elemName, newElem]) => {
139
145
  if (node.elements && node.elements[elemName].$location) setProp(newElem, '$location', node.elements[elemName].$location);
140
146
  exposeStructTypeOf(newElem,
147
+ isKey,
141
148
  memberName,
142
149
  typeDef.kind === 'type' ? node.type : defName,
143
150
  service,
@@ -251,6 +258,9 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
251
258
  error(null, path, `"${elemName}": Element name conflicts with existing element`);
252
259
  }
253
260
  let cloned = cloneCsn(element, options);
261
+ // if this was an anonymous sub element of a key, mark it as not nullable
262
+ if(isAnonymous && isKey && !cloned.key && cloned.notNull === undefined)
263
+ cloned.notNull = true;
254
264
  type.elements[elemName] = cloned;
255
265
  });
256
266
 
@@ -265,12 +275,12 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
265
275
 
266
276
  // If a member is of type "array of <named type|anonymous type>", we expose the arrayed type,
267
277
  // like we expose structures in structured mode
268
- function exposeArrayOfTypeOf(node, memberName, defName, service, artificialName, path) {
278
+ function exposeArrayOfTypeOf(node, isKey, memberName, defName, service, artificialName, path) {
269
279
  // if anonymously defined in place -> we always expose the type
270
280
  // this would be definition like 'elem: array of { ... }'
271
281
  // and we use the artificial name for the new type name
272
282
  if (node.items && !node.type) {
273
- exposeStructTypeOf(node.items, memberName, defName, service, artificialName, path.concat('items'));
283
+ exposeStructTypeOf(node.items, isKey, memberName, defName, service, artificialName, path.concat('items'));
274
284
  }
275
285
  // we can have both of the 'type' and 'items' in the cases:
276
286
  // 1. 'elem: Foo' and 'type Foo: array of Baz' and 'type Baz: { ... }'