@sap/cds-compiler 5.6.0 → 5.7.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 (54) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/bin/cdsse.js +1 -0
  3. package/bin/cdsv2m.js +2 -1
  4. package/doc/Versioning.md +4 -4
  5. package/lib/api/options.js +1 -0
  6. package/lib/base/builtins.js +2 -2
  7. package/lib/base/dictionaries.js +1 -2
  8. package/lib/base/keywords.js +3 -1
  9. package/lib/base/lazyload.js +1 -1
  10. package/lib/base/message-registry.js +169 -144
  11. package/lib/base/messages.js +69 -59
  12. package/lib/base/model.js +3 -3
  13. package/lib/base/node-helpers.js +17 -16
  14. package/lib/base/optionProcessorHelper.js +13 -14
  15. package/lib/base/shuffle.js +4 -1
  16. package/lib/checks/structuredAnnoExpressions.js +1 -1
  17. package/lib/compiler/assert-consistency.js +1 -1
  18. package/lib/compiler/builtins.js +2 -1
  19. package/lib/compiler/extend.js +20 -5
  20. package/lib/compiler/resolve.js +45 -9
  21. package/lib/compiler/shared.js +1 -0
  22. package/lib/edm/annotations/edmJson.js +3 -3
  23. package/lib/edm/annotations/genericTranslation.js +5 -1
  24. package/lib/edm/annotations/vocabularyDefinitions.js +2 -2
  25. package/lib/edm/edmUtils.js +2 -1
  26. package/lib/gen/BaseParser.js +32 -32
  27. package/lib/gen/CdlParser.js +2237 -2196
  28. package/lib/json/from-csn.js +2 -0
  29. package/lib/json/to-csn.js +13 -4
  30. package/lib/language/docCommentParser.js +11 -5
  31. package/lib/language/errorStrategy.js +3 -3
  32. package/lib/language/genericAntlrParser.js +2 -0
  33. package/lib/model/csnUtils.js +6 -1
  34. package/lib/optionProcessor.js +5 -1
  35. package/lib/parsers/AstBuildingParser.js +151 -72
  36. package/lib/parsers/CdlGrammar.g4 +125 -83
  37. package/lib/parsers/Lexer.js +5 -3
  38. package/lib/parsers/index.js +1 -1
  39. package/lib/render/toCdl.js +6 -5
  40. package/lib/render/toHdbcds.js +1 -1
  41. package/lib/render/toSql.js +5 -3
  42. package/lib/render/utils/common.js +19 -6
  43. package/lib/render/utils/standardDatabaseFunctions.js +576 -0
  44. package/lib/transform/addTenantFields.js +2 -1
  45. package/lib/transform/db/flattening.js +18 -77
  46. package/lib/transform/db/groupByOrderBy.js +2 -2
  47. package/lib/transform/db/rewriteCalculatedElements.js +14 -19
  48. package/lib/transform/db/temporal.js +2 -1
  49. package/lib/transform/odata/adaptAnnotationRefs.js +79 -0
  50. package/lib/transform/odata/createForeignKeys.js +4 -71
  51. package/lib/transform/odata/flattening.js +11 -1
  52. package/lib/transform/transformUtils.js +20 -85
  53. package/package.json +2 -1
  54. package/bin/cds_update_annotations.js +0 -180
@@ -3,10 +3,8 @@
3
3
  const {
4
4
  applyTransformations,
5
5
  applyTransformationsOnNonDictionary,
6
- cardinality2str,
7
6
  copyAnnotations,
8
7
  implicitAs,
9
- isDeepEqual,
10
8
  findAnnotationExpression,
11
9
  } = require('../../model/csnUtils');
12
10
  const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
@@ -17,6 +15,7 @@ const { forEach } = require('../../utils/objectUtils');
17
15
  const { transformExpression } = require('./applyTransformations');
18
16
  const { cloneCsnNonDict } = require('../../model/cloneCsn');
19
17
  const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
18
+ const adaptAnnotationsRefs = require('../odata/adaptAnnotationRefs');
20
19
 
21
20
  /**
22
21
  * Strip off leading $self from refs where applicable.
@@ -74,7 +73,6 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
74
73
  }
75
74
  }
76
75
  const { toFinalBaseType, csnUtils } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
77
- const { getServiceName, getFinalTypeInfo } = csnUtils;
78
76
 
79
77
  // We don't want to iterate over actions
80
78
  if (iterateOptions.skipDict && !iterateOptions.skipDict.actions)
@@ -82,14 +80,11 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
82
80
  else
83
81
  iterateOptions.skipDict = { actions: true };
84
82
 
85
- const ignoreOdataKinds = { aspect: 1, event: 1, type: 1 };
86
83
  const replaceWithDummyKinds = { action: 1, function: 1, event: 1 };
87
84
  const stripItems = options.transformation === 'hdbcds' || options.transformation === 'sql';
88
85
  const removeItems = new Set();
89
86
  applyTransformations(csn, {
90
87
  type: (node, prop, type, path, parent, parentProp) => {
91
- if (options.toOdata && node.kind && node.kind in ignoreOdataKinds)
92
- return;
93
88
  if (parentProp === 'cast') {
94
89
  const e = csnUtils.getFinalTypeInfo(type, t => resolved.get(t)?.art || csnUtils.artifactRef(t));
95
90
  if (e.items && stripItems)
@@ -97,7 +92,7 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
97
92
  if (!e || e.items || e.elements)
98
93
  return;
99
94
  }
100
- if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type, path) && !isODataItems(type))) {
95
+ if (!isBuiltinType(type)) {
101
96
  toFinalBaseType(node, resolved, true);
102
97
 
103
98
 
@@ -160,42 +155,6 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
160
155
  delete node.items;
161
156
  }
162
157
  removeItems.clear();
163
-
164
-
165
- /**
166
- * OData V4 only:
167
- * Do not replace a type ref if:
168
- * The type definition is terminating on a scalar type (that can also be a derived type chain)
169
- * AND the typeName (that is the start of that (derived) type chain is defined within the same
170
- * service as the artifact from which the type reference has to be resolved.
171
- *
172
- * @param {string} typeName
173
- * @param {CSN.Path} path
174
- * @returns {boolean}
175
- */
176
- function isODataV4BuiltinFromService( typeName, path ) {
177
- if (!options.toOdata || (options.odataVersion === 'v2') || typeof typeName !== 'string')
178
- return false;
179
-
180
- const typeServiceName = getServiceName(typeName);
181
- const finalBaseType = getFinalTypeInfo(typeName)?.type;
182
- // we need the service of the current definition
183
- const currDefServiceName = getServiceName(path[1]);
184
-
185
- return typeServiceName === currDefServiceName && isBuiltinType(finalBaseType);
186
- }
187
-
188
- /**
189
- * OData stops replacing types @ 'items', if the type ref is a user defined type
190
- * AND that type has items, don't do toFinalBaseType
191
- *
192
- * @param {string} typeName
193
- * @returns {boolean}
194
- */
195
- function isODataItems( typeName ) {
196
- const typeDef = csn.definitions[typeName];
197
- return !!(options.toOdata && typeDef && typeDef?.items);
198
- }
199
158
  }
200
159
 
201
160
  /**
@@ -334,9 +293,6 @@ function flattenElements( csn, options, messageFunctions, pathDelimiter, iterate
334
293
  elements: flatten,
335
294
  };
336
295
 
337
- if (options.toOdata) // Odata needs to flatten the .params as if it was a .elements
338
- transformers.params = flatten;
339
-
340
296
  applyTransformations(csn, transformers, [], iterateOptions);
341
297
 
342
298
  /**
@@ -474,7 +430,7 @@ function getBranches( element, elementName, effectiveType, pathDelimiter ) {
474
430
  * @param {object} iterateOptions
475
431
  */
476
432
  function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFunctions, pathDelimiter, flattenKeyRefs, csnUtils, iterateOptions = {} ) {
477
- const { error, warning } = messageFunctions;
433
+ const { error } = messageFunctions;
478
434
  const { inspectRef, isStructured } = csnUtils;
479
435
  const { flattenStructStepsInRef, flattenStructuredElement } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
480
436
  if (flattenKeyRefs) {
@@ -572,10 +528,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
572
528
  }
573
529
  else if (assoc.keys[i].ref && !assoc.keys[i].as) {
574
530
  setProp(assoc.keys[i], inferredAlias, true);
575
- if (!(options.toOdata && assoc.keys[i].ref.length === 1))
576
- // In OData backend there are no aliases assigned when the same as the ref
577
- // TODO: remove the if after the new flattening in OData has been completed
578
- assoc.keys[i].as = assoc.keys[i].ref[assoc.keys[i].ref.length - 1];
531
+ assoc.keys[i].as = assoc.keys[i].ref[assoc.keys[i].ref.length - 1];
579
532
  collector.push(assoc.keys[i]);
580
533
  }
581
534
  else {
@@ -632,8 +585,6 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
632
585
  const transformers = {
633
586
  elements: createFks,
634
587
  };
635
- if (options.toOdata)
636
- transformers.params = createFks;
637
588
 
638
589
  applyTransformations(csn, transformers, [], Object.assign({ skipIgnore: false }, iterateOptions));
639
590
 
@@ -661,9 +612,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
661
612
  acc[fk[0]] = 1;
662
613
 
663
614
  // check for name clash with existing elements
664
- if ((parent[prop][fk[0]]) &&
665
- ((options.toOdata && isDeepEqual(element, parent[prop][fk[0]], true)) ||
666
- !options.toOdata)) {
615
+ if (parent[prop][fk[0]]) {
667
616
  // error location is the colliding element
668
617
  error('name-duplicate-element', eltPath, { '#': 'flatten-fkey-exists', name: fk[0], art: elementName });
669
618
  }
@@ -690,29 +639,18 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
690
639
  key.$generatedFieldName = fks[i][0];
691
640
  key.ref = [ (key.$ref || key.ref).join(pathDelimiter) ];
692
641
  delete key.$ref;
693
- // TODO: remove the if after the new flattening in OData has been completed
694
- if (options.toOdata && key.as && key.as === key.ref[0])
695
- delete key.as;
642
+ const fk = fks[i][1];
643
+ if (options.transformation === 'effective')
644
+ copyAnnotations(key, fk);
696
645
  }
697
646
  });
698
- // OData specific:
699
- // Not Null sets min cardinality to 1
700
- if (options.toOdata && element.notNull) {
701
- if (element.cardinality === undefined)
702
- element.cardinality = {};
703
- // min=0 is falsy => check for undefined
704
- if (element.cardinality.min === undefined) {
705
- element.cardinality.min = 1;
706
- }
707
- else if (element.cardinality.min === 0) {
708
- warning(null, element.$path, { value: cardinality2str(element, false), code: 'not null' },
709
- 'Expected target cardinality $(VALUE) and $(CODE) to match');
710
- }
711
- }
712
647
 
713
648
  if (options.transformation === 'effective')
714
649
  delete element.default;
715
650
  }
651
+
652
+ if (options.transformation === 'effective')
653
+ adaptAnnotationsRefs(fks, csnUtils, messageFunctions, eltPath);
716
654
  orderedElements.push(...fks);
717
655
  });
718
656
 
@@ -737,9 +675,10 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
737
675
  * @param {object} options
738
676
  * @param {string} pathDelimiter
739
677
  * @param {number} lvl
678
+ * @param {object} originalKey
740
679
  * @returns {Array[]} First element of every sub-array is the foreign key name, second is the foreign key definition
741
680
  */
742
- function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0 ) {
681
+ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0, originalKey = { }) {
743
682
  const special$self = !csn?.definitions?.$self && '$self';
744
683
  const isInspectRefResult = !Array.isArray(path);
745
684
 
@@ -785,7 +724,9 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
785
724
  const continuePath = getContinuePath([ 'keys', keyIndex ]);
786
725
  const alias = key.as || implicitAs(key.ref);
787
726
  const result = csnUtils.inspectRef(continuePath);
788
- fks = fks.concat(createForeignKeys(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1));
727
+ fks = fks.concat(createForeignKeys(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1, lvl === 0 ? {
728
+ ref: key.ref, as: key.as, $path: key.$path, $originalKeyRef: key.$originalKeyRef,
729
+ } : originalKey));
789
730
  });
790
731
  if (!hasKeys)
791
732
  delete finalElement.keys;
@@ -799,7 +740,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
799
740
  // Skip already produced foreign keys
800
741
  if (!elem['@odata.foreignKey4']) {
801
742
  const continuePath = getContinuePath([ 'elements', elemName ]);
802
- fks = fks.concat(createForeignKeys(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
743
+ fks = fks.concat(createForeignKeys(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1, originalKey));
803
744
  }
804
745
  });
805
746
  }
@@ -811,7 +752,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
811
752
  if (element[prop] !== undefined)
812
753
  newFk[prop] = element[prop];
813
754
  });
814
- return [ [ prefix, newFk ] ];
755
+ return [ [ prefix, newFk, originalKey ] ];
815
756
  }
816
757
 
817
758
  fks.forEach((fk) => {
@@ -61,8 +61,8 @@ function replaceAssociationsInGroupByOrderBy( inputQuery, options, inspectRef, e
61
61
  // (230 d) If we keep associations as they are (hdbcds naming convention), we can't have associations in ORDER BY
62
62
  if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
63
63
  error(null, orderByPath,
64
- { $reviewed: true },
65
- 'Unexpected managed association in ORDER BY for naming mode “hdbcds”');
64
+ { $reviewed: true, keyword: 'ORDER BY', value: 'hdbcds' },
65
+ 'Unexpected managed association in $(KEYWORD) for naming mode $(VALUE)');
66
66
  continue;
67
67
  }
68
68
  const pathPrefix = query.orderBy[i].ref.slice(0, -1);
@@ -311,25 +311,20 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
311
311
 
312
312
  const refPrefix = refBase.slice(0, -1);
313
313
  const linksPrefix = linksBase.slice(0, -1);
314
- if (newValue.xpr) {
315
- // We need to adapt the scope of all refs in the new .xpr, as it might have been at a different "root"
316
- applyTransformationsOnNonDictionary(clone, 'value', {
317
- ref: (p, prop, ref) => {
318
- if (ref[0] !== '$self' && ref[0] !== '$projection') {
319
- p.ref = [ ...refPrefix, ...ref ];
320
- if (p._links)
321
- p._links = [ ...linksPrefix, ...p._links ]; // TODO: Make non-enum, increment idx
322
- }
323
- },
324
- }, {
325
- // Do not rewrite refs inside an association-where; avoids endless loop
326
- skipStandard: { where: true },
327
- });
328
- }
329
- else if (clone.value.ref && clone.value.ref[0] !== '$self' && clone.value.ref[0] !== '$projection' ) {
330
- clone.value.ref = [ ...refPrefix, ...clone.value.ref ];
331
- clone.value._links = [ ...linksPrefix, ...clone.value._links ]; // TODO: Make non-enum, increment idx
332
- }
314
+
315
+ // We need to adapt the scope of all refs in the new .xpr, as it might have been at a different "root"
316
+ applyTransformationsOnNonDictionary(clone, 'value', {
317
+ ref: (p, prop, ref) => {
318
+ if (ref[0] !== '$self' && ref[0] !== '$projection') {
319
+ p.ref = [ ...refPrefix, ...ref ];
320
+ if (p._links)
321
+ p._links = [ ...linksPrefix, ...p._links ]; // TODO: Make non-enum, increment idx
322
+ }
323
+ },
324
+ }, {
325
+ // Do not rewrite refs inside an association-where; avoids endless loop
326
+ skipStandard: { where: true },
327
+ });
333
328
  return clone.value;
334
329
  }
335
330
 
@@ -206,7 +206,8 @@ function getAnnotationHandler( csn, options, pathDelimiter, messageFunctions ) {
206
206
  if (validKey.length) {
207
207
  if (!validFrom.length || !validTo.length) {
208
208
  error(null, [ 'definitions', artifactName ],
209
- 'Expecting “@cds.valid.from and “@cds.valid.to if “@cds.valid.key is used');
209
+ { name: '@cds.valid.from', id: '@cds.valid.to', anno: '@cds.valid.key' },
210
+ 'Expecting $(NAME) and $(ID) if $(ANNO) is used');
210
211
  }
211
212
 
212
213
  forEachMember(artifact, (member) => {
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ const { transformAnnotationExpression, implicitAs, } = require('../../model/csnUtils');
4
+
5
+ /**
6
+ * Used during annotating of foreign keys.
7
+ * Expression annotations which are assigned to a foreign key are examined. If some ref point to another foreign
8
+ * key declared in the scope, we replace it with referencing the foreign key itself. If a reference is a $self reference,
9
+ * we do nothing and if a ref points to a structure/managed association, an error is thrown
10
+ *
11
+ * @param {Array[]} generatedForeignKeys
12
+ * @param {object} csnUtils
13
+ * @param {object} messageFunctions
14
+ * @param {CSN.Path} elementPath
15
+ */
16
+ 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
26
+ if (csnUtils.isManagedAssociation(art)) {
27
+ if (!reportedErrorsForAnnoPath[path]) {
28
+ error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
29
+ reportedErrorsForAnnoPath[path] = true;
30
+ }
31
+ } else {
32
+ const gfkForRef = findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref);
33
+ if (gfkForRef.length === 1) {
34
+ ref[0] = gfkForRef[0][0];
35
+
36
+ if (ctx?.annoExpr?.['=']) {
37
+ ctx.annoExpr['='] = true;
38
+ }
39
+ } else {
40
+ // check if the annotation reference points to a structure that has been expanded,
41
+ // if so -> report an error
42
+ const foundInOriginalRef = findOriginalRef(generatedForeignKeys.filter(gfk => gfk[2].$originalKeyRef), ref);
43
+ // references to expanded structures in flat mode will be found in the $originalKeyRef
44
+ // and in structured mode more than one match will be found in the generated foreign keys
45
+ if ((foundInOriginalRef.length || gfkForRef.length > 1) && !reportedErrorsForAnnoPath[path]) {
46
+ error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
47
+ reportedErrorsForAnnoPath[path] = true;
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }, elementPath ? elementPath.concat(['keys', index]) : value?.$path?.slice(0, value.$path.length - 1)); // OData uses $path
54
+ });
55
+ });
56
+
57
+ // During tuple expansion, the key ref object looses the $path, therefore
58
+ // it needs to be extracted from the anno path
59
+ function getOriginatingKeyPath(gfk, path) {
60
+ return gfk[2].$path || path.slice(0, path.findIndex(ps => ps[0] === '@'));
61
+ }
62
+
63
+ // Loops through the generated foreign keys for this entity
64
+ // and filters the ones, which were created for this specific
65
+ // key ref. In case there are more than one foreign keys found,
66
+ // that means the key ref points to a structured element/managed assoc
67
+ function findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref) {
68
+ return generatedForeignKeys.filter(gfk => (ref.join() === (gfk[2].as || implicitAs(gfk[2].ref))));
69
+ }
70
+
71
+ // Tuple expansion is performed before the generation of the foreign keys and the original(unexpanded) key ref
72
+ // is stored in the property $originalKeyRef. Here we try to evaluate whether the reference in the annotation
73
+ // points to a structure that has been expanded.
74
+ function findOriginalRef(generatedForeignKeys, ref) {
75
+ return generatedForeignKeys.filter(gfk => (ref.join() === (gfk[2].$originalKeyRef.as || implicitAs(gfk[2].$originalKeyRef.ref))));
76
+ }
77
+ }
78
+
79
+ module.exports = adaptAnnotationsRefs;
@@ -2,15 +2,16 @@
2
2
 
3
3
  const { isBuiltinType } = require('../../base/builtins');
4
4
  const { setProp } = require('../../base/model');
5
- const { transformAnnotationExpression, applyTransformations, implicitAs, findAnnotationExpression, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
5
+ const { applyTransformations, implicitAs, findAnnotationExpression, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
6
6
  const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
7
+ const adaptAnnotationsRefs = require('./adaptAnnotationRefs');
7
8
 
8
9
  function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iterateOptions = {}) {
9
10
 
10
11
  const { error } = messageFunctions;
11
12
 
12
13
  applyTransformations(csn, { elements: createForeignKeysInCsn, params: createForeignKeysInCsn},
13
- [], Object.assign(iterateOptions, { skip: ['event'], skipStandard: { targetAspect: true } }));
14
+ [], Object.assign(iterateOptions, { skip: ['event'] }));
14
15
 
15
16
  /**
16
17
  * Process a given elements or params dictionary and create foreign key elements.
@@ -61,7 +62,7 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
61
62
  delete element.default;
62
63
  }
63
64
 
64
- adaptAnnotationsRefs(generatedForeignKeys, csnUtils);
65
+ adaptAnnotationsRefs(generatedForeignKeys, csnUtils, messageFunctions);
65
66
  setProp(element, '$generatedForeignKeys', generatedForeignKeys.map(gfk => gfk[0]));
66
67
  orderedElements.push(...generatedForeignKeys);
67
68
 
@@ -190,74 +191,6 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
190
191
  return [ ...path, ...additions ];
191
192
  }
192
193
  }
193
-
194
- function adaptAnnotationsRefs(generatedForeignKeys, csnUtils) {
195
- let reportedErrorsForAnnoPath = {};
196
- generatedForeignKeys.forEach(gfk => {
197
- Object.entries(gfk[1]).forEach(([key, value]) => {
198
- if (key[0] !== '@') return;
199
-
200
- transformAnnotationExpression(gfk[1], key, {
201
- ref: (_parent, _prop, ref, path, _p, _ppn, ctx) => {
202
- if (ref[0] !== '$self') {
203
-
204
- const { art } = csnUtils.inspectRef(getOriginatingKeyPath(gfk, path));
205
- if (csnUtils.isManagedAssociation(art)) {
206
- if (!reportedErrorsForAnnoPath[path]) {
207
- error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
208
- reportedErrorsForAnnoPath[path] = true;
209
- }
210
- } else {
211
- const gfkForRef = findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref);
212
- if (gfkForRef.length === 1) {
213
- ref[0] = gfkForRef[0][0];
214
-
215
- if (ctx?.annoExpr?.['=']) {
216
- ctx.annoExpr['='] = true;
217
- }
218
- } else {
219
- // check if the annotation reference points to a structure that has been expanded,
220
- // if so -> report an error
221
- const foundInOriginalRef = findOriginalRef(generatedForeignKeys.filter(gfk => gfk[2].$originalKeyRef), ref);
222
- // references to expanded structures in flat mode will be found in the $originalKeyRef
223
- // and in strucred mode more than one match will be found in the generated foreign keys
224
- if ((foundInOriginalRef.length || gfkForRef.length > 1) && !reportedErrorsForAnnoPath[path]) {
225
- error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
226
- reportedErrorsForAnnoPath[path] = true;
227
- }
228
- }
229
- }
230
- }
231
- }
232
- }, value?.$path?.slice(0, value.$path.length - 1));
233
- });
234
- });
235
-
236
- // During tuple expansion, the key ref object looses the $path, therefore
237
- // it needs to be extracted from the anno path
238
- function getOriginatingKeyPath(gfk, path) {
239
- return gfk[2].$path || path.slice(0, path.findIndex(ps => ps[0] === '@'));
240
- }
241
-
242
- // Loops through the generated foreign keys for this entity
243
- // and filters the ones, which were created for this specific
244
- // key ref. In case there are more than one foreign keys found,
245
- // that means the key ref points to a structured element/managed assoc
246
- function findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref) {
247
- return generatedForeignKeys.filter(gfk => {
248
- return (ref.join() === (gfk[2].as || implicitAs(gfk[2].ref)));
249
- });
250
- }
251
-
252
- // Tuple expansion is performed before the generation of the foreign keys and the original(unexpanded) key ref
253
- // is stored in the property $originalKeyRef. Here we try to evaluate whether the reference in the annotation
254
- // points to a structure that has been expanded.
255
- function findOriginalRef(generatedForeignKeys, ref) {
256
- return generatedForeignKeys.filter(gfk => {
257
- return (ref.join() === (gfk[2].$originalKeyRef.as || implicitAs(gfk[2].$originalKeyRef.ref)));
258
- });
259
- }
260
- }
261
194
  }
262
195
 
263
196
  module.exports = createForeignKeyElements;
@@ -64,6 +64,15 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
64
64
  allMgdAssocDefs.push(elt);
65
65
  }
66
66
  }, [ flatEltName ], true, { pathWithoutProp: true } );
67
+ } else if (flatElt.targetAspect) {
68
+ forEachMemberRecursively(flatElt.targetAspect, (elt, _eltName, _prop, _path) => {
69
+ // TODO: check whether that needs to be done for targetAspects as well
70
+ // const exprAnnos = Object.keys(elt).filter(pn => pn[0] === '@');
71
+ // flattenAndPrefixExprPaths(elt, exprAnnos, elt.$path, path, 0, true);
72
+ if (csnUtils.isManagedAssociation(elt)) {
73
+ allMgdAssocDefs.push(elt);
74
+ }
75
+ }, [ flatEltName ], true, { pathWithoutProp: true } );
67
76
  }
68
77
  setProp(flatElt, '$pathInStructuredModel', flatElt.$path);
69
78
  setProp(flatElt, '$path', [ ...csnPath, flatEltName ]);
@@ -339,7 +348,8 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTy
339
348
 
340
349
  let hasItems = false;
341
350
  for(; i >= 0 && !getProp('target') && !hasItems; i--) {
342
- hasItems = !!getProp('items')
351
+ const art = links[i].art;
352
+ hasItems = !!getProp('items') || (art.type && !!csnUtils.getFinalTypeInfo(art.type)?.items)
343
353
  }
344
354
  if(!hasItems) {
345
355
  const ft = csnUtils.getFinalTypeInfo(art.type);
@@ -6,10 +6,10 @@
6
6
 
7
7
  const { setProp } = require('../base/model');
8
8
 
9
- const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand } = require('../model/csnUtils');
9
+ const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand, transformAnnotationExpression } = require('../model/csnUtils');
10
10
  const { getUtils } = require('../model/csnUtils');
11
11
  const { typeParameters } = require('../compiler/builtins');
12
- const { isBuiltinType } = require('../base/builtins');
12
+ const { isBuiltinType, isAnnotationExpression } = require('../base/builtins');
13
13
  const { ModelError, CompilerAssertion} = require('../base/error');
14
14
  const { forEach } = require('../utils/objectUtils');
15
15
  const { cloneCsnNonDict, cloneCsnDict } = require('../model/cloneCsn');
@@ -39,11 +39,9 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
39
39
  resolvePath,
40
40
  flattenPath,
41
41
  addDefaultTypeFacets,
42
- getForeignKeyArtifact,
43
42
  flattenStructuredElement,
44
43
  flattenStructStepsInRef,
45
44
  toFinalBaseType,
46
- copyTypeProperties,
47
45
  createExposingProjection,
48
46
  createAndAddDraftAdminDataProjection,
49
47
  createScalarElement,
@@ -105,53 +103,6 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
105
103
  */
106
104
  }
107
105
 
108
- // createRealFK 'really' creates the new foreign key element and is used by the two
109
- // main FK generators getForeignKeyArtifact & createForeignKeyElement
110
- // and (not yet) generateForeignKeyElements.js::generateForeignKeysForRef()
111
- // TODO/FIXME: Can they be combined?
112
- function createRealFK(fkArtifact, assoc, assocName, foreignKey, path, foreignKeyElementName) {
113
- const foreignKeyElement = Object.create(null);
114
-
115
- // Transfer selected type properties from target key element
116
- // FIXME: There is currently no other way but to treat the annotation '@odata.Type' as a type property.
117
- for (const prop of ['type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type']) {
118
- if (fkArtifact[prop] !== undefined) {
119
- foreignKeyElement[prop] = fkArtifact[prop];
120
- }
121
- }
122
-
123
- if (options.transformation === 'odata' || options.transformation === 'effective')
124
- copyAnnotations(assoc, foreignKeyElement, true);
125
-
126
- // If the association is non-fkArtifact resp. key, so should be the foreign key field
127
- for (const prop of ['notNull', 'key']) {
128
- if (assoc[prop] !== undefined) {
129
- foreignKeyElement[prop] = assoc[prop];
130
- }
131
- }
132
-
133
- // Establish the relationship between generated field and association:
134
- // - generated field has annotation '@odata.foreignKey4'.
135
- // - foreign key info has 'generatedFieldName'
136
- if(options.transformation !== 'effective')
137
- foreignKeyElement['@odata.foreignKey4'] = assocName;
138
- foreignKey.$generatedFieldName = foreignKeyElementName;
139
- // attach $path to the newly created element - used for inspectRef in processAssociationOrComposition
140
- setProp(foreignKeyElement, '$path', path);
141
- if (assoc.$location) {
142
- setProp(foreignKeyElement, '$location', assoc.$location);
143
- }
144
- return foreignKeyElement;
145
- }
146
-
147
- function getForeignKeyArtifact(assoc, assocName, foreignKey, path) {
148
- const fkArtifact = inspectRef(path).art;
149
- // FIXME: Duplicate code
150
- // Assemble foreign key element name from assoc name, '_' and foreign key name/alias
151
- const foreignKeyElementName = `${assocName.replace(/\./g, pathDelimiter)}${pathDelimiter}${foreignKey.as || foreignKey.ref.join(pathDelimiter)}`;
152
- return [ foreignKeyElementName, createRealFK(fkArtifact, assoc, assocName, foreignKey, path, foreignKeyElementName) ];
153
- }
154
-
155
106
  // For a structured element 'elem', return a dictionary of flattened elements to
156
107
  // replace it, flattening names with pathDelimiter's value and propagating all annotations and the
157
108
  // type properties 'key', 'notNull', 'virtual', 'masked' to the flattened elements.
@@ -244,7 +195,24 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
244
195
  '@odata.Collation': 1,
245
196
  '@odata.Unicode': 1,
246
197
  } : {};
247
- copyAnnotations(elem, flatElem, false, excludes);
198
+ const annoNames = copyAnnotations(elem, flatElem, false, excludes);
199
+ if(options.transformation === 'effective') {
200
+ for(const annoName of annoNames) {
201
+ if(flatElem.keys && isAnnotationExpression(elem[annoName])) {
202
+ flatElem.keys.forEach(key => {
203
+ if(!key[annoName]) {
204
+ key[annoName] = flatElem[annoName];
205
+ transformAnnotationExpression(key, annoName, {
206
+ ref: (parent, prop, ref) => {
207
+ if(ref[0] !== '$self')
208
+ parent.ref = ['$self', ...parent.ref];
209
+ }
210
+ })
211
+ }
212
+ })
213
+ }
214
+ }
215
+ }
248
216
 
249
217
  // Copy selected type properties
250
218
  const props = ['key', 'virtual', 'masked', 'viaAll'];
@@ -335,39 +303,6 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
335
303
  return [ result, refChanged ];
336
304
  }
337
305
 
338
- /**
339
- * Copy properties of the referenced type, but don't resolve to the final base type.
340
- *
341
- * Do not copy the length if it was just set via the default-value.
342
- *
343
- * @param {any} node Node to copy to
344
- * @returns {void}
345
- */
346
- function copyTypeProperties(node) {
347
- // Nothing to do if no type (or if array/struct type)
348
- if (!node || !node.type) return;
349
- // .. or if it is a ref
350
- if (node.type && node.type.ref) return;
351
- // .. or builtin already
352
- if (node.type && isBuiltinType(node.type)) return;
353
-
354
- // The type might already be a full fledged type def (array of)
355
- const typeDef = typeof node.type === 'string' ? getCsnDef(node.type) : node.type;
356
- // Nothing to do if type is an array or a struct type
357
- if (typeDef.items || typeDef.elements) return;
358
- // if the declared element is an enum, these values are with priority
359
- if (!node.enum && typeDef.enum)
360
- Object.assign(node, { enum: typeDef.enum });
361
- if (!node.length && typeDef.length && !typeDef.$default)
362
- Object.assign(node, { length: typeDef.length });
363
- if (!node.precision && typeDef.precision)
364
- Object.assign(node, { precision: typeDef.precision });
365
- if (!node.scale && typeDef.scale)
366
- Object.assign(node, { scale: typeDef.scale });
367
- if (!node.srid && typeDef.srid)
368
- Object.assign(node, { srid: typeDef.srid });
369
- }
370
-
371
306
  /**
372
307
  * Replace the type of 'nodeWithType' with its final base type, i.e. copy relevant type properties and
373
308
  * set the `type` property to the builtin if scalar or delete it if structured/arrayed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "5.6.0",
3
+ "version": "5.7.0",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",
@@ -22,6 +22,7 @@
22
22
  "test:ci": "node scripts/verifyGrammarChecksum.js && mocha --timeout 10000 --reporter-option maxDiffSize=0 scripts/testLazyLoading.js && mocha --parallel --reporter-option maxDiffSize=0 test/ test3/",
23
23
  "test:piper": "node scripts/verifyGrammarChecksum.js && npm run coverage:piper",
24
24
  "test3": "node scripts/verifyGrammarChecksum.js && mocha --reporter-option maxDiffSize=0 test3/",
25
+ "testStandardDatabaseFunctions": "CDS_COMPILER_STANDARD_DB_FUNCTIONS=1 mocha --reporter-option maxDiffSize=0 test3/test.standard-database-functions.js",
25
26
  "deployHanaSql": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 test3/test.deploy.hana-sql.js",
26
27
  "deployHdiHdbcds": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 test3/test.deploy.hdi.hdbcds.js",
27
28
  "deployHdi": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 --extensions .hdi test3/test.deploy.hdi.hdbcds.js",