@sap/cds-compiler 3.9.4 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +107 -4
  2. package/README.md +0 -1
  3. package/bin/cdsc.js +11 -23
  4. package/bin/cdsse.js +3 -3
  5. package/doc/API.md +5 -0
  6. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  7. package/doc/CHANGELOG_BETA.md +17 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +28 -0
  9. package/lib/api/.eslintrc.json +1 -1
  10. package/lib/api/main.js +55 -9
  11. package/lib/api/options.js +2 -0
  12. package/lib/base/error.js +2 -0
  13. package/lib/base/message-registry.js +143 -64
  14. package/lib/base/messages.js +213 -107
  15. package/lib/base/model.js +11 -11
  16. package/lib/checks/.eslintrc.json +1 -1
  17. package/lib/checks/annotationsOData.js +2 -2
  18. package/lib/checks/elements.js +1 -1
  19. package/lib/checks/enricher.js +26 -3
  20. package/lib/checks/onConditions.js +67 -12
  21. package/lib/checks/queryNoDbArtifacts.js +106 -105
  22. package/lib/checks/sql-snippets.js +2 -0
  23. package/lib/checks/types.js +12 -6
  24. package/lib/checks/validator.js +2 -2
  25. package/lib/compiler/assert-consistency.js +10 -8
  26. package/lib/compiler/builtins.js +8 -2
  27. package/lib/compiler/checks.js +52 -35
  28. package/lib/compiler/define.js +31 -26
  29. package/lib/compiler/extend.js +120 -65
  30. package/lib/compiler/finalize-parse-cdl.js +12 -43
  31. package/lib/compiler/generate.js +16 -5
  32. package/lib/compiler/index.js +8 -5
  33. package/lib/compiler/kick-start.js +4 -3
  34. package/lib/compiler/populate.js +96 -95
  35. package/lib/compiler/propagator.js +7 -8
  36. package/lib/compiler/resolve.js +377 -103
  37. package/lib/compiler/shared.js +794 -517
  38. package/lib/compiler/tweak-assocs.js +8 -6
  39. package/lib/compiler/utils.js +44 -0
  40. package/lib/edm/annotations/genericTranslation.js +41 -5
  41. package/lib/edm/csn2edm.js +34 -32
  42. package/lib/edm/edm.js +34 -31
  43. package/lib/edm/edmAnnoPreprocessor.js +0 -23
  44. package/lib/edm/edmInboundChecks.js +7 -2
  45. package/lib/edm/edmPreprocessor.js +25 -18
  46. package/lib/edm/edmUtils.js +8 -4
  47. package/lib/gen/Dictionary.json +18 -0
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +4 -2
  50. package/lib/gen/languageParser.js +5006 -4582
  51. package/lib/json/from-csn.js +157 -112
  52. package/lib/json/to-csn.js +60 -89
  53. package/lib/language/antlrParser.js +17 -13
  54. package/lib/language/docCommentParser.js +11 -1
  55. package/lib/language/genericAntlrParser.js +13 -10
  56. package/lib/language/language.g4 +168 -97
  57. package/lib/main.d.ts +128 -36
  58. package/lib/main.js +1 -1
  59. package/lib/model/csnRefs.js +24 -5
  60. package/lib/model/csnUtils.js +9 -8
  61. package/lib/model/revealInternalProperties.js +7 -12
  62. package/lib/model/sortViews.js +4 -2
  63. package/lib/modelCompare/compare.js +1 -1
  64. package/lib/modelCompare/utils/filter.js +40 -2
  65. package/lib/optionProcessor.js +0 -3
  66. package/lib/render/toCdl.js +247 -214
  67. package/lib/render/toHdbcds.js +197 -181
  68. package/lib/render/toSql.js +325 -289
  69. package/lib/render/utils/common.js +42 -4
  70. package/lib/render/utils/delta.js +1 -1
  71. package/lib/render/utils/sql.js +3 -3
  72. package/lib/transform/braceExpression.js +2 -2
  73. package/lib/transform/db/.eslintrc.json +1 -1
  74. package/lib/transform/db/applyTransformations.js +3 -3
  75. package/lib/transform/db/associations.js +24 -12
  76. package/lib/transform/db/expansion.js +17 -18
  77. package/lib/transform/db/flattening.js +17 -21
  78. package/lib/transform/db/rewriteCalculatedElements.js +171 -64
  79. package/lib/transform/db/views.js +3 -4
  80. package/lib/transform/draft/db.js +21 -12
  81. package/lib/transform/draft/odata.js +4 -0
  82. package/lib/transform/forOdataNew.js +62 -47
  83. package/lib/transform/forRelationalDB.js +12 -7
  84. package/lib/transform/localized.js +4 -2
  85. package/lib/transform/odata/toFinalBaseType.js +5 -5
  86. package/lib/transform/odata/typesExposure.js +3 -3
  87. package/lib/transform/parseExpr.js +3 -0
  88. package/lib/transform/transformUtilsNew.js +43 -23
  89. package/lib/transform/translateAssocsToJoins.js +7 -6
  90. package/lib/transform/universalCsn/.eslintrc.json +1 -1
  91. package/lib/transform/universalCsn/coreComputed.js +7 -5
  92. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
  93. package/package.json +2 -2
  94. package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
  95. package/share/messages/message-explanations.json +1 -1
@@ -28,7 +28,7 @@ function enrichCsn( csn ) {
28
28
  mixin: dictionary,
29
29
  ref: pathRef,
30
30
  type: simpleRef,
31
- target: simpleRef,
31
+ target,
32
32
  includes: simpleRef,
33
33
  columns,
34
34
  // Annotations are ignored.
@@ -99,11 +99,10 @@ function enrichCsn( csn ) {
99
99
  }
100
100
 
101
101
  // eslint-disable-next-line jsdoc/require-jsdoc
102
- function simpleRef( node, prop ) {
102
+ function simpleRef( node, prop, ref ) {
103
103
  setProp(node, '$path', [ ...csnPath ]);
104
104
  cleanupCallbacks.push(() => delete node.$path);
105
105
 
106
- const ref = node[prop];
107
106
  if (typeof ref === 'string') {
108
107
  const art = artifactRef( ref, null );
109
108
  if (art || !ref.startsWith( 'cds.')) {
@@ -112,9 +111,19 @@ function enrichCsn( csn ) {
112
111
  }
113
112
  }
114
113
  else if (Array.isArray( ref )) {
114
+ // e.g. `includes: [ 'E' ]`, which gets a parallel `_includes`.
115
115
  setProp(node, `_${ prop }`, ref.map( r => artifactRef( r, null ) ));
116
116
  cleanupCallbacks.push(() => delete node[`_${ prop }`]);
117
117
  }
118
+ else if (typeof ref === 'object') {
119
+ // e.g. type refs via `{ type: { ref: [ 'E', 'field' ] } }
120
+ standard(node, prop, ref);
121
+ const art = artifactRef( ref, null );
122
+ if (art) {
123
+ setProp(node, `_${ prop }`, art);
124
+ cleanupCallbacks.push(() => delete node[`_${ prop }`]);
125
+ }
126
+ }
118
127
  }
119
128
 
120
129
  // eslint-disable-next-line jsdoc/require-jsdoc
@@ -152,6 +161,20 @@ function enrichCsn( csn ) {
152
161
  } );
153
162
  csnPath.pop();
154
163
  }
164
+
165
+ /**
166
+ * A target is either an anonymous aspect (with elements, etc.) via gensrc or a reference.
167
+ *
168
+ * @param {object} parent
169
+ * @param {string} prop
170
+ * @param {any} node
171
+ */
172
+ function target( parent, prop, node ) {
173
+ if (node?.elements) // e.g. via gensrc
174
+ standard(parent, prop, node);
175
+ else
176
+ simpleRef(parent, prop, node);
177
+ }
155
178
  }
156
179
 
157
180
  module.exports = enrichCsn;
@@ -74,6 +74,7 @@ function validateOnCondition( member, memberName, property, path ) {
74
74
  const validDollarSelf = otherSideIsValidDollarSelf(member.on, i);
75
75
  const validStructuredElement = otherSideIsExpandableStructure.call(this, member.on, i);
76
76
  for (let j = 0; j < _links.length - 1; j++) {
77
+ let hasPathError = false;
77
78
  const csnPath = path.concat([ 'on', i, 'ref', j ]);
78
79
 
79
80
  // For error messages
@@ -88,25 +89,34 @@ function validateOnCondition( member, memberName, property, path ) {
88
89
  if (stepArt.on) {
89
90
  // It's an unmanaged association - traversal is always forbidden
90
91
  this.error('ref-unexpected-navigation', csnPath, { '#': 'unmanaged', id, elemref });
92
+ hasPathError = true;
91
93
  }
92
94
  else {
93
95
  // It's a managed association - access of the foreign keys is allowed
94
- const nextRef = ref[j + 1].id || ref[j + 1];
95
- if (!stepArt.keys.some(r => r.ref[0] === nextRef)) {
96
+ checkForeignKeyAccess(member.on[i], j, csnPath, (errorIndex) => {
96
97
  this.error('ref-unexpected-navigation', csnPath, {
97
- '#': 'std', id, elemref, name: nextRef,
98
+ '#': 'std', id, elemref, name: ref[errorIndex].id || ref[errorIndex],
98
99
  });
99
- }
100
+ hasPathError = true;
101
+ });
100
102
  }
101
103
  }
102
- if (stepArt.virtual)
103
- this.error(null, csnPath, { id, elemref }, 'Virtual elements can\'t be used in ON-conditions, step $(ID) of path $(ELEMREF)');
104
-
105
- if (ref[j].where)
106
- this.error(null, csnPath, { id, elemref }, 'ON-conditions must not contain filters, step $(ID) of path $(ELEMREF)');
104
+ if (stepArt.virtual) {
105
+ this.error(null, csnPath, { id, elemref }, //
106
+ 'Virtual elements can\'t be used in ON-conditions, step $(ID) of path $(ELEMREF)');
107
+ hasPathError = true;
108
+ }
109
+ if (ref[j].where) {
110
+ this.error('ref-unexpected-filter', csnPath, { '#': 'on-condition', id, elemref });
111
+ hasPathError = true;
112
+ }
113
+ if (ref[j].args) {
114
+ this.error('ref-unexpected-args', csnPath, { '#': 'on-condition', id, elemref });
115
+ hasPathError = true;
116
+ }
107
117
 
108
- if (ref[j].args)
109
- this.error(null, csnPath, { id, elemref }, 'ON-conditions must not contain parameters, step $(ID) of path $(ELEMREF)');
118
+ if (hasPathError)
119
+ break; // avoid too many consequent errors
110
120
  }
111
121
 
112
122
  if (_art && !($scope === '$self' && ref.length === 1)) {
@@ -145,6 +155,51 @@ function validateOnCondition( member, memberName, property, path ) {
145
155
  }
146
156
  }
147
157
 
158
+
159
+ /**
160
+ * Ensure that only foreign keys of the association `parent.ref[refIndex]` are accessed in `parent.ref`.
161
+ * If a non-fk field is accessed, `callback` is invoked.
162
+ *
163
+ * @param {object} parent Object containing `ref` and `_links` from csnRefs.
164
+ * @param {number} refIndex Index of the to-be-checked association in `parent.ref`
165
+ * @param {CSN.Path} csnPath
166
+ * @param {(errorIndex: number) => void} callback Called if there are non-fk path steps. Argument is index in
167
+ * `parent.ref` that is faulty. If a fk-step is missing, `errorIndex` will be `> parent.ref.length`.
168
+ */
169
+ function checkForeignKeyAccess( parent, refIndex, csnPath, callback ) {
170
+ const { ref, _links } = parent;
171
+ const assoc = _links[refIndex].art;
172
+
173
+ const next = ref[refIndex + 1].id || ref[refIndex + 1];
174
+ let possibleKeys = next && assoc.keys.filter(r => r.ref[0] === next);
175
+ if (!possibleKeys || possibleKeys.length === 0) {
176
+ callback(refIndex + 1);
177
+ }
178
+ else {
179
+ // For cases where `Association to T { struct.one, struct.two };` is used instead of `{ struct }`.
180
+ // Note: We know that `{ struct, struct.one }` is not possible, so no prefix check required.
181
+ let fkIndex = 0;
182
+ let success = false;
183
+ while (!success && possibleKeys.length > 0) {
184
+ const pathStep = ref[refIndex + fkIndex + 1].id || ref[refIndex + fkIndex + 1];
185
+
186
+ // Function is immediately executed, before next iteration of loop. Access is fine.
187
+ // eslint-disable-next-line no-loop-func
188
+ possibleKeys = possibleKeys.filter((r) => {
189
+ const result = r.ref[fkIndex] === pathStep;
190
+ if (result && r.ref.length - 1 === fkIndex)
191
+ success = true; // full fk matched
192
+
193
+ return result;
194
+ });
195
+ ++fkIndex;
196
+ }
197
+ if (!success)
198
+ callback(refIndex + fkIndex);
199
+ }
200
+ }
201
+
202
+
148
203
  /**
149
204
  * Run the above validations also for mixins.
150
205
  *
@@ -156,4 +211,4 @@ function validateMixinOnCondition( query, path ) {
156
211
  forEachGeneric( query.SELECT, 'mixin', validateOnCondition.bind(this), path );
157
212
  }
158
213
 
159
- module.exports = { validateOnCondition, validateMixinOnCondition };
214
+ module.exports = { validateOnCondition, validateMixinOnCondition, checkForeignKeyAccess };
@@ -17,121 +17,122 @@ const { hasAnnotationValue, isPersistedOnDatabase, isBuiltinType } = require('..
17
17
  * @param {CSN.Query} query Query to check
18
18
  */
19
19
  function checkQueryForNoDBArtifacts( query ) {
20
- /**
21
- * Count the leaf-elements resulting from a given element.
22
- *
23
- * @param {CSN.Element} def Definition to check
24
- * @returns {number} Number of leaf elements
25
- */
26
- const leafCount = (def) => {
27
- let c = 0;
28
- if (!def)
29
- return c;
30
- if (def.elements) {
31
- c += Object.values(def.elements).reduce((acc, e) => {
32
- acc += leafCount(e);
33
- return acc;
34
- }, 0);
35
- }
36
- else if (def.keys) {
37
- c += def.keys.reduce((acc, e) => {
38
- acc += leafCount(e._art);
39
- return acc;
40
- }, 0);
41
- }
42
- else if (def.type) {
43
- if (isBuiltinType(def.type) && !(def.target))
44
- return 1;
45
- c += leafCount(this.csn.definitions[def.type]);
20
+ if (isPersistedOnDatabase(this.artifact) && !hasAnnotationValue(this.artifact, '@cds.persistence.table')) {
21
+ const generalQueryProperties = [ 'from', 'columns', 'where', 'groupBy', 'orderBy', 'having', 'limit' ];
22
+ for (const prop of generalQueryProperties) {
23
+ const queryPart = (query.SELECT || query.SET)[prop];
24
+ if (Array.isArray(queryPart)) {
25
+ for (const part of queryPart)
26
+ checkRef.call(this, part, prop === 'columns');
27
+ }
28
+ else if (typeof queryPart === 'object') {
29
+ checkRef.call(this, queryPart, prop === 'columns');
30
+ }
46
31
  }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Count the leaf-elements resulting from a given element.
37
+ *
38
+ * @param {CSN.Element} def Definition to check
39
+ * @returns {number} Number of leaf elements
40
+ */
41
+ function leafCount( def ) {
42
+ let c = 0;
43
+ if (!def)
47
44
  return c;
48
- };
49
- /**
50
- * Check the given ref for usage of skipped/abstract assoc targets
51
- *
52
- * @param {object} obj CSN "thing" to check
53
- * @param {boolean} inColumns True if the ref is part of a from
54
- */
55
- const checkRef = (obj, inColumns) => {
56
- if (!(obj && obj.ref) || !obj._links || obj.$scope === 'alias')
57
- return;
45
+ if (def.elements) {
46
+ c += Object.values(def.elements).reduce((acc, e) => {
47
+ acc += leafCount.call(this, e);
48
+ return acc;
49
+ }, 0);
50
+ }
51
+ else if (def.keys) {
52
+ c += def.keys.reduce((acc, e) => {
53
+ acc += leafCount.call(this, e._art);
54
+ return acc;
55
+ }, 0);
56
+ }
57
+ else if (def.type) {
58
+ if (isBuiltinType(def.type) && !(def.target))
59
+ return 1;
60
+ c += leafCount.call(this, this.csn.definitions[def.type]);
61
+ }
62
+ return c;
63
+ }
58
64
 
59
- const links = obj._links;
65
+ /**
66
+ * Check the given ref for usage of skipped/abstract assoc targets
67
+ *
68
+ * @param {object} obj CSN "thing" to check
69
+ * @param {boolean} inColumns True if the ref is part of a from
70
+ */
71
+ function checkRef( obj, inColumns ) {
72
+ if (!(obj && obj.ref) || !obj._links || obj.$scope === 'alias')
73
+ return;
60
74
 
61
- // Don't check the last element - to allow association publishing in columns
62
- for (let i = 0; i < (inColumns ? links.length - 1 : links.length); i++) {
63
- const link = links[i];
64
- if (!link)
65
- continue;
75
+ const links = obj._links;
66
76
 
67
- const { art } = link;
68
- if (!art)
69
- continue;
77
+ // Don't check the last element - to allow association publishing in columns
78
+ for (let i = 0; i < (inColumns ? links.length - 1 : links.length); i++) {
79
+ const link = links[i];
80
+ if (!link)
81
+ continue;
70
82
 
71
- const endArtifact = art.target ? this.csn.definitions[art.target] : art;
72
- const pathStep = obj.ref[i].id ? obj.ref[i].id : obj.ref[i];
73
- const name = art.target ? art.target : pathStep;
74
- if (!isPersistedOnDatabase(endArtifact)) {
75
- const nextElement = obj.ref[i + 1];
76
- /**
77
- * if we only navigate to foreign keys of the managed association in a view, we do not need to join,
78
- * thus we can produce the view even if the target of the association is not persisted
79
- *
80
- * @param {CSN.Element} assoc association in ref
81
- * @param {string} nextStep the ref step following the association
82
- * @returns {boolean} true if no join will be generated
83
- */
84
- const isJoinRelevant = (assoc, nextStep) => {
85
- if (!assoc.keys)
86
- return true;
87
- const isExposedColumnAssocOrComposition = this.csnUtils.isAssocOrComposition(obj._art);
88
- return !assoc.keys
89
- .some(fk => fk.ref[0] === nextStep && !isExposedColumnAssocOrComposition);
90
- };
91
- if (isJoinRelevant(art, nextElement)) {
92
- const cdsPersistenceSkipped = hasAnnotationValue(endArtifact, '@cds.persistence.skip');
93
- this.error( null, obj.$path, {
94
- id: pathStep, elemref: obj, name, '#': cdsPersistenceSkipped ? 'std' : 'abstract',
95
- }, {
96
- std: 'Unexpected “@cds.persistence.skip” annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
97
- abstract: 'Unexpected “abstract” association target $(NAME) of $(ID) in path $(ELEMREF)',
98
- } );
99
- }
100
- }
101
- // check managed association to have foreign keys array filled
102
- if (art.keys && leafCount(art) === 0) {
103
- this.error(null,
104
- obj.$path,
105
- { id: pathStep, elemref: obj },
106
- 'Path step $(ID) of $(ELEMREF) has no foreign keys');
107
- }
83
+ const { art } = link;
84
+ if (!art)
85
+ continue;
108
86
 
109
- if (art.on) {
110
- for (let j = 0; j < art.on.length; j++) {
111
- if (j < art.on.length - 2 && art.on[j].ref && art.on[j + 1] === '=' && art.on[j + 2].ref) {
112
- const [ fwdAssoc, fwdPath ] = getForwardAssociation(pathStep, art.on[j], art.on[j + 2]);
113
- if (fwdAssoc && fwdAssoc.keys && leafCount(fwdAssoc) === 0) {
114
- this.error(null, obj.$path,
115
- { name: pathStep, elemref: obj, id: fwdPath },
116
- 'Path step $(NAME) of $(ELEMREF) is a $self comparison with $(ID) that has no foreign keys');
117
- j += 2;
118
- }
119
- }
120
- }
87
+ const endArtifact = art.target ? this.csn.definitions[art.target] : art;
88
+ const pathStep = obj.ref[i].id ? obj.ref[i].id : obj.ref[i];
89
+ const name = art.target ? art.target : pathStep;
90
+ if (!isPersistedOnDatabase(endArtifact)) {
91
+ const nextElement = obj.ref[i + 1];
92
+ /**
93
+ * if we only navigate to foreign keys of the managed association in a view, we do not need to join,
94
+ * thus we can produce the view even if the target of the association is not persisted
95
+ *
96
+ * @param {CSN.Element} assoc association in ref
97
+ * @param {string} nextStep the ref step following the association
98
+ * @returns {boolean} true if no join will be generated
99
+ */
100
+ const isJoinRelevant = (assoc, nextStep) => {
101
+ if (!assoc.keys)
102
+ return true;
103
+ const isExposedColumnAssocOrComposition = this.csnUtils.isAssocOrComposition(obj._art.type);
104
+ return !assoc.keys
105
+ .some(fk => fk.ref[0] === nextStep && !isExposedColumnAssocOrComposition);
106
+ };
107
+ if (isJoinRelevant(art, nextElement)) {
108
+ const cdsPersistenceSkipped = hasAnnotationValue(endArtifact, '@cds.persistence.skip');
109
+ this.error( null, obj.$path, {
110
+ id: pathStep, elemref: obj, name, '#': cdsPersistenceSkipped ? 'std' : 'abstract',
111
+ }, {
112
+ std: 'Unexpected “@cds.persistence.skip” annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
113
+ abstract: 'Unexpected “abstract” association target $(NAME) of $(ID) in path $(ELEMREF)',
114
+ } );
121
115
  }
122
116
  }
123
- };
117
+ // check managed association to have foreign keys array filled
118
+ if (art.keys && leafCount.call(this, art) === 0) {
119
+ this.error(null,
120
+ obj.$path,
121
+ { id: pathStep, elemref: obj },
122
+ 'Path step $(ID) of $(ELEMREF) has no foreign keys');
123
+ }
124
124
 
125
- if (isPersistedOnDatabase(this.artifact) && !hasAnnotationValue(this.artifact, '@cds.persistence.table')) {
126
- const generalQueryProperties = [ 'from', 'columns', 'where', 'groupBy', 'orderBy', 'having', 'limit' ];
127
- for (const prop of generalQueryProperties) {
128
- const queryPart = (query.SELECT || query.SET)[prop];
129
- if (Array.isArray(queryPart)) {
130
- for (const part of queryPart)
131
- checkRef(part, prop === 'columns');
132
- }
133
- else if (typeof queryPart === 'object') {
134
- checkRef(queryPart, prop === 'columns');
125
+ if (art.on) {
126
+ for (let j = 0; j < art.on.length; j++) {
127
+ if (j < art.on.length - 2 && art.on[j].ref && art.on[j + 1] === '=' && art.on[j + 2].ref) {
128
+ const [ fwdAssoc, fwdPath ] = getForwardAssociation(pathStep, art.on[j], art.on[j + 2]);
129
+ if (fwdAssoc && fwdAssoc.keys && leafCount.call(this, fwdAssoc) === 0) {
130
+ this.error(null, obj.$path,
131
+ { name: pathStep, elemref: obj, id: fwdPath },
132
+ 'Path step $(NAME) of $(ELEMREF) is a $self comparison with $(ID) that has no foreign keys');
133
+ j += 2;
134
+ }
135
+ }
135
136
  }
136
137
  }
137
138
  }
@@ -22,6 +22,8 @@ function checkSqlAnnotationOnElement( member, memberName, prop, path ) {
22
22
  this.message('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on elements in views' );
23
23
  else if (this.csnUtils.isStructured(member))
24
24
  this.message('anno-invalid-sql-struct', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on structured elements' );
25
+ else if (member.value && !member.value.stored)
26
+ this.message('anno-invalid-sql-calc', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on calculated elements on read' );
25
27
  else
26
28
  checkValidAnnoValue(member, '@sql.append', path, this.error, this.options);
27
29
  }
@@ -53,10 +53,10 @@ function checkElementTypeDefinitionHasType( member, memberName, prop, path ) {
53
53
  const parent = this.csn.definitions[path[1]];
54
54
 
55
55
  // should only happen with csn input, not in cdl
56
- // calculated elements may not have a .type (requires beta flag)
57
- if (!member.value &&
56
+ // calculated elements on-read may not have a .type (requires beta flag)
57
+ if ((!member.value || member.value.stored) &&
58
58
  !parent.projection && !parent.query && !hasArtifactTypeInformation(member)) {
59
- errorAboutMissingType(this.error, path, memberName, true);
59
+ errorAboutMissingType(this.error, path, member, memberName, true);
60
60
  return;
61
61
  }
62
62
 
@@ -97,7 +97,7 @@ function checkTypeDefinitionHasType( artifact, artifactName, prop, path ) {
97
97
 
98
98
  // should only happen with csn input, not in cdl
99
99
  if (!hasArtifactTypeInformation(artifact)) {
100
- errorAboutMissingType(this.error, path, artifactName);
100
+ errorAboutMissingType(this.error, path, artifact, artifactName);
101
101
  return;
102
102
  }
103
103
 
@@ -153,13 +153,19 @@ function checkTypeOfHasProperType( artOrElement, name, model, error, path, deriv
153
153
  *
154
154
  * @param {Function} error the error function
155
155
  * @param {CSN.Path} path the path to the element or the artifact
156
+ * @param {CSN.Artifact} artifact Element or other member/definition.
156
157
  * @param {string} name of the element or the artifact which is dubious
157
158
  * @param {boolean} isElement indicates whether we are dealing with an element or an artifact
158
159
  */
159
- function errorAboutMissingType( error, path, name, isElement = false ) {
160
- error('check-proper-type', path, { art: name, '#': isElement ? 'elm' : 'std' }, {
160
+ function errorAboutMissingType( error, path, artifact, name,
161
+ isElement = false ) {
162
+ let variant = isElement ? 'elm' : 'std';
163
+ if (artifact.value?.stored)
164
+ variant = 'calc';
165
+ error('check-proper-type', path, { art: name, '#': variant }, {
161
166
  std: 'Dubious type $(ART) without type information',
162
167
  elm: 'Dubious element $(ART) without type information',
168
+ calc: 'A stored calculated element must have a type',
163
169
  });
164
170
  }
165
171
 
@@ -19,7 +19,7 @@ const checkForParams = require('./parameters');
19
19
  const { validateDefaultValues } = require('./defaultValues');
20
20
  const { checkActionOrFunction } = require('./actionsFunctions');
21
21
  const {
22
- checkCoreMediaTypeAllowence, checkAnalytics,
22
+ checkCoreMediaTypeAllowance, checkAnalytics,
23
23
  checkAtSapAnnotations, checkReadOnlyAndInsertOnly,
24
24
  } = require('./annotationsOData');
25
25
  // both
@@ -251,7 +251,7 @@ function forOdata( csn, that ) {
251
251
  if (that.csnUtils.getServiceName(artifactName)) {
252
252
  checkAtSapAnnotations.bind(that)(artifact);
253
253
  forEachMemberRecursively(artifact, [
254
- checkCoreMediaTypeAllowence.bind(that),
254
+ checkCoreMediaTypeAllowance.bind(that),
255
255
  checkAnalytics.bind(that),
256
256
  checkAtSapAnnotations.bind(that),
257
257
  ]);
@@ -105,7 +105,6 @@ function assertConsistency( model, stage ) {
105
105
  '$blocks',
106
106
  '$messageFunctions',
107
107
  '$functions',
108
- '$volatileFunctions',
109
108
  '_sortedSources',
110
109
  ],
111
110
  },
@@ -238,6 +237,7 @@ function assertConsistency( model, stage ) {
238
237
  // specified elements in query entities (TODO: introduce real "specified elements" instead):
239
238
  elements$: { kind: true, enumerable: false, test: TODO },
240
239
  enum$: { kind: true, enumerable: false, test: TODO },
240
+ typeProps$: { kind: true, enumerable: false, test: TODO },
241
241
  actions: { kind: true, inherits: 'definitions' },
242
242
  enum: { kind: true, inherits: 'definitions' },
243
243
  foreignKeys: { kind: true, inherits: 'definitions' },
@@ -253,6 +253,7 @@ function assertConsistency( model, stage ) {
253
253
  requires: [ 'op', 'location', 'args' ],
254
254
  optional: [
255
255
  'quantifier', 'orderBy', 'limit', 'name', '$parens', 'kind',
256
+ '_origin', // TODO tmp, see TODO in getOriginRaw()
256
257
  '_parent', '_main', '_leadingQuery', '_effectiveType', '$effectiveSeqNo', // in FROM
257
258
  ],
258
259
  },
@@ -359,8 +360,9 @@ function assertConsistency( model, stage ) {
359
360
  optional: [
360
361
  '$delimited', // TODO remove?
361
362
  'args', '$syntax',
362
- 'where', 'cardinality',
363
- '_artifact', '_navigation',
363
+ 'where', 'groupBy', 'limit', 'orderBy', 'having',
364
+ 'cardinality',
365
+ '_artifact', '_navigation', '_user',
364
366
  '$inferred',
365
367
  ],
366
368
  },
@@ -386,6 +388,7 @@ function assertConsistency( model, stage ) {
386
388
  // locations of parentheses pairs around expression:
387
389
  $parens: { parser: true, test: TODO },
388
390
  $prefix: { test: isString }, // compiler-corrected path prefix
391
+ $extended: { test: TODO, kind: [ 'element', '$inline' ] }, // `extend … with columns`
389
392
  $syntax: {
390
393
  parser: true,
391
394
  kind: [ 'entity', 'view', 'type', 'aspect' ],
@@ -438,7 +441,7 @@ function assertConsistency( model, stage ) {
438
441
  ...typeProperties, // for CAST
439
442
  ],
440
443
  },
441
- query: { requires: [ 'query', 'location' ] },
444
+ query: { requires: [ 'query', 'location' ], optional: [ 'stored' ] },
442
445
  },
443
446
  literal: { // TODO: check value against literal
444
447
  test: isString,
@@ -564,6 +567,7 @@ function assertConsistency( model, stage ) {
564
567
  _parent: { kind: true, test: TODO },
565
568
  _service: { kind: true, test: TODO },
566
569
  _main: { kind: true, test: TODO },
570
+ _user: { kind: true, test: TODO },
567
571
  _artifact: { test: TODO },
568
572
  _navigation: { test: TODO },
569
573
  _effectiveType: { kind: true, test: TODO },
@@ -631,13 +635,12 @@ function assertConsistency( model, stage ) {
631
635
  parser: true,
632
636
  kind: true,
633
637
  test: isOneOf([
634
- '', // constructed “super annotate” statement
638
+ '', // constructed “super annotate” statement, redirected user-provided target
635
639
  // Uppercase values are used in logic, lowercase value are "just for us", i.e.
636
640
  // debugging or to add properties such as $generated in Universal CSN.
637
641
  // However, that is no longer true. For example, `autoexposed` is used in populate.js
638
642
  // as well.
639
643
  'IMPLICIT',
640
- 'REDIRECTED',
641
644
  'NULL', // from propagator
642
645
  'prop', // from propagator
643
646
 
@@ -651,7 +654,7 @@ function assertConsistency( model, stage ) {
651
654
  'cast', // type from cast() function
652
655
  'composition-entity',
653
656
  'copy', // only used in rewriteCondition(): On-condition is copied
654
- 'duplicate-autoexposed', // just like `autoexposed`, but with `duplicate` error.
657
+ 'def-duplicate-autoexposed', // just like `autoexposed`, but with `duplicate` error.
655
658
  'expanded', // expanded elements, items, params
656
659
  'include', // through includes, e.g. `entity E : F {}`
657
660
  'keys',
@@ -683,7 +686,6 @@ function assertConsistency( model, stage ) {
683
686
  $expected: { parser: true, test: isOneOf([ 'approved-exists', 'exists' ]) },
684
687
  $messageFunctions: { test: TODO },
685
688
  $functions: { test: TODO },
686
- $volatileFunctions: { test: TODO },
687
689
  };
688
690
  let _noSyntaxErrors = null;
689
691
  assertProp( model, null, stageParser ? ':parser' : ':model', null, true );
@@ -433,6 +433,8 @@ function initBuiltins( model ) {
433
433
  cds._subArtifacts.hana = hana;
434
434
  env( coreHana, 'cds.hana.', hana );
435
435
  model.$internal = { $frontend: '$internal' };
436
+ // namespace:"localized" reserved ---
437
+ model.definitions.localized = createNamespace( 'localized', 'reserved' );
436
438
  return;
437
439
 
438
440
  function createNamespace( name, builtin ) {
@@ -494,7 +496,7 @@ function initBuiltins( model ) {
494
496
  art.$requireElementAccess = magic.$requireElementAccess;
495
497
 
496
498
  createMagicElements( art, magic.elements );
497
- if (options.variableReplacements)
499
+ if (options.variableReplacements?.[name])
498
500
  createMagicElements( art, options.variableReplacements[name] );
499
501
  // setProp( art, '_effectiveType', art );
500
502
  }
@@ -512,7 +514,11 @@ function initBuiltins( model ) {
512
514
  for (const n of names) {
513
515
  const magic = {
514
516
  kind: 'builtin',
515
- name: { id: n, element: `${ art.name.element }.${ n }` },
517
+ name: {
518
+ id: n,
519
+ absolute: art.name.absolute,
520
+ element: art.name.element ? `${ art.name.element }.${ n }` : n,
521
+ },
516
522
  };
517
523
  // Propagate this property so that it is available for sub-elements.
518
524
  if (art.$uncheckedElements)