@sap/cds-compiler 5.7.2 → 5.8.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 (73) hide show
  1. package/CHANGELOG.md +62 -2
  2. package/bin/cdsse.js +13 -1
  3. package/doc/CHANGELOG_BETA.md +7 -0
  4. package/lib/api/options.js +2 -1
  5. package/lib/api/validate.js +9 -0
  6. package/lib/base/location.js +1 -1
  7. package/lib/base/message-registry.js +55 -20
  8. package/lib/base/messages.js +5 -2
  9. package/lib/base/model.js +8 -6
  10. package/lib/checks/assocOutsideService.js +40 -0
  11. package/lib/checks/featureFlags.js +4 -1
  12. package/lib/checks/types.js +7 -4
  13. package/lib/checks/validator.js +3 -0
  14. package/lib/compiler/assert-consistency.js +11 -5
  15. package/lib/compiler/checks.js +79 -17
  16. package/lib/compiler/define.js +60 -3
  17. package/lib/compiler/extend.js +1 -2
  18. package/lib/compiler/generate.js +1 -1
  19. package/lib/compiler/populate.js +17 -6
  20. package/lib/compiler/propagator.js +1 -1
  21. package/lib/compiler/resolve.js +181 -150
  22. package/lib/compiler/shared.js +276 -22
  23. package/lib/compiler/tweak-assocs.js +15 -4
  24. package/lib/compiler/xpr-rewrite.js +76 -50
  25. package/lib/edm/annotations/edmJson.js +1 -1
  26. package/lib/edm/annotations/genericTranslation.js +2 -2
  27. package/lib/edm/csn2edm.js +2 -2
  28. package/lib/edm/edmPreprocessor.js +15 -9
  29. package/lib/edm/edmUtils.js +12 -5
  30. package/lib/gen/CdlGrammar.checksum +1 -0
  31. package/lib/gen/CdlParser.js +2239 -2229
  32. package/lib/gen/Dictionary.json +55 -8
  33. package/lib/json/from-csn.js +37 -17
  34. package/lib/json/to-csn.js +4 -0
  35. package/lib/language/genericAntlrParser.js +7 -0
  36. package/lib/main.d.ts +5 -1
  37. package/lib/model/cloneCsn.js +1 -0
  38. package/lib/model/csnRefs.js +1 -0
  39. package/lib/model/csnUtils.js +0 -5
  40. package/lib/modelCompare/utils/filter.js +2 -2
  41. package/lib/optionProcessor.js +2 -0
  42. package/lib/parsers/AstBuildingParser.js +72 -34
  43. package/lib/parsers/CdlGrammar.g4 +20 -19
  44. package/lib/parsers/XprTree.js +206 -0
  45. package/lib/parsers/index.js +1 -1
  46. package/lib/render/toCdl.js +61 -89
  47. package/lib/render/toSql.js +59 -29
  48. package/lib/render/utils/standardDatabaseFunctions.js +252 -15
  49. package/lib/transform/addTenantFields.js +9 -3
  50. package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
  51. package/lib/transform/db/assocsToQueries/utils.js +10 -3
  52. package/lib/transform/db/expansion.js +3 -1
  53. package/lib/transform/db/flattening.js +7 -3
  54. package/lib/transform/db/killAnnotations.js +1 -0
  55. package/lib/transform/db/processSqlServices.js +70 -17
  56. package/lib/transform/draft/db.js +8 -3
  57. package/lib/transform/draft/odata.js +27 -4
  58. package/lib/transform/effective/main.js +37 -10
  59. package/lib/transform/effective/misc.js +4 -9
  60. package/lib/transform/effective/service.js +34 -0
  61. package/lib/transform/effective/types.js +28 -17
  62. package/lib/transform/forOdata.js +36 -10
  63. package/lib/transform/forRelationalDB.js +30 -18
  64. package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
  65. package/lib/transform/odata/createForeignKeys.js +120 -116
  66. package/lib/transform/odata/flattening.js +10 -8
  67. package/lib/transform/transformUtils.js +58 -25
  68. package/lib/transform/translateAssocsToJoins.js +10 -6
  69. package/lib/transform/universalCsn/coreComputed.js +5 -1
  70. package/package.json +1 -1
  71. package/share/messages/message-explanations.json +1 -0
  72. package/share/messages/rewrite-not-supported.md +5 -0
  73. package/share/messages/rewrite-undefined-key.md +94 -0
@@ -3,7 +3,7 @@
3
3
  const { setProp, isBetaEnabled } = require('../base/model');
4
4
  const { forEachMemberRecursively, forAllQueries, applyTransformationsOnNonDictionary,
5
5
  getArtifactDatabaseNameOf, getElementDatabaseNameOf, applyTransformations,
6
- isAspect, walkCsnPath, isPersistedOnDatabase
6
+ walkCsnPath, isPersistedOnDatabase
7
7
  } = require('../model/csnUtils');
8
8
  const { isBuiltinType } = require('../base/builtins');
9
9
  const transformUtils = require('./transformUtils');
@@ -34,7 +34,7 @@ const backlinks = require('./db/backlinks');
34
34
  const { getDefaultTypeLengths } = require('../render/utils/common');
35
35
  const { featureFlags } = require('./featureFlags');
36
36
  const { cloneCsnNonDict, cloneFullCsn } = require('../model/cloneCsn');
37
- const { processSqlServices } = require('./db/processSqlServices');
37
+ const { processSqlServices, createServiceDummy } = require('./db/processSqlServices');
38
38
 
39
39
  // By default: Do not process non-entities/views
40
40
  function forEachDefinition(csn, cb) {
@@ -157,7 +157,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
157
157
  timetrace.start('Validate');
158
158
  // Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
159
159
  validate.forRelationalDB(csn, {
160
- ...messageFunctions, csnUtils, ...csnUtils, csn, options, isAspect
160
+ ...messageFunctions, csnUtils, ...csnUtils, csn, options,
161
161
  });
162
162
  timetrace.stop('Validate');
163
163
 
@@ -354,12 +354,18 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
354
354
  // some errors can't be handled in the subsequent processing steps for e.g. HDBCDS
355
355
  messageFunctions.throwWithError();
356
356
 
357
+ // TODO: Might have to do this earlier if we want special rendering for projections?
358
+ const findAndMarkSqlServiceArtifacts = options.sqlDialect === 'hana' && options.src === 'hdi' && (csn.meta?.[featureFlags]?.$sqlService || csn.meta?.[featureFlags]?.$dummyService) ? processSqlServices(csn, options): () => {}
359
+
357
360
  // Apply view-specific transformations
358
361
  // (160) Projections now finally become views
359
362
  // Replace managed association in group/order by with foreign keys
360
363
  const transformEntityOrViewPass2 = getViewTransformer(csn, options, messageFunctions);
361
- forEachDefinition(csn, transformViews);
362
-
364
+ forEachDefinition(csn, [(artifact, artifactName) => {
365
+ findAndMarkSqlServiceArtifacts(artifact, artifactName);
366
+ if(artifact.$dummyService)
367
+ createServiceDummy(artifact, artifactName, csn, messageFunctions);
368
+ }, transformViews]);
363
369
 
364
370
  if(!doA2J) {
365
371
  forEachDefinition(csn, [
@@ -381,9 +387,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
381
387
  ]);
382
388
  }
383
389
 
384
- // TODO: Might have to do this earlier if we want special rendering for projections?
385
- const findAndMarkSqlServiceArtifacts = options.sqlDialect === 'hana' && options.src === 'hdi' && csn.meta?.[featureFlags]?.$sqlService ? processSqlServices(csn): () => {}
386
-
387
390
  // TODO: Could we maybe merge this with the final applyTransformations?
388
391
  applyTransformations(csn, {
389
392
  type: (parent, prop, type, path) => {
@@ -409,8 +412,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
409
412
  // Attach @cds.persistence.name to artifacts
410
413
  if (!artifact.$ignore && artifact.kind !== 'service' && artifact.kind !== 'context')
411
414
  csnUtils.addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
412
-
413
- findAndMarkSqlServiceArtifacts(artifact, artifactName);
414
415
  }], { allowArtifact: artifact => artifact.kind === 'entity'});
415
416
 
416
417
  throwWithAnyError();
@@ -636,9 +637,14 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
636
637
  function handleAssocToJoins() {
637
638
  // the augmentor isn't able to deal with technical configurations and since assoc2join can ignore it we
638
639
  // simply make it invisible and copy it over to the result csn
639
- forEachDefinition(csn,
640
- art => art.technicalConfig && setProp(art, 'technicalConfig',
641
- art.technicalConfig));
640
+ forEachDefinition(csn, (art) => {
641
+ if (art.technicalConfig)
642
+ setProp(art, 'technicalConfig', art.technicalConfig);
643
+ if (art.kind === 'type' && art.projection) {
644
+ // Missing 'elements' already reported by csnRefs.
645
+ delete art.projection;
646
+ }
647
+ });
642
648
 
643
649
  const newCsn = translateAssocsToJoinsCSN(csn, options);
644
650
 
@@ -712,9 +718,15 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
712
718
 
713
719
  // If 'elem' has a default that is an enum constant, replace that by its value. Complain
714
720
  // if not found or not an enum type,
721
+ // usually done by the Core Compiler, but backend might be called directly
715
722
  function replaceEnumSymbolsByValues(elem, path) {
716
723
  // (190 a) Replace enum symbols by their value (if found)
717
- if (elem.default && elem.default['#']) {
724
+ if (!elem.default?.['#'])
725
+ return;
726
+ if (elem.default.val !== undefined) {
727
+ delete elem.default['#'];
728
+ }
729
+ else {
718
730
  let Enum = elem.enum;
719
731
  if (!Enum && !isBuiltinType(elem.type)) {
720
732
  const typeDef = csnUtils.getCsnDef(elem.type);
@@ -755,10 +767,10 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
755
767
  /**
756
768
  * Check that required actual parameters on 'node.type' are set, that their values are in the correct range etc.
757
769
 
758
- * @param {*} node
759
- * @param {*} nodeName
760
- * @param {*} model
761
- * @param {*} path
770
+ * @param {*} node
771
+ * @param {*} nodeName
772
+ * @param {*} model
773
+ * @param {*} path
762
774
  */
763
775
  function checkTypeParameters(node, artifact, path) {
764
776
  if (node.type && !node.virtual) {
@@ -8,21 +8,31 @@ const { transformAnnotationExpression, implicitAs, } = require('../../model/csnU
8
8
  * key declared in the scope, we replace it with referencing the foreign key itself. If a reference is a $self reference,
9
9
  * we do nothing and if a ref points to a structure/managed association, an error is thrown
10
10
  *
11
- * @param {Array[]} generatedForeignKeys
11
+ * @param {Array[object]||Array[]} generatedForeignKeys
12
12
  * @param {object} csnUtils
13
13
  * @param {object} messageFunctions
14
- * @param {CSN.Path} elementPath
15
14
  */
16
15
  function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, elementPath) {
17
- const reportedErrorsForAnnoPath = {};
18
- generatedForeignKeys.forEach((gfk, index) => {
19
- Object.entries(gfk[1]).forEach(([key, value]) => {
20
- if (key[0] !== '@') return;
21
-
22
- transformAnnotationExpression(gfk[1], key, {
23
- ref: (_parent, _prop, ref, path, _p, _ppn, ctx) => {
24
- if (ref[0] !== '$self') {
25
- const { art } = csnUtils.inspectRef(elementPath ? path : getOriginatingKeyPath(gfk, path)); // OData uses getOriginatingKeyPath - as it relies on $path
16
+ if(Array.isArray(generatedForeignKeys && generatedForeignKeys[0])) {
17
+ // ensure we are always called with an array of objects. TODO: Cleanup fk creation in for.effective to create array of objects
18
+ adaptAnnotationsRefs(remapToArrayOfObjects(generatedForeignKeys), csnUtils, { error }, elementPath);
19
+ } else {
20
+ const reportedErrorsForAnnoPath = {};
21
+ generatedForeignKeys.forEach((gfk, index) => {
22
+ Object.entries(gfk.foreignKey).forEach(([key, value]) => {
23
+ if (key[0] !== '@') return;
24
+
25
+ transformAnnotationExpression(gfk.foreignKey, key, {
26
+ ref: (_parent, _prop, ref, path, _p, _ppn, ctx) => {
27
+ // if the reference is a $self reference, we do nothing,
28
+ // as this is the way to tell that we do not reference the foreign key
29
+ if (ref[0] === '$self') return;
30
+ // if annotation was not propagated from the keys array during foreign keys creation,
31
+ // means that it is not a candidate for foreign key substitution
32
+ if (gfk.keyAnnotations !== null && !gfk.keyAnnotations.includes(key)) return;
33
+
34
+ const art = gfk.originalKey._art ||
35
+ csnUtils.inspectRef(elementPath ? path : getOriginatingKeyPath(gfk, path)).art; // OData uses getOriginatingKeyPath - as it relies on $path
26
36
  if (csnUtils.isManagedAssociation(art)) {
27
37
  if (!reportedErrorsForAnnoPath[path]) {
28
38
  error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
@@ -31,15 +41,15 @@ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, element
31
41
  } else {
32
42
  const gfkForRef = findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref);
33
43
  if (gfkForRef.length === 1) {
34
- ref[0] = gfkForRef[0][0];
35
-
44
+ ref[0] = gfkForRef[0].prefix;
45
+
36
46
  if (ctx?.annoExpr?.['=']) {
37
47
  ctx.annoExpr['='] = true;
38
48
  }
39
49
  } else {
40
50
  // check if the annotation reference points to a structure that has been expanded,
41
51
  // if so -> report an error
42
- const foundInOriginalRef = findOriginalRef(generatedForeignKeys.filter(gfk => gfk[2].$originalKeyRef), ref);
52
+ const foundInOriginalRef = findOriginalRef(generatedForeignKeys.filter(gfk => gfk.originalKey.$originalKeyRef), ref);
43
53
  // references to expanded structures in flat mode will be found in the $originalKeyRef
44
54
  // and in structured mode more than one match will be found in the generated foreign keys
45
55
  if ((foundInOriginalRef.length || gfkForRef.length > 1) && !reportedErrorsForAnnoPath[path]) {
@@ -49,15 +59,15 @@ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, element
49
59
  }
50
60
  }
51
61
  }
52
- }
53
- }, elementPath ? elementPath.concat(['keys', index]) : value?.$path?.slice(0, value.$path.length - 1)); // OData uses $path
62
+ }, elementPath ? elementPath.concat(['keys', index]) : value?.$path?.slice(0, value.$path.length - 1)); // OData uses $path
63
+ });
54
64
  });
55
- });
65
+ }
56
66
 
57
67
  // During tuple expansion, the key ref object looses the $path, therefore
58
68
  // it needs to be extracted from the anno path
59
69
  function getOriginatingKeyPath(gfk, path) {
60
- return gfk[2].$path || path.slice(0, path.findIndex(ps => ps[0] === '@'));
70
+ return gfk.originalKey.$path || path.slice(0, path.findIndex(ps => ps[0] === '@'));
61
71
  }
62
72
 
63
73
  // Loops through the generated foreign keys for this entity
@@ -65,15 +75,21 @@ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, element
65
75
  // key ref. In case there are more than one foreign keys found,
66
76
  // that means the key ref points to a structured element/managed assoc
67
77
  function findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref) {
68
- return generatedForeignKeys.filter(gfk => (ref.join() === (gfk[2].as || implicitAs(gfk[2].ref))));
78
+ return generatedForeignKeys.filter(gfk => (ref.join() === (gfk.originalKey.as || implicitAs(gfk.originalKey.ref))));
69
79
  }
70
80
 
71
81
  // Tuple expansion is performed before the generation of the foreign keys and the original(unexpanded) key ref
72
82
  // is stored in the property $originalKeyRef. Here we try to evaluate whether the reference in the annotation
73
83
  // points to a structure that has been expanded.
74
84
  function findOriginalRef(generatedForeignKeys, ref) {
75
- return generatedForeignKeys.filter(gfk => (ref.join() === (gfk[2].$originalKeyRef.as || implicitAs(gfk[2].$originalKeyRef.ref))));
85
+ return generatedForeignKeys.filter(gfk => (ref.join() === (gfk.originalKey.$originalKeyRef.as || implicitAs(gfk.originalKey.$originalKeyRef.ref))));
76
86
  }
77
87
  }
78
88
 
79
- module.exports = adaptAnnotationsRefs;
89
+ function remapToArrayOfObjects(generatedForeignKeys) {
90
+ return generatedForeignKeys.map(([ prefix, foreignKey, originalKey ]) => {
91
+ return { prefix, foreignKey, originalKey, keyAnnotations: null };
92
+ });
93
+ }
94
+
95
+ module.exports = { adaptAnnotationsRefs };
@@ -2,9 +2,9 @@
2
2
 
3
3
  const { isBuiltinType } = require('../../base/builtins');
4
4
  const { setProp } = require('../../base/model');
5
- const { applyTransformations, implicitAs, findAnnotationExpression, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
5
+ const { applyTransformations, implicitAs, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
6
6
  const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
7
- const adaptAnnotationsRefs = require('./adaptAnnotationRefs');
7
+ const { adaptAnnotationsRefs } = require('./adaptAnnotationRefs');
8
8
 
9
9
  function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iterateOptions = {}) {
10
10
 
@@ -28,20 +28,20 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
28
28
  orderedElements.push([ elementName, element ]);
29
29
  if (!csnUtils.isManagedAssociation(element)) return;
30
30
  const elementPath = path.concat(prop, elementName);
31
- const generatedForeignKeys = createForeignKeysForElement(csnUtils, elementPath, element, elementName, csn, options, '_');
31
+ const generatedForeignKeys = createForeignKeysForElement(elementPath, element, elementName, csn, options, '_');
32
32
 
33
33
  // Second, finalize the generated FK elements
34
34
  const refCount = generatedForeignKeys.reduce((acc, fk) => {
35
35
  // count duplicates
36
- if (acc[fk[0]])
37
- acc[fk[0]]++;
36
+ if (acc[fk.prefix])
37
+ acc[fk.prefix]++;
38
38
  else
39
- acc[fk[0]] = 1;
39
+ acc[fk.prefix] = 1;
40
40
 
41
41
  // check for name clash with existing elements
42
- if (parent[prop][fk[0]] && isDeepEqual(element, parent[prop][fk[0]], true)) {
42
+ if (parent[prop][fk.prefix] && isDeepEqual(element, parent[prop][fk.prefix], true)) {
43
43
  // error location is the colliding element
44
- error('name-duplicate-element', elementPath, { '#': 'flatten-fkey-exists', name: fk[0], art: elementName });
44
+ error('name-duplicate-element', elementPath, { '#': 'flatten-fkey-exists', name: fk.prefix, art: elementName });
45
45
  }
46
46
  // attach a proper $path
47
47
  setProp(element, '$path', elementPath);
@@ -50,7 +50,7 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
50
50
 
51
51
  // set default for single foreign key from association (if available)
52
52
  if (element.default?.val !== undefined && generatedForeignKeys.length === 1)
53
- generatedForeignKeys[0][1].default = element.default;
53
+ generatedForeignKeys[0].foreignKey.default = element.default;
54
54
 
55
55
  // check for duplicate foreign keys
56
56
  Object.entries(refCount).forEach(([ name, occ ]) => {
@@ -63,8 +63,8 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
63
63
  }
64
64
 
65
65
  adaptAnnotationsRefs(generatedForeignKeys, csnUtils, messageFunctions);
66
- setProp(element, '$generatedForeignKeys', generatedForeignKeys.map(gfk => gfk[0]));
67
- orderedElements.push(...generatedForeignKeys);
66
+ setProp(element, '$generatedForeignKeys', generatedForeignKeys.map(gfk => gfk.prefix));
67
+ orderedElements.push(...generatedForeignKeys.map(gfk => [ gfk.prefix, gfk.foreignKey ]));
68
68
 
69
69
  });
70
70
 
@@ -74,123 +74,127 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
74
74
  }, Object.create(null));
75
75
  }
76
76
 
77
- function createForeignKeysForElement(csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0, originalkey = {} ) {
78
- const special$self = !csn?.definitions?.$self && '$self';
79
- const isInspectRefResult = !Array.isArray(path);
77
+ function createForeignKeysForElement(path, element, prefix, csn, options, pathDelimiter, lvl = 0, originalKey = {} ) {
78
+ const special$self = !csn?.definitions?.$self && '$self';
79
+ const isInspectRefResult = !Array.isArray(path);
80
80
 
81
- let fks = [];
82
- if (!element)
83
- return fks;
81
+ let fks = [];
82
+ if (!element)
83
+ return fks;
84
84
 
85
- let finalElement = element;
86
- let finalTypeName;
85
+ let finalElement = element;
86
+ let finalTypeName;
87
87
 
88
- // resolve derived type
89
- if (element.type && !isBuiltinType(element.type) && element.type !== special$self) {
90
- const tmpElt = csnUtils.effectiveType(element);
91
- // effective type resolves to structs and enums only but not scalars
92
- if (Object.keys(tmpElt).length) {
93
- finalElement = tmpElt;
94
- finalTypeName = finalElement.$path[1];
95
- }
96
- else {
97
- // unwind a derived type chain to a scalar type
98
- while (finalElement?.type && !isBuiltinType(finalElement?.type)) {
99
- finalTypeName = finalElement.type;
100
- finalElement = csn.definitions[finalElement.type];
88
+ // resolve derived type
89
+ if (element.type && !isBuiltinType(element.type) && element.type !== special$self) {
90
+ const tmpElt = csnUtils.effectiveType(element);
91
+ // effective type resolves to structs and enums only but not scalars
92
+ if (Object.keys(tmpElt).length) {
93
+ finalElement = tmpElt;
94
+ finalTypeName = finalElement.$path[1];
95
+ }
96
+ else {
97
+ // unwind a derived type chain to a scalar type
98
+ while (finalElement?.type && !isBuiltinType(finalElement?.type)) {
99
+ finalTypeName = finalElement.type;
100
+ finalElement = csn.definitions[finalElement.type];
101
+ }
101
102
  }
102
103
  }
103
- }
104
104
 
105
- if (!finalElement)
106
- return [];
107
-
108
- // main if for this function
109
- // the element is a managed association
110
- if (csnUtils.isManagedAssociation(finalElement)) {
111
- finalElement.keys.forEach((key, keyIndex) => {
112
- const continuePath = getContinuePath([ 'keys', keyIndex ]);
113
- const alias = key.as || implicitAs(key.ref);
114
- const result = csnUtils.inspectRef(continuePath);
115
- let gfks = createForeignKeysForElement(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1,
116
- lvl === 0 ? { ref: key.ref, as: key.as, $path: key.$path, $originalKeyRef: key.$originalKeyRef } : originalkey);
105
+ if (!finalElement)
106
+ return [];
107
+
108
+ // main if for this function
109
+ // the element is a managed association
110
+ if (csnUtils.isManagedAssociation(finalElement)) {
111
+ finalElement.keys.forEach((key, keyIndex) => {
112
+ const continuePath = getContinuePath([ 'keys', keyIndex ]);
113
+ const alias = key.as || implicitAs(key.ref);
114
+ const result = csnUtils.inspectRef(continuePath);
115
+ let gfks = createForeignKeysForElement(result, result.art, alias, csn, options, pathDelimiter, lvl + 1,
116
+ lvl === 0 ? key : originalKey);
117
+ if (lvl === 0) {
118
+ gfks.forEach(gfk => gfk.keyAnnotations.push( ...copyAnnotations(key, gfk.foreignKey)));
119
+ Object.keys(key).forEach( prop => {
120
+ // once applied -> remove the annotations from the keys array, to keep the OData CSN size as small as possible
121
+ if (prop[0] === '@')
122
+ delete key[prop]
123
+ });
124
+ }
125
+ fks = fks.concat(gfks);
126
+ });
127
+ }
128
+ // the element is a structure
129
+ else if (finalElement.elements) {
130
+ Object.entries(finalElement.elements).forEach(([ elemName, elem ]) => {
131
+ // Skip already produced foreign keys
132
+ if (!elem['@odata.foreignKey4']) {
133
+ const continuePath = getContinuePath([ 'elements', elemName ]);
134
+ fks = fks.concat(createForeignKeysForElement(continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1, originalKey));
135
+ }
136
+ });
137
+ }
138
+ // we have reached a leaf element, create a foreign key
139
+ else if ((finalElement.type == null || isBuiltinType(finalElement.type)) && !finalElement.on) {
140
+ const newFk = Object.create(null);
141
+ [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`) ].forEach((prop) => {
142
+ // copy props from original element to preserve derived types!
143
+ if (element[prop] !== undefined)
144
+ newFk[prop] = element[prop];
145
+ });
146
+ return [ { prefix, foreignKey: newFk, originalKey, keyAnnotations: [] } ];
147
+ }
148
+
149
+ fks.forEach((fk) => {
150
+ // prepend current prefix
151
+ fk.prefix = `${prefix}${pathDelimiter}${fk.prefix}`;
152
+ // if this is the entry association, decorate the final foreign keys with the association props/annos
117
153
  if (lvl === 0) {
118
- gfks.forEach(gfk => copyAnnotations(key, gfk[1]));
119
- // once applied -> remove the annotations from the keys array, to keep the OData CSN size as small as possible
120
- Object.keys(key).forEach( prop => {
121
- if (prop[0] === '@') delete key[prop]
122
- });
123
- }
124
- fks = fks.concat(gfks);
125
- });
126
- }
127
- // the element is a structure
128
- else if (finalElement.elements) {
129
- Object.entries(finalElement.elements).forEach(([ elemName, elem ]) => {
130
- // Skip already produced foreign keys
131
- if (!elem['@odata.foreignKey4']) {
132
- const continuePath = getContinuePath([ 'elements', elemName ]);
133
- fks = fks.concat(createForeignKeysForElement(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1, originalkey));
154
+ fk.foreignKey['@odata.foreignKey4'] = prefix;
155
+
156
+ const fkPath = path.slice(0, path.length - 1);
157
+ fkPath.push(fk.prefix);
158
+ setProp(fk.foreignKey, '$path', fkPath);
159
+
160
+ const allowedOverwriteAnnotationNames = ['@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`)];
161
+ const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !allowedOverwriteAnnotationNames.includes(pn));
162
+ copyAnnotations(element, fk.foreignKey, false, {}, validAnnoNames);
163
+ const overwriteAnnoNames = Object.keys(element).filter(pn => allowedOverwriteAnnotationNames.includes(pn));
164
+ copyAnnotations(element, fk.foreignKey, true, {}, overwriteAnnoNames);
165
+
166
+ // propagate not null to final foreign key
167
+ for (const prop of [ 'notNull', 'key' ]) {
168
+ if (element[prop] !== undefined)
169
+ fk.foreignKey[prop] = element[prop];
170
+ }
171
+ if (element.$location)
172
+ setProp(fk.foreignKey, '$location', element.$location);
134
173
  }
135
174
  });
136
- }
137
- // we have reached a leaf element, create a foreign key
138
- else if ((finalElement.type == null || isBuiltinType(finalElement.type)) && !finalElement.on) {
139
- const newFk = Object.create(null);
140
- [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`) ].forEach((prop) => {
141
- // copy props from original element to preserve derived types!
142
- if (element[prop] !== undefined)
143
- newFk[prop] = element[prop];
144
- });
145
- return [ [ prefix, newFk, originalkey ] ];
146
- }
175
+ return fks;
147
176
 
148
- fks.forEach((fk) => {
149
- // prepend current prefix
150
- fk[0] = `${prefix}${pathDelimiter}${fk[0]}`;
151
- // if this is the entry association, decorate the final foreign keys with the association props/annos
152
- if (lvl === 0) {
153
- if (options.transformation !== 'effective')
154
- fk[1]['@odata.foreignKey4'] = prefix;
155
-
156
- const allowedOverwriteAnnotationNames = ['@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`)];
157
- const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !allowedOverwriteAnnotationNames.includes(pn) && !findAnnotationExpression(element, pn));
158
- copyAnnotations(element, fk[1], false, {}, validAnnoNames);
159
- const overwriteAnnoNames = Object.keys(element).filter(pn => allowedOverwriteAnnotationNames.includes(pn) && !findAnnotationExpression(element, pn));
160
- copyAnnotations(element, fk[1], true, {}, overwriteAnnoNames);
161
-
162
- // propagate not null to final foreign key
163
- for (const prop of [ 'notNull', 'key' ]) {
164
- if (element[prop] !== undefined)
165
- fk[1][prop] = element[prop];
166
- }
167
- if (element.$location)
168
- setProp(fk[1], '$location', element.$location);
177
+ /**
178
+ * Get the path to continue resolving references
179
+ *
180
+ * If we are currently inside of a type, we need to start our path fresh from that given type.
181
+ * Otherwise, we would try to resolve .elements on a thing that does not exist.
182
+ *
183
+ * We also respect if we have a previous inspectRef result as our base.
184
+ *
185
+ * @param {Array} additions
186
+ * @returns {CSN.Path}
187
+ */
188
+ function getContinuePath( additions ) {
189
+ if (csn.definitions[finalElement.type])
190
+ return [ 'definitions', finalElement.type, ...additions ];
191
+ else if (finalTypeName)
192
+ return [ 'definitions', finalTypeName, ...additions ];
193
+ else if (isInspectRefResult)
194
+ return [ path, ...additions ];
195
+ return [ ...path, ...additions ];
169
196
  }
170
- });
171
- return fks;
172
-
173
- /**
174
- * Get the path to continue resolving references
175
- *
176
- * If we are currently inside of a type, we need to start our path fresh from that given type.
177
- * Otherwise, we would try to resolve .elements on a thing that does not exist.
178
- *
179
- * We also respect if we have a previous inspectRef result as our base.
180
- *
181
- * @param {Array} additions
182
- * @returns {CSN.Path}
183
- */
184
- function getContinuePath( additions ) {
185
- if (csn.definitions[finalElement.type])
186
- return [ 'definitions', finalElement.type, ...additions ];
187
- else if (finalTypeName)
188
- return [ 'definitions', finalTypeName, ...additions ];
189
- else if (isInspectRefResult)
190
- return [ path, ...additions ];
191
- return [ ...path, ...additions ];
192
197
  }
193
198
  }
194
- }
195
199
 
196
200
  module.exports = createForeignKeyElements;
@@ -14,7 +14,7 @@ const { forEach } = require('../../utils/objectUtils');
14
14
  const { assignAnnotation } = require('../../edm/edmUtils');
15
15
 
16
16
  function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTypeInfo, isExternalServiceMember, error, csnUtils, options) {
17
- const allMgdAssocDefs = [];
17
+ const allMgdAssocDefs = [];
18
18
  forEachDefinition(csn, (def, defName) => {
19
19
  if (def.kind === 'entity' && !isExternalServiceMember(def, defName)) {
20
20
  ['elements', 'params'].forEach(dictName => {
@@ -345,7 +345,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
345
345
  // try to find rightmost 'items', terminate if association comes first.
346
346
  let i = links.length-1;
347
347
  const getProp = (propName) => links[i].art?.[propName];
348
-
348
+
349
349
  let hasItems = false;
350
350
  for(; i >= 0 && !getProp('target') && !hasItems; i--) {
351
351
  const art = links[i].art;
@@ -361,7 +361,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
361
361
  name: refCheck.eltLocationStr,
362
362
  '#': 'flatten_builtin'
363
363
  });
364
- }
364
+ }
365
365
  }
366
366
  }
367
367
  }
@@ -467,7 +467,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
467
467
  let comeFromSameDef = flatElt.$defPath.slice(0, flatElt.$defPath.length - 1).join('.') === se[1].$defPath.slice(0, se[1].$defPath.length - 1).join('.');
468
468
  return (comeFromSameDef && (se[1]['@odata.foreignKey4'] && se[1]['@odata.foreignKey4'] === structuredAssocName) && se[0].startsWith(flatEltName))
469
469
  });
470
-
470
+
471
471
  generatedForeignKeysForAssoc.forEach(gfk => gfk[1]['@odata.foreignKey4'] = flatEltName);
472
472
  // reassign the generated foreign keys for current assoc in order to assign
473
473
  // correct values for $generatedFieldName later on during flattenManagedAssocsAsKeys();
@@ -565,7 +565,7 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
565
565
  if (insideKeys(path))
566
566
  setProp(parent, '_art', art);
567
567
  const lastRef = ref[ref.length - 1];
568
- const fn = (suspend = false, suspendPos = 0,
568
+ const fn = (suspend = false, suspendPos = 0,
569
569
  refFilter = (_parent) => true) => {
570
570
  if (refFilter(parent)) {
571
571
  const scopedPath = [ ...parent.$path ];
@@ -573,9 +573,11 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
573
573
  // full path into target, uncomment this line and
574
574
  // comment/remove setProp in expansion.js
575
575
  // setProp(parent, '$structRef', parent.ref);
576
+ const flattenParameters = true; // structured parameters are flattened
576
577
  const [ newRef, refChanged ] = flattenStructStepsInRef(ref,
577
578
  scopedPath, links, scope, resolvedLinkTypes,
578
- suspend, suspendPos, parent.$bparam);
579
+ suspend, suspendPos, parent.$bparam,
580
+ flattenParameters);
579
581
  parent.ref = newRef;
580
582
  resolved.set(parent, { links, art, scope });
581
583
  // Explicitly set implicit alias for things that are now flattened - but only in columns
@@ -628,7 +630,7 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
628
630
  annoExpr['='] = true;
629
631
  });
630
632
  }
631
- else
633
+ else
632
634
  adaptRefs.push(fn);
633
635
  },
634
636
  }
@@ -679,7 +681,7 @@ function replaceManagedAssocsAsKeys(allFlatManagedAssocDefinitions, csnUtils) {
679
681
  return done;
680
682
  }
681
683
  });
682
-
684
+
683
685
  function cloneAndExtendRef(keyAssocKey, key) {
684
686
  let newKey = { ref: [`${key.ref.join('_')}_${keyAssocKey.as || keyAssocKey.ref.join('_')}`] };
685
687
  setProp(newKey, '_art', keyAssocKey._art);