@sap/cds-compiler 5.7.4 → 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 (71) hide show
  1. package/CHANGELOG.md +54 -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/message-registry.js +55 -20
  7. package/lib/base/messages.js +5 -2
  8. package/lib/base/model.js +4 -1
  9. package/lib/checks/assocOutsideService.js +40 -0
  10. package/lib/checks/featureFlags.js +4 -1
  11. package/lib/checks/types.js +7 -4
  12. package/lib/checks/validator.js +3 -0
  13. package/lib/compiler/assert-consistency.js +11 -5
  14. package/lib/compiler/checks.js +79 -17
  15. package/lib/compiler/define.js +57 -3
  16. package/lib/compiler/extend.js +1 -2
  17. package/lib/compiler/generate.js +1 -1
  18. package/lib/compiler/populate.js +17 -6
  19. package/lib/compiler/propagator.js +1 -1
  20. package/lib/compiler/resolve.js +181 -150
  21. package/lib/compiler/shared.js +276 -22
  22. package/lib/compiler/tweak-assocs.js +15 -4
  23. package/lib/compiler/xpr-rewrite.js +76 -50
  24. package/lib/edm/annotations/edmJson.js +1 -1
  25. package/lib/edm/annotations/genericTranslation.js +2 -2
  26. package/lib/edm/csn2edm.js +2 -2
  27. package/lib/edm/edmPreprocessor.js +15 -9
  28. package/lib/edm/edmUtils.js +12 -5
  29. package/lib/gen/CdlGrammar.checksum +1 -0
  30. package/lib/gen/CdlParser.js +2234 -2233
  31. package/lib/gen/Dictionary.json +55 -8
  32. package/lib/json/from-csn.js +37 -17
  33. package/lib/json/to-csn.js +4 -0
  34. package/lib/language/genericAntlrParser.js +7 -0
  35. package/lib/main.d.ts +5 -0
  36. package/lib/model/cloneCsn.js +1 -0
  37. package/lib/model/csnRefs.js +1 -0
  38. package/lib/model/csnUtils.js +0 -5
  39. package/lib/modelCompare/utils/filter.js +2 -2
  40. package/lib/optionProcessor.js +2 -0
  41. package/lib/parsers/AstBuildingParser.js +47 -17
  42. package/lib/parsers/CdlGrammar.g4 +10 -12
  43. package/lib/parsers/XprTree.js +206 -0
  44. package/lib/render/toCdl.js +61 -89
  45. package/lib/render/toSql.js +59 -29
  46. package/lib/render/utils/standardDatabaseFunctions.js +252 -15
  47. package/lib/transform/addTenantFields.js +9 -3
  48. package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
  49. package/lib/transform/db/assocsToQueries/utils.js +10 -3
  50. package/lib/transform/db/expansion.js +3 -1
  51. package/lib/transform/db/flattening.js +7 -3
  52. package/lib/transform/db/killAnnotations.js +1 -0
  53. package/lib/transform/db/processSqlServices.js +70 -17
  54. package/lib/transform/draft/db.js +8 -3
  55. package/lib/transform/draft/odata.js +27 -4
  56. package/lib/transform/effective/main.js +37 -10
  57. package/lib/transform/effective/misc.js +4 -9
  58. package/lib/transform/effective/service.js +34 -0
  59. package/lib/transform/effective/types.js +28 -17
  60. package/lib/transform/forOdata.js +36 -10
  61. package/lib/transform/forRelationalDB.js +30 -18
  62. package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
  63. package/lib/transform/odata/createForeignKeys.js +120 -116
  64. package/lib/transform/odata/flattening.js +10 -8
  65. package/lib/transform/transformUtils.js +58 -25
  66. package/lib/transform/translateAssocsToJoins.js +10 -6
  67. package/lib/transform/universalCsn/coreComputed.js +5 -1
  68. package/package.json +1 -1
  69. package/share/messages/message-explanations.json +1 -0
  70. package/share/messages/rewrite-not-supported.md +5 -0
  71. package/share/messages/rewrite-undefined-key.md +94 -0
@@ -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);
@@ -4,12 +4,12 @@
4
4
  // different backends.
5
5
  // The sibling of model/transform/TransformUtil.js which works with compacted new CSN.
6
6
 
7
- const { setProp } = require('../base/model');
7
+ const { setProp, isBetaEnabled } = require('../base/model');
8
8
 
9
- const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand, transformAnnotationExpression } = require('../model/csnUtils');
9
+ const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand, isDeepEqual } = require('../model/csnUtils');
10
10
  const { getUtils } = require('../model/csnUtils');
11
11
  const { typeParameters } = require('../compiler/builtins');
12
- const { isBuiltinType, isAnnotationExpression } = require('../base/builtins');
12
+ const { isBuiltinType } = 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');
@@ -44,6 +44,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
44
44
  toFinalBaseType,
45
45
  createExposingProjection,
46
46
  createAndAddDraftAdminDataProjection,
47
+ isValidDraftAdminDataMessagesType,
47
48
  createScalarElement,
48
49
  createAssociationElement,
49
50
  createAssociationPathComparison,
@@ -195,24 +196,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
195
196
  '@odata.Collation': 1,
196
197
  '@odata.Unicode': 1,
197
198
  } : {};
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
- }
199
+
200
+ copyAnnotations(elem, flatElem, false, excludes);
216
201
 
217
202
  // Copy selected type properties
218
203
  const props = ['key', 'virtual', 'masked', 'viaAll'];
@@ -243,15 +228,20 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
243
228
  * @param {bool} [suspend] suspend flattening by caller until association path step
244
229
  * @param {int} [suspendPos] suspend if starting pos is lower or equal to suspendPos and suspend is true
245
230
  * @param {bool} [revokeAtSuspendPos] revoke suspension after suspendPos (binding parameter path use case)
231
+ * @param {bool} [flattenParameters] Whether to flatten references into structured parameters. OData flattens parameters, SQL/for.effective does not.
232
+ *
233
+ * @todo: Refactor to take config object instead of N boolean arguments.
246
234
  * @returns [string[], bool]
247
235
  */
248
- function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), suspend=false, suspendPos=0, revokeAtSuspendPos=false) {
236
+ function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), suspend=false, suspendPos=0, revokeAtSuspendPos=false, flattenParameters = false) {
237
+ // A path is absolute if it starts with $self or a parameter. Then we must not flatten the first path step.
238
+ const pathIsAbsolute = scope === '$self' || (!flattenParameters && scope === 'param');
249
239
  // Refs of length 1 cannot contain steps - no need to check
250
- if (ref.length < 2 || (scope === '$self' && ref.length === 2)) {
240
+ if (ref.length < 2 || (pathIsAbsolute && ref.length === 2)) {
251
241
  return [ ref, false ];
252
242
  }
253
243
 
254
- const result = scope === '$self' ? [ref[0]] : [];
244
+ const result = pathIsAbsolute ? [ref[0]] : [];
255
245
  //let stack = []; // IDs of path steps not yet processed or part of a struct traversal
256
246
  if(!links && !scope) { // calculate JIT if not supplied
257
247
  const res = inspectRef(path);
@@ -262,7 +252,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
262
252
  return [ ref, false ];
263
253
 
264
254
  // Don't process a leading $self - it will a .art with .elements!
265
- let i = scope === '$self' ? 1 : 0;
255
+ let i = pathIsAbsolute ? 1 : 0;
266
256
 
267
257
  // read property from resolved path link
268
258
  const art = (propName) =>
@@ -413,6 +403,11 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
413
403
  if (!draftAdminDataEntity) {
414
404
  draftAdminDataEntity = createAndAddDraftAdminDataEntity();
415
405
  model.definitions['DRAFT.DraftAdministrativeData'] = draftAdminDataEntity;
406
+ if (isBetaEnabled(options, 'draftMessages')
407
+ && options.transformation === 'odata'
408
+ && !model.definitions['DRAFT.DraftAdministrativeData_DraftMessage']) {
409
+ model.definitions['DRAFT.DraftAdministrativeData_DraftMessage'] = createDraftAdminDataMessagesType();
410
+ }
416
411
  if(options.tenantDiscriminator && options.transformation !== 'odata')
417
412
  addTenantFieldToArt(model.definitions['DRAFT.DraftAdministrativeData'], options);
418
413
  }
@@ -481,10 +476,48 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
481
476
  draftIsProcessedByMe.DraftIsProcessedByMe['@Common.Label'] = '{i18n>Draft_DraftIsProcessedByMe}';
482
477
  addElement(draftIsProcessedByMe, artifact, artifactName);
483
478
 
479
+ if (isBetaEnabled(options, 'draftMessages')) {
480
+ const messages = { DraftMessages: { } };
481
+ if (options.transformation === 'odata') {
482
+ messages.DraftMessages = { items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } };
483
+ } else {
484
+ messages.DraftMessages = { type: 'cds.LargeString' };
485
+ }
486
+ messages.DraftMessages['@cds.api.ignore'] = true;
487
+ addElement(messages , artifact, artifactName);
488
+ }
489
+
484
490
  return artifact;
485
491
  }
486
492
  }
487
493
 
494
+ // Create the artificial 'DRAFT.Draf tAdministrativeData_DraftMessage' type
495
+ // for the beta feature 'draftMessages'
496
+ function createDraftAdminDataMessagesType() {
497
+ const messagesType = {
498
+ kind: 'type',
499
+ elements: Object.create(null)
500
+ }
501
+
502
+ addElement(createScalarElement('code', 'cds.String'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
503
+ addElement(createScalarElement('message', 'cds.String'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
504
+ addElement(createScalarElement('target', 'cds.String'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
505
+ addElement({ 'additionalTargets': createScalarElement('items', 'cds.String') }, messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
506
+ addElement(createScalarElement('transition', 'cds.Boolean'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
507
+ addElement(createScalarElement('numericSeverity', 'cds.UInt8'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
508
+ addElement(createScalarElement('longtextUrl', 'cds.String'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
509
+ // the tag element not needed for now, but might be added later on
510
+ // addElement(createScalarElement('tag', 'cds.String'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
511
+ // setAnnotation(messagesType.tag, '@cds.api.ignore', true);
512
+ return messagesType;
513
+ }
514
+
515
+ // Checks if the given definition is a valid 'DRAFT.DraftAdministrativeData_DraftMessage' type
516
+ function isValidDraftAdminDataMessagesType(def) {
517
+ const expectedType = createDraftAdminDataMessagesType();
518
+ return isDeepEqual(def, expectedType, false);
519
+ }
520
+
488
521
  // Create an artificial scalar element 'elemName' with final type 'typeName'.
489
522
  // Make the element a key element if 'isKey' is true.
490
523
  // Add a default value 'defaultVal' if supplied
@@ -50,6 +50,7 @@ function translateAssocsToJoinsCSN(csn, options){
50
50
  // FIXME: Move this somewhere more appropriate
51
51
  const newCsn = compactModel(model, compileOptions);
52
52
  //require('fs').writeFileSync('./csnoutput_a2j.json', JSON.stringify(newCsn, null,2))
53
+ //console.log('CSN returned by A2J:',JSON.stringify(newCsn,null,2))
53
54
  return newCsn;
54
55
  }
55
56
 
@@ -607,6 +608,7 @@ function translateAssocsToJoins(model, inputOptions = {})
607
608
  args: [ constructPathNode( [ srcAlias, prefixFK(assoc.$elementPrefix, assoc.$flatSrcFKs[i]) ] ),
608
609
  constructPathNode( [ tgtAlias, assoc.$flatTgtFKs[i] ] ) ] });
609
610
  }
611
+ // TODO: why inner "parenthesise" - comparison in `and`?
610
612
  return parenthesise((args.length > 1 ? { op: { val: 'and' }, args: [ ...args.map(parenthesise) ] } : args[0] ));
611
613
  }
612
614
  else {
@@ -689,7 +691,8 @@ function translateAssocsToJoins(model, inputOptions = {})
689
691
  // clone ON condition with rewritten paths and substituted backlink conditions
690
692
  function cloneOnCondition(expr)
691
693
  {
692
- if(expr.op?.val === 'xpr')
694
+ const op = expr.op?.val;
695
+ if(op === 'xpr' || op === 'ixpr' || op === 'nary')
693
696
  return cloneOnCondExprStream(expr);
694
697
  else
695
698
  return cloneOnCondExprTree(expr);
@@ -700,7 +703,8 @@ function translateAssocsToJoins(model, inputOptions = {})
700
703
  const result = { op: { val: expr.op.val }, args: [ ] };
701
704
  for(let i = 0; i < args.length; i++)
702
705
  {
703
- if(args[i].op?.val === 'xpr')
706
+ const op = args[i].op?.val;
707
+ if(op === 'xpr' || op === 'ixpr' || op === 'nary')
704
708
  {
705
709
  result.args.push(cloneOnCondition(args[i]));
706
710
  }
@@ -1890,15 +1894,15 @@ function walk(node, env)
1890
1894
  function walkPath(node, env)
1891
1895
  {
1892
1896
  const path = node.path;
1893
- // Ignore paths that that have no artifact (function calls etc) or that are builtins ($now, $user)
1897
+ // Ignore paths that have no artifact (function calls etc.) or that are builtins ($now, $user)
1894
1898
  // or that are parameters ($parameters or escaped paths (':')
1895
1899
  //path.length && path[ path.length-1 ]._artifact
1896
- const art = path && path.length && path[path.length-1]._artifact;
1900
+ const art = path?.length && path[path.length-1]._artifact;
1897
1901
 
1898
1902
  // regardless of the position in the query, ignore paths that have virtual path steps
1899
- if(art && path.some(ps => ps._artifact && ps._artifact.virtual && ps._artifact.virtual.val))
1903
+ if(art && path.some(ps => ps._artifact?.virtual?.val))
1900
1904
  return path;
1901
- if(art && !internalArtifactKinds.includes(art.kind))
1905
+ if(art && !internalArtifactKinds.includes(art.kind) && node.scope !== 'param')
1902
1906
  {
1903
1907
  if(env.callback)
1904
1908
  {
@@ -21,8 +21,12 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
21
21
 
22
22
  forEachDefinition(csn, (artifact, name, prop, path) => {
23
23
  if (artifact.query || artifact.projection) {
24
+ // For events and types, the query is only used for inferring the element signature.
25
+ // There, we don't want to set `@Core.Computed`.
26
+ const queryForSignatureOnly = artifact.kind === 'type' || artifact.kind === 'event';
24
27
  forAllQueries(getNormalizedQuery(artifact).query, (query) => {
25
- if (query.SELECT)
28
+ const isTopLevelQuery = query.SELECT === (artifact.query?.SELECT || artifact.projection);
29
+ if (query.SELECT && (!isTopLevelQuery || !queryForSignatureOnly))
26
30
  traverseQueryAndAttachCoreComputed(query, query.SELECT.elements || artifact.elements);
27
31
  }, path);
28
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "5.7.4",
3
+ "version": "5.8.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)",
@@ -12,6 +12,7 @@
12
12
  "redirected-to-complex",
13
13
  "redirected-to-unrelated",
14
14
  "rewrite-not-supported",
15
+ "rewrite-undefined-key",
15
16
  "syntax-expecting-unsigned-int",
16
17
  "type-missing-enum-value",
17
18
  "type-unexpected-foreign-keys",
@@ -54,3 +54,8 @@ entity View as select from Base {
54
54
  In the corrected view above, the association `secondary` gets an explicit ON
55
55
  condition. For this to work it is necessary to add `secondary_id` to the
56
56
  selection list, that means, we have to explicitly use the foreign key.
57
+
58
+
59
+ ## Related Messages
60
+
61
+ - `rewrite-undefined-key`