@sap/cds-compiler 4.4.4 → 4.6.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 (82) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/bin/cdsc.js +18 -11
  3. package/bin/cdsv2m.js +7 -5
  4. package/doc/CHANGELOG_BETA.md +22 -0
  5. package/lib/api/main.js +306 -144
  6. package/lib/api/options.js +18 -6
  7. package/lib/api/validate.js +1 -1
  8. package/lib/base/message-registry.js +45 -10
  9. package/lib/base/messages.js +33 -16
  10. package/lib/base/model.js +4 -0
  11. package/lib/base/optionProcessorHelper.js +45 -176
  12. package/lib/checks/annotationsOData.js +49 -0
  13. package/lib/checks/elements.js +32 -34
  14. package/lib/checks/enricher.js +39 -3
  15. package/lib/checks/validator.js +8 -7
  16. package/lib/compiler/assert-consistency.js +40 -17
  17. package/lib/compiler/builtins.js +30 -53
  18. package/lib/compiler/checks.js +46 -14
  19. package/lib/compiler/cycle-detector.js +1 -4
  20. package/lib/compiler/define.js +35 -10
  21. package/lib/compiler/extend.js +21 -7
  22. package/lib/compiler/generate.js +3 -0
  23. package/lib/compiler/populate.js +5 -1
  24. package/lib/compiler/propagator.js +46 -9
  25. package/lib/compiler/resolve.js +94 -35
  26. package/lib/compiler/shared.js +60 -33
  27. package/lib/compiler/tweak-assocs.js +188 -92
  28. package/lib/compiler/utils.js +11 -1
  29. package/lib/edm/annotations/edmJson.js +41 -66
  30. package/lib/edm/annotations/genericTranslation.js +27 -9
  31. package/lib/edm/annotations/preprocessAnnotations.js +2 -3
  32. package/lib/edm/csn2edm.js +28 -11
  33. package/lib/edm/edmInboundChecks.js +58 -15
  34. package/lib/edm/edmPreprocessor.js +12 -16
  35. package/lib/edm/edmUtils.js +5 -2
  36. package/lib/gen/Dictionary.json +10 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +15 -2
  39. package/lib/gen/language.tokens +1 -0
  40. package/lib/gen/languageParser.js +6557 -5618
  41. package/lib/json/from-csn.js +4 -5
  42. package/lib/json/to-csn.js +29 -4
  43. package/lib/language/antlrParser.js +19 -1
  44. package/lib/language/errorStrategy.js +28 -7
  45. package/lib/language/genericAntlrParser.js +118 -24
  46. package/lib/language/textUtils.js +16 -0
  47. package/lib/main.d.ts +28 -3
  48. package/lib/main.js +3 -0
  49. package/lib/model/csnRefs.js +4 -1
  50. package/lib/model/csnUtils.js +20 -14
  51. package/lib/model/revealInternalProperties.js +5 -2
  52. package/lib/optionProcessor.js +23 -22
  53. package/lib/render/manageConstraints.js +13 -29
  54. package/lib/render/toCdl.js +47 -26
  55. package/lib/render/toHdbcds.js +63 -42
  56. package/lib/render/toRename.js +6 -10
  57. package/lib/render/toSql.js +71 -117
  58. package/lib/render/utils/common.js +41 -6
  59. package/lib/transform/.eslintrc.json +9 -1
  60. package/lib/transform/addTenantFields.js +228 -0
  61. package/lib/transform/db/applyTransformations.js +57 -4
  62. package/lib/transform/db/assertUnique.js +4 -4
  63. package/lib/transform/db/backlinks.js +13 -1
  64. package/lib/transform/db/cdsPersistence.js +1 -1
  65. package/lib/transform/db/expansion.js +24 -3
  66. package/lib/transform/db/flattening.js +70 -71
  67. package/lib/transform/db/killAnnotations.js +37 -0
  68. package/lib/transform/db/rewriteCalculatedElements.js +46 -6
  69. package/lib/transform/db/temporal.js +1 -1
  70. package/lib/transform/draft/db.js +2 -16
  71. package/lib/transform/draft/odata.js +3 -3
  72. package/lib/transform/effective/associations.js +3 -5
  73. package/lib/transform/effective/main.js +6 -9
  74. package/lib/transform/forOdata.js +26 -55
  75. package/lib/transform/forRelationalDB.js +38 -18
  76. package/lib/transform/odata/toFinalBaseType.js +3 -3
  77. package/lib/transform/odata/typesExposure.js +14 -5
  78. package/lib/transform/transformUtils.js +47 -34
  79. package/lib/transform/translateAssocsToJoins.js +45 -11
  80. package/lib/transform/universalCsn/coreComputed.js +1 -1
  81. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  82. package/package.json +7 -6
@@ -0,0 +1,228 @@
1
+ // Add tenant field MANDT to entities
2
+
3
+ // Prerequisites:
4
+
5
+ // - the input CSN is a `client` style CSN from the Core Compiler
6
+ // - using structure types with unmanaged associations is not supported by the
7
+ // Core Compiler (due to missing ON-rewrite)
8
+
9
+ // TODO entities without MANDT:
10
+
11
+ // - cache whether structure type contains (managed) association to entity with MANDT
12
+ // - disallow use of such a type in entity without MANDT
13
+
14
+ // Implementation remark:
15
+
16
+ // - the functions `forEachDefinition` & friends in csnUtils.js have become quite
17
+ // (too) general and are probably slow → not used here
18
+
19
+ 'use strict';
20
+
21
+ const { createMessageFunctions } = require( '../base/messages' );
22
+ const { traverseQuery } = require( '../model/csnRefs' );
23
+
24
+ const fieldName = 'tenant';
25
+ const fieldDef = {
26
+ key: true,
27
+ type: 'cds.String',
28
+ length: 36,
29
+ '@cds.api.ignore': true, // and/or $generated: 'tenant' for the full Universal CSN?
30
+ };
31
+
32
+ function addTenantFields( csn, options ) {
33
+ const { definitions } = csn;
34
+ if (!definitions)
35
+ return csn;
36
+ const { error, throwWithError } = createMessageFunctions( options, 'tenant', csn );
37
+
38
+ const csnPath = [ 'definitions', '' ];
39
+ let projection;
40
+
41
+ for (const name in definitions) {
42
+ const art = definitions[name];
43
+ if (art?.kind !== 'entity')
44
+ continue;
45
+ csnPath[1] = name;
46
+
47
+ if (art['@cds.tenant.independent'] != null) {
48
+ error( null, csnPath, { anno: '@cds.tenant.independent' },
49
+ 'Can\'t yet add annotation $(ANNO) to an entity' );
50
+ }
51
+ if (!handleElements( art ))
52
+ continue;
53
+ projection = art.query || art.projection && art;
54
+ if (projection)
55
+ traverseQuery( projection, null, null, handleQuery );
56
+ }
57
+
58
+ (csn.extensions || []).forEach( (ext, idx) => {
59
+ const tenant = ext.elements?.[fieldName];
60
+ const name = ext.annotate || ext.extend; // extend should not happen
61
+ if (tenant && definitions[name]?.kind === 'entity') { // TODO: ok for tenant-independent
62
+ error( null, [ 'extensions', idx, 'elements', 'tenant' ],
63
+ { name: fieldName },
64
+ 'Can\'t annotate element $(NAME) of a tenant-dependent entity' );
65
+ }
66
+ } );
67
+
68
+ throwWithError();
69
+ return csn; // input CSN changed by side effect
70
+
71
+ function handleElements( art ) {
72
+ const { elements } = art;
73
+ if (elements[fieldName]) {
74
+ error( null, [ ...csnPath, 'elements', fieldName ], { name: fieldName },
75
+ 'Can\'t add tenant field to entity having an element $(NAME)' );
76
+ return false;
77
+ }
78
+ if (!Object.values( elements ).some( e => e.key )) {
79
+ error( null, csnPath, {},
80
+ 'There must be a key in a tenant-dependent entity' );
81
+ return false;
82
+ }
83
+ handleAssociations( art );
84
+ art.elements = { [fieldName]: { ...fieldDef }, ...elements };
85
+ return true;
86
+ }
87
+
88
+ function handleQuery( query ) {
89
+ // TODO: errors are temporary: start with simple projections only = no better
90
+ // message $location necessary yet
91
+ if (!projection) // error already reported
92
+ return;
93
+ if (query.ref) {
94
+ if ((query.as || implicitAs( query.ref )) === fieldName) {
95
+ error( null, csnPath, { name: fieldName },
96
+ 'Can\'t have a table alias named $(NAME) in a tenant-dependent entity' );
97
+ }
98
+ return;
99
+ }
100
+
101
+ const select = query.SELECT || query.projection;
102
+ if (query.SET || query !== projection || !select?.from?.ref) {
103
+ error( null, csnPath, {},
104
+ 'Can\'t add tenant columns to non-simple query entities' );
105
+ projection = null;
106
+ return;
107
+ }
108
+
109
+ if (query.projection)
110
+ csnPath.push( 'projection' );
111
+ else
112
+ csnPath.push( 'query', 'SELECT' );
113
+
114
+ if (select.mixin)
115
+ handleMixins( select.mixin );
116
+ if (select.excluding)
117
+ checkExcluding( select.excluding );
118
+ if (select.columns)
119
+ handleColumns( select.columns );
120
+ // TODO: for subqueries, we might need to adapt the inferred elements
121
+ // TODO: where exists ref -
122
+ // TODO: select and query clauses, especially with aggregation functions
123
+ handleGroupBy( select );
124
+ csnPath.length = 2;
125
+ }
126
+
127
+ function handleMixins( mixin ) {
128
+ csnPath.push( 'mixin', '' );
129
+ for (const name in mixin) {
130
+ csnPath[csnPath.length - 1] = name;
131
+ if (name !== fieldName) {
132
+ addToCondition( mixin[name], name );
133
+ }
134
+ else {
135
+ error( null, csnPath, { name },
136
+ 'Can\'t define a mixin named $(NAME) in a tenant-dependent entity' );
137
+ }
138
+ }
139
+ csnPath.length -= 2;
140
+ }
141
+
142
+ function checkExcluding( excludeList ) {
143
+ if (excludeList.includes( fieldName )) {
144
+ error( null, csnPath, { name: fieldName },
145
+ 'Can\'t exclude $(NAME) from query source' );
146
+ }
147
+ }
148
+
149
+ function handleGroupBy( select ) {
150
+ // TODO: in the future, we allow model-wise keyless views when using
151
+ // aggregation function, and add a GROUP BY for MANDT in this case. Now, also
152
+ // views with agg functions need to have a key element → it very likely
153
+ // already contains a GROUP BY. And anyway: if we miss to add GROUP BY MANDT,
154
+ // the database will complain → no safetly risk.
155
+ if (select.groupBy)
156
+ select.groupBy.unshift( { ref: [ fieldName ] } );
157
+ }
158
+
159
+ function handleColumns( columns ) {
160
+ let specifiedKey = false;
161
+ csnPath.push( 'columns', -1 );
162
+ for (const col of columns) {
163
+ ++csnPath[csnPath.length - 1];
164
+ if (col.expand || col.inline) {
165
+ error( null, csnPath, {},
166
+ 'Can\'t use expand/inline in a tenant-dependent entity' );
167
+ }
168
+ if (col.key != null) // yes, also with key: false
169
+ specifiedKey = true;
170
+ if (col.cast?.on) // REDIRECTED TO with explicit ON - TODO (low prio): less $self
171
+ addToCondition( col.cast, col.as || implicitAs( col.ref ) );
172
+ }
173
+ csnPath.length -= 2;
174
+ columns.unshift( specifiedKey
175
+ ? { key: true, ref: [ fieldName ] }
176
+ : { ref: [ fieldName ] } );
177
+ }
178
+
179
+ function handleAssociations( elem ) {
180
+ const { elements } = elem;
181
+ if (elements) {
182
+ csnPath.push( 'elements', '' );
183
+ for (const name in elements) {
184
+ csnPath[csnPath.length - 1] = name;
185
+ handleAssociations( elements[name] );
186
+ }
187
+ csnPath.length -= 2;
188
+ }
189
+ else if (elem.target) {
190
+ if (elem.on) {
191
+ addToCondition( elem, csnPath[csnPath.length - 1] );
192
+ }
193
+ else {
194
+ error( null, csnPath, {},
195
+ 'Can\'t yet use managed associations in a tenant-dependent entity' );
196
+ }
197
+ }
198
+ else if (elem.items) {
199
+ csnPath.push( 'items' );
200
+ handleAssociations( elem.items );
201
+ --csnPath.length;
202
+ }
203
+ }
204
+
205
+ function addToCondition( elem, assoc ) {
206
+ if (!elem.on)
207
+ return;
208
+ const withSelf = csnPath.length > 4;
209
+ elem.on = [
210
+ { ref: [ assoc, fieldName ] }, // TODO: consider assoc name starting with '$'
211
+ '=',
212
+ { ref: (withSelf) ? [ '$self', fieldName ] : [ fieldName ] },
213
+ 'and',
214
+ // TODO: avoid (...) for standard AND-ed EQ-comparisons ?
215
+ { xpr: elem.on },
216
+ ];
217
+ }
218
+ }
219
+
220
+ function implicitAs( ref ) {
221
+ const item = ref[ref.length - 1];
222
+ const id = (typeof item === 'string') ? item : item.id;
223
+ return id.substring( id.lastIndexOf('.') + 1 );
224
+ }
225
+
226
+ module.exports = {
227
+ addTenantFields,
228
+ };
@@ -13,7 +13,7 @@
13
13
 
14
14
 
15
15
  const { setProp } = require('../../base/model');
16
- const { xprInAnnoProperties } = require('../../compiler/builtins');
16
+ const { isAnnotationExpression } = require('../../compiler/builtins');
17
17
 
18
18
 
19
19
  /**
@@ -85,6 +85,8 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
85
85
  const trans = transformers[name] || transformers[name.charAt(0)] || standard;
86
86
  if (customTransformers[name])
87
87
  customTransformers[name](node, name, node[name], csnPath, _parent, _prop);
88
+ else if (options.processAnnotations && customTransformers['@'] && name.charAt(0) === '@')
89
+ customTransformers['@'](node, name, node[name], csnPath, _parent, _prop);
88
90
  trans( node, name, node[name], csnPath );
89
91
  }
90
92
  }
@@ -109,6 +111,8 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
109
111
  const trans = transformers[name] || transformers[name.charAt(0)] || standard;
110
112
  if (customTransformers[name])
111
113
  customTransformers[name](node, name, node[name], csnPath, dict);
114
+ else if (options.processAnnotations && customTransformers['@'] && name.charAt(0) === '@')
115
+ customTransformers['@'](node, name, node[name], csnPath, dict);
112
116
  trans( node, name, node[name], csnPath );
113
117
  }
114
118
  csnPath.pop();
@@ -144,14 +148,19 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
144
148
  */
145
149
  function annotation( _parent, _prop, node ) {
146
150
  if (options.processAnnotations) {
147
- if (node?.['='] !== undefined && xprInAnnoProperties.some(xProp => node[xProp] !== undefined)) {
151
+ if (isAnnotationExpression(node)) {
148
152
  standard(_parent, _prop, node);
149
153
  }
150
154
  else if (node && typeof node === 'object') {
151
155
  csnPath.push(_prop);
152
156
 
153
- for (const name of Object.getOwnPropertyNames( node ))
154
- annotation( node, name, node[name] );
157
+ if (Array.isArray(node)) {
158
+ node.forEach( (n, i) => annotation( node, i, n ) );
159
+ }
160
+ else {
161
+ for (const name of Object.getOwnPropertyNames( node ))
162
+ annotation( node, name, node[name] );
163
+ }
155
164
 
156
165
  csnPath.pop();
157
166
  }
@@ -287,7 +296,51 @@ function applyTransformationsOnDictionary( dictionary, customTransformers = {},
287
296
  return applyTransformationsInternal(dictionary, null, customTransformers, [], { directDict: true, ...options }, path);
288
297
  }
289
298
 
299
+ /**
300
+ * transformExpression is a lightweight version of applyTransformations
301
+ * used primarily to transform annotation expressions.
302
+ * If propName is undefined, all properties of parent are transformed.
303
+ * @param {object} parent Start node
304
+ * @param {string} propName Start at specific property of parent
305
+ * @param {object} transformers Map of callback functions
306
+ * @param {CSN.Path} path Path to parent
307
+ * @returns {object} transformed node
308
+ */
309
+ function transformExpression( parent, propName, transformers, path = [] ) {
310
+ if (propName != null) {
311
+ const child = parent[propName];
312
+ if (!child || typeof child !== 'object' ||
313
+ !{}.propertyIsEnumerable.call( parent, propName ))
314
+ return parent;
315
+
316
+ path = [ ...path, propName ];
317
+ if (Array.isArray(child)) {
318
+ child.forEach( (n, i) => transformExpression( child, i, transformers, path ) );
319
+ }
320
+ else {
321
+ for (const cpn of Object.getOwnPropertyNames( child )) {
322
+ const ct = transformers[cpn];
323
+ if (ct) {
324
+ const ppn = propName;
325
+ if (Array.isArray(ct))
326
+ ct.forEach(cti => cti(child, cpn, child[cpn], path, parent, ppn));
327
+
328
+ else
329
+ ct(child, cpn, child[cpn], path, parent, ppn);
330
+ }
331
+ transformExpression(child, cpn, transformers, path);
332
+ }
333
+ }
334
+ }
335
+ else {
336
+ for (propName of Object.getOwnPropertyNames( parent ))
337
+ transformExpression( parent, propName, transformers, path );
338
+ }
339
+ return parent;
340
+ }
341
+
290
342
  module.exports = {
343
+ transformExpression,
291
344
  applyTransformations,
292
345
  applyTransformationsOnNonDictionary,
293
346
  applyTransformationsOnDictionary,
@@ -16,12 +16,12 @@ const { pathName } = require('../../compiler/utils');
16
16
  *
17
17
  * @param {CSN.Model} csn Overall CSN model
18
18
  * @param {CSN.Options} options Options
19
- * @param {Function} error Message function for errors
20
- * @param {Function} info Message function for info
19
+ * @param {object} messageFunctions Message functions (error(), info(), …)
21
20
  * @returns {Function} forEachDefinition callback
22
21
  */
23
- function processAssertUnique( csn, options, error, info ) {
24
- const { resolvePath, flattenPath } = getTransformers(csn, options);
22
+ function processAssertUnique( csn, options, messageFunctions ) {
23
+ const { resolvePath, flattenPath } = getTransformers(csn, options, messageFunctions);
24
+ const { error, info } = messageFunctions;
25
25
 
26
26
  return handleAssertUnique;
27
27
  /**
@@ -18,6 +18,7 @@ const { forEach } = require('../../utils/objectUtils');
18
18
  * @returns {import('../../model/csnUtils').genericCallback} callback for forEachDefinition
19
19
  */
20
20
  function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimiter, doA2J = true ) {
21
+ let prepend$self = false;
21
22
  return transformSelfInBacklinks;
22
23
  /**
23
24
  * @param {CSN.Artifact} artifact
@@ -26,12 +27,15 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
26
27
  * @param {CSN.Path} path
27
28
  */
28
29
  function transformSelfInBacklinks( artifact, artifactName, dummy, path ) {
30
+ prepend$self = false;
29
31
  // Fixme: For toHana mixins must be transformed, for toSql -d hana
30
32
  // mixin elements must be transformed, why can't toSql also use mixins?
31
33
  if (options.transformation === 'effective' && artifact.elements || artifact.kind === 'entity' || artifact.query || (options.forHana && options.sqlMapping === 'hdbcds' && artifact.kind === 'type'))
32
34
  processDict(artifact.elements, path.concat([ 'elements' ]));
33
- if (artifact.query?.SELECT?.mixin)
35
+ if (artifact.query?.SELECT?.mixin) {
36
+ prepend$self = options.transformation === 'effective';
34
37
  processDict(artifact.query.SELECT.mixin, path.concat([ 'query', 'SELECT', 'mixin' ]));
38
+ }
35
39
 
36
40
  /**
37
41
  * Loop over the dict and start the processing.
@@ -237,6 +241,10 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
237
241
  { ref: k.ref },
238
242
  ];
239
243
 
244
+ if (prepend$self)
245
+ a[1].ref = [ '$self', ...a[1].ref ];
246
+
247
+
240
248
  conditions.push([ a[0], '=', a[1] ]);
241
249
  });
242
250
 
@@ -273,11 +281,15 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
273
281
  // we are in the "path" from the forwarding assoc => need to remove the first part of the path
274
282
  if (ref[0] === assocName) {
275
283
  ref.shift();
284
+ if (prepend$self)
285
+ ref.unshift('$self');
276
286
  }
277
287
  else if (ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
278
288
  // We could also have a $self in front of the assoc name - so we would need to shift twice
279
289
  ref.shift();
280
290
  ref.shift();
291
+ if (prepend$self)
292
+ ref.unshift('$self');
281
293
  }
282
294
  else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
283
295
  ref.unshift(elemName);
@@ -106,7 +106,7 @@ function getPersistenceTableProcessor( csn, options, messageFunctions ) {
106
106
  const { error } = messageFunctions;
107
107
  const {
108
108
  recurseElements,
109
- } = transformUtils.getTransformers(csn, options, '_');
109
+ } = transformUtils.getTransformers(csn, options, messageFunctions, '_');
110
110
 
111
111
  return handleQueryish;
112
112
 
@@ -10,6 +10,7 @@ const {
10
10
  const { implicitAs, columnAlias, pathId } = require('../../model/csnRefs');
11
11
  const { setProp } = require('../../base/model');
12
12
  const { forEach } = require('../../utils/objectUtils');
13
+ const { killNonrequiredAnno } = require('./killAnnotations');
13
14
 
14
15
  /**
15
16
  * For keys, columns, groupBy and orderBy, expand structured things.
@@ -27,7 +28,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
27
28
 
28
29
  rewriteExpandInline();
29
30
 
30
- applyTransformations(csn, {
31
+ const transformers = {
31
32
  keys: (parent, name, keys, path) => {
32
33
  parent.keys = expand(keys, path.concat('keys'), true);
33
34
  },
@@ -53,7 +54,13 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
53
54
  orderBy: (parent, name, orderBy, path) => {
54
55
  parent.orderBy = expand(orderBy, path.concat('orderBy'));
55
56
  },
56
- }, [], iterateOptions);
57
+ };
58
+
59
+ // To not have a whole model loop for such a "small" thing, we kill all non-sql-backend relevant annotations here
60
+ if (options.transformation === 'sql' || options.transformation === 'hdbcds')
61
+ transformers['@'] = killNonrequiredAnno;
62
+
63
+ applyTransformations(csn, transformers, [], iterateOptions);
57
64
 
58
65
  /**
59
66
  * Turn .expand/.inline into normal refs. @cds.persistence.skip .expand with to-many (and all transitive views).
@@ -529,7 +536,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
529
536
  else
530
537
  newThing.push(col);
531
538
  }
532
- else if (col.ref && col.$scope === '$magic' && ( col.ref[0] === '$user' || col.ref[0] === '$session' ) && !col.as) {
539
+ else if (col.ref && col.$scope === '$magic' && ( col.ref[0] === '$user' || col.ref[0] === '$tenant' || col.ref[0] === '$session' ) && !col.as) {
533
540
  col.as = implicitAs(col.ref);
534
541
  newThing.push(col);
535
542
  }
@@ -618,6 +625,20 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
618
625
  setProp(obj, '$implicitAlias', true);
619
626
  }
620
627
 
628
+ // If our column/thing was cast to a structured type, we need to keep the "cast" insync with the
629
+ // flattened out leaf elements that we turn the ref into
630
+ if (obj.cast?.type) {
631
+ const addedRef = currentRef.slice(root.ref.length);
632
+ if (addedRef.length > 0) {
633
+ // Decouple from other leafs
634
+ obj.cast = { ...obj.cast };
635
+ if (!obj.cast.type.ref)
636
+ obj.cast.type = { ref: [ obj.cast.type ] };
637
+
638
+ obj.cast.type.ref = [ ...obj.cast.type.ref, ...addedRef ];
639
+ }
640
+ }
641
+
621
642
  // The Java runtime, as of 2023-09-13, assumes that for _simple projections_, all references
622
643
  // are relative to the query source. To avoid breaking that assumption unless necessary,
623
644
  // we only add the table alias if: