@sap/cds-compiler 6.2.2 → 6.3.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 (57) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/bin/cdsc.js +11 -4
  3. package/lib/api/options.js +1 -1
  4. package/lib/base/message-registry.js +36 -7
  5. package/lib/base/messages.js +11 -4
  6. package/lib/base/model.js +0 -1
  7. package/lib/checks/assocOutsideService.js +17 -30
  8. package/lib/checks/checkForTypes.js +0 -18
  9. package/lib/checks/checkPathsInStoredCalcElement.js +2 -1
  10. package/lib/checks/onConditions.js +2 -2
  11. package/lib/checks/queryNoDbArtifacts.js +16 -15
  12. package/lib/checks/types.js +1 -1
  13. package/lib/checks/utils.js +30 -6
  14. package/lib/checks/validator.js +4 -5
  15. package/lib/compiler/checks.js +47 -18
  16. package/lib/compiler/index.js +88 -6
  17. package/lib/compiler/resolve.js +7 -7
  18. package/lib/compiler/tweak-assocs.js +47 -25
  19. package/lib/gen/BaseParser.js +1 -1
  20. package/lib/gen/CdlGrammar.checksum +1 -1
  21. package/lib/gen/CdlParser.js +381 -378
  22. package/lib/gen/Dictionary.json +0 -2
  23. package/lib/model/csnRefs.js +9 -4
  24. package/lib/model/csnUtils.js +67 -2
  25. package/lib/optionProcessor.js +2 -3
  26. package/lib/parsers/AstBuildingParser.js +5 -6
  27. package/lib/render/toCdl.js +10 -4
  28. package/lib/render/utils/common.js +4 -2
  29. package/lib/transform/db/assertUnique.js +2 -1
  30. package/lib/transform/db/associations.js +37 -1
  31. package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
  32. package/lib/transform/db/assocsToQueries/utils.js +1 -1
  33. package/lib/transform/db/cdsPersistence.js +1 -1
  34. package/lib/transform/db/expansion.js +37 -36
  35. package/lib/transform/draft/db.js +20 -20
  36. package/lib/transform/draft/odata.js +38 -40
  37. package/lib/transform/effective/associations.js +1 -1
  38. package/lib/transform/effective/flattening.js +40 -47
  39. package/lib/transform/effective/main.js +6 -4
  40. package/lib/transform/forOdata.js +135 -115
  41. package/lib/transform/forRelationalDB.js +151 -142
  42. package/lib/transform/localized.js +116 -109
  43. package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
  44. package/lib/transform/odata/createForeignKeys.js +73 -70
  45. package/lib/transform/odata/flattening.js +216 -200
  46. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
  47. package/lib/transform/odata/toFinalBaseType.js +40 -39
  48. package/lib/transform/odata/typesExposure.js +151 -133
  49. package/lib/transform/odata/utils.js +7 -6
  50. package/lib/transform/parseExpr.js +165 -162
  51. package/lib/transform/transformUtils.js +184 -551
  52. package/lib/transform/translateAssocsToJoins.js +510 -571
  53. package/lib/transform/tupleExpansion.js +495 -0
  54. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  55. package/package.json +1 -1
  56. package/lib/base/cleanSymbols.js +0 -17
  57. package/lib/checks/nonexpandableStructured.js +0 -39
@@ -808,7 +808,6 @@
808
808
  "Type": "Core.Tag"
809
809
  },
810
810
  "Common.IsTimezone": {
811
- "$experimental": true,
812
811
  "AppliesTo": [
813
812
  "Property",
814
813
  "Parameter"
@@ -1035,7 +1034,6 @@
1035
1034
  "Type": "Edm.DateTimeOffset"
1036
1035
  },
1037
1036
  "Common.Timezone": {
1038
- "$experimental": true,
1039
1037
  "AppliesTo": [
1040
1038
  "Property",
1041
1039
  "Parameter"
@@ -250,10 +250,9 @@ function csnRefs( csn, universalReady ) {
250
250
  // const { definitions } = csn;
251
251
  const cache = new WeakMap();
252
252
  setCache( BUILTIN_TYPE, '_origin', null );
253
- if (universalReady === 'init-all') {
254
- for (const name of Object.keys( csn.definitions || {}))
255
- initDefinition( name );
256
- }
253
+ if (universalReady === 'init-all')
254
+ initAllDefinitions();
255
+
257
256
  // Functions which set the new `baseEnv`:
258
257
  resolveRef.expandInline = function resolveExpandInline( ref, ...args ) {
259
258
  return cached( ref, '_env', () => navigationEnv( resolveRef( ref, ...args ).art ) );
@@ -278,6 +277,7 @@ function csnRefs( csn, universalReady ) {
278
277
  getColumnName: col => getCache( col, '$as' ),
279
278
  $getQueries: def => getCache( def, '$queries' ), // unstable API
280
279
  initDefinition,
280
+ initAllDefinitions,
281
281
  dropDefinitionCache,
282
282
  targetAspect,
283
283
  msgLocations,
@@ -489,6 +489,11 @@ function csnRefs( csn, universalReady ) {
489
489
  return query && cache.get( query.projection || query );
490
490
  }
491
491
 
492
+ function initAllDefinitions() {
493
+ for (const name of Object.keys( csn.definitions || {}))
494
+ initDefinition( name );
495
+ }
496
+
492
497
  function initDefinition( main ) {
493
498
  const name = typeof main === 'string' && main;
494
499
  if (name) {
@@ -64,6 +64,7 @@ function getUtils( model, universalReady ) {
64
64
  addStringAnnotationTo,
65
65
  getServiceName,
66
66
  getFinalTypeInfo,
67
+ resolvePath,
67
68
  get$combined,
68
69
  getQueryPrimarySource,
69
70
  ..._csnRefs,
@@ -448,6 +449,71 @@ function getUtils( model, universalReady ) {
448
449
  return target;
449
450
  }
450
451
  }
452
+
453
+
454
+ /**
455
+ * Resolve the type of an artifact
456
+ * If art is undefined, stop
457
+ * If art has elements or items.elements, stop
458
+ * If art has a type and the type is scalar, stop
459
+ * If art has a named type or a type ref, resolve it
460
+ */
461
+ function resolveType( art ) {
462
+ while (art &&
463
+ !((art.items && art.items.elements) || art.elements) &&
464
+ (art.type &&
465
+ ((!art.type.ref && !isBuiltinType(art.type)) || art.type.ref))) {
466
+ if (art.type.ref)
467
+ art = resolvePath(art.type);
468
+ else
469
+ art = model.definitions[art.type];
470
+ }
471
+ return art;
472
+ }
473
+
474
+ /**
475
+ * Path resolution, attach artifact to each path step, if found,
476
+ * Dereference types and follow associations.
477
+ *
478
+ * @param {any} path ref object
479
+ * @param {any} art start environment
480
+ * @returns {any} path with resolved artifacts or artifact
481
+ * (if called with simple ref paths)
482
+ */
483
+ function resolvePath( path, art = undefined ) {
484
+ let notFound = false;
485
+ for (let i = 0; i < path.ref.length && !notFound; i++) {
486
+ const ps = path.ref[i];
487
+ const id = ps.id || ps;
488
+ if (art) {
489
+ if (art.target)
490
+ art = model.definitions[art.target].elements[id];
491
+ else if (art.items && art.items.elements || art.elements)
492
+ art = (art.items && art.items.elements || art.elements)[id];
493
+
494
+ else
495
+ art = undefined;
496
+ }
497
+ else {
498
+ art = model.definitions[id];
499
+ }
500
+ art = resolveType(art);
501
+
502
+ // if path step has id, store art
503
+ if (ps.id && art)
504
+ ps._art = art;
505
+ notFound = !art;
506
+ }
507
+ // if resolve was called on constraint path, path has id.
508
+ // Store art and return path, if called recursively for model ref paths,
509
+ // return artifact only
510
+ if (path.ref[0].id) {
511
+ if (art)
512
+ path._art = art;
513
+ return path;
514
+ }
515
+ return art;
516
+ }
451
517
  }
452
518
 
453
519
 
@@ -1248,7 +1314,6 @@ function walkCsnPath( csn, path ) {
1248
1314
  for (const segment of path)
1249
1315
  obj = obj[segment];
1250
1316
 
1251
-
1252
1317
  return obj;
1253
1318
  }
1254
1319
 
@@ -1356,7 +1421,7 @@ function functionList( functions, thisArg ) {
1356
1421
  * @returns {boolean}
1357
1422
  */
1358
1423
  function isDollarSelfOrProjectionOperand( arg ) {
1359
- return arg.ref && arg.ref.length === 1 &&
1424
+ return arg.ref?.length === 1 &&
1360
1425
  (arg.ref[0] === '$self' || arg.ref[0] === '$projection');
1361
1426
  }
1362
1427
 
@@ -265,7 +265,7 @@ optionProcessor.command('O, toOdata')
265
265
  .option(' --odata-vocabularies <list>')
266
266
  .option(' --odata-no-creator')
267
267
  .option(' --draft-messages')
268
- .option(' --add-annotation-AddressViaNavigationPath')
268
+ .option(' --draft-user-description')
269
269
  .option('-c, --csn')
270
270
  .option('-f, --odata-format <format>', { valid: [ 'flat', 'structured' ] })
271
271
  .option('-n, --sql-mapping <style>', { valid: [ 'plain', 'quoted', 'hdbcds' ], aliases: [ '--names' ] })
@@ -301,8 +301,7 @@ optionProcessor.command('O, toOdata')
301
301
  { prefix: { alias, ns, uri }, ... }
302
302
  --odata-no-creator Omit creator identification in API
303
303
  --draft-messages Add draft messages as part of the draft creation
304
- --add-annotation-AddressViaNavigationPath Add annotation "@Common.AddressViaNavigationPath" to the services
305
- containing draft enabled entitties
304
+ --draft-user-description Add user description fields to the DraftAdministrativeData entity
306
305
  -n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is
307
306
  the corresponding database name (see "--sql-mapping" for "toSql")
308
307
  plain : (default) Names in uppercase and flattened with underscores
@@ -49,7 +49,6 @@ const PRECEDENCE_OF_EQUAL = 10;
49
49
  class AstBuildingParser extends BaseParser {
50
50
  leanConditions = {
51
51
  afterBrace: true,
52
- atRightParen: true,
53
52
  fail: true,
54
53
  };
55
54
 
@@ -203,6 +202,10 @@ class AstBuildingParser extends BaseParser {
203
202
  }
204
203
 
205
204
  addTokenToSet_( set, tokenName, val, collectKeywordsOnly ) {
205
+ if (!this.dynamic_.call) {
206
+ super.addTokenToSet_( set, tokenName, val, collectKeywordsOnly );
207
+ return;
208
+ }
206
209
  const token = parserTokens[tokenName];
207
210
  // TODO: use lower-case in specialFunctions
208
211
  const realTokens = token && this.dynamic_.generic?.[token];
@@ -215,7 +218,7 @@ class AstBuildingParser extends BaseParser {
215
218
  // Works, since `DeleteStarFromSet` comes after `*` (length-sorted):
216
219
  delete set['*'];
217
220
  }
218
- else {
221
+ else if (!token) {
219
222
  super.addTokenToSet_( set, tokenName, val, collectKeywordsOnly );
220
223
  }
221
224
  }
@@ -489,10 +492,6 @@ class AstBuildingParser extends BaseParser {
489
492
  // token is not on a new line?
490
493
  }
491
494
 
492
- atRightParen() {
493
- return this.l() !== ')';
494
- }
495
-
496
495
  /**
497
496
  * For annotations at the beginning of columns outside parentheses
498
497
  */
@@ -796,10 +796,15 @@ class CsnToCdl {
796
796
  }
797
797
 
798
798
  // Top-level annotations of the artifact
799
- let result = this.renderAnnotationAssignmentsAndDocComment(ext, env);
799
+ const topLevelAnnotations = this.renderAnnotationAssignmentsAndDocComment(ext, env.withIncreasedIndent());
800
+
800
801
  // Note: Not renderDefinitionReference, because we don't care if there
801
802
  // are annotations to unknown things. That's allowed!
802
- result += `${ env.indent }annotate ${ this.renderArtifactName(ext.annotate, env) }`;
803
+ let result = `${ env.indent }annotate ${ this.renderArtifactName(ext.annotate, env) }`;
804
+
805
+ if (topLevelAnnotations)
806
+ result += ` with\n${ topLevelAnnotations }`;
807
+
803
808
 
804
809
  if (ext.params)
805
810
  result += this.renderAnnotateParamsInParentheses(ext, env);
@@ -1368,7 +1373,7 @@ class CsnToCdl {
1368
1373
  }
1369
1374
 
1370
1375
  /**
1371
- * Render the given columns.
1376
+ * Render the given columns / select items.
1372
1377
  *
1373
1378
  * @param {CSN.Extension | CSN.QuerySelect} art
1374
1379
  * @param {object} elements
@@ -1459,6 +1464,7 @@ class CsnToCdl {
1459
1464
 
1460
1465
  const isAnonymousExpand = (obj.expand && !obj.ref);
1461
1466
 
1467
+
1462
1468
  // s as alias { * }
1463
1469
  if (obj.as && !isAnonymousExpand)
1464
1470
  result += this.renderAlias(obj.as, env);
@@ -1483,7 +1489,7 @@ class CsnToCdl {
1483
1489
  const childEnv = env.withIncreasedIndent();
1484
1490
  const expandInline = obj.expand || obj.inline;
1485
1491
  result += expandInline //
1486
- .map(elm => this.renderAnnotationAssignmentsAndDocComment(elm, childEnv) + childEnv.indent + this.renderInlineExpand(elm, childEnv))
1492
+ .map(elm => this.renderViewColumn(elm, childEnv))
1487
1493
  .join(',\n');
1488
1494
  result += `\n${ env.indent }}`;
1489
1495
 
@@ -305,10 +305,12 @@ const cdsToSqlTypes = {
305
305
  h2: {
306
306
  'cds.Binary': 'VARBINARY', // same as for plain
307
307
  'cds.LargeBinary': 'BINARY LARGE OBJECT', // BLOB would require a length!
308
- 'cds.DecimalFloat': 'DECFLOAT', // Decimal and Decimal(p) is mapped to cds.DecimalFloat
308
+ // 'cds.Decimal': 'DECFLOAT', // Decimal and Decimal(p) is mapped to cds.DecimalFloat
309
+ 'cds.DecimalFloat': 'DECFLOAT',
309
310
  'cds.DateTime': 'TIMESTAMP(0)',
310
311
  'cds.Timestamp': 'TIMESTAMP(7)',
311
312
  'cds.Map': 'JSON',
313
+ 'cds.UInt8': 'SMALLINT', // See #13870; not equivalent, but smallint can hold >byte
312
314
  },
313
315
  postgres: {
314
316
  // See <https://www.postgresql.org/docs/current/datatype.html>
@@ -317,7 +319,7 @@ const cdsToSqlTypes = {
317
319
  'cds.LargeBinary': 'BYTEA',
318
320
  'cds.Binary': 'BYTEA',
319
321
  'cds.Double': 'FLOAT8',
320
- 'cds.UInt8': 'INTEGER', // Not equivalent
322
+ 'cds.UInt8': 'SMALLINT', // See #13870; not equivalent, but smallint can hold >byte
321
323
  'cds.Vector': 'VARCHAR', // Not supported; see #11725
322
324
  'cds.Map': 'JSONB',
323
325
  },
@@ -20,7 +20,8 @@ const { pathName } = require('../../compiler/utils');
20
20
  * @returns {Function} forEachDefinition callback
21
21
  */
22
22
  function processAssertUnique( csn, options, messageFunctions ) {
23
- const { resolvePath, flattenPath } = getTransformers(csn, options, messageFunctions);
23
+ const { csnUtils, flattenPath } = getTransformers(csn, options, messageFunctions);
24
+ const { resolvePath } = csnUtils;
24
25
  const { error, info } = messageFunctions;
25
26
 
26
27
  return handleAssertUnique;
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
+ transformAnnotationExpression,
4
5
  applyTransformationsOnNonDictionary,
5
6
  applyTransformations,
6
7
  implicitAs,
@@ -106,18 +107,40 @@ function attachOnConditions( csn, csnUtils, pathDelimiter, iterateOptions = {},
106
107
 
107
108
  /**
108
109
  * @param {CSN.Model} csn
110
+ * @param {CSN.Options} options
109
111
  * @param {object} csnUtils
110
112
  * @param {string} pathDelimiter
111
113
  * @param {boolean} [processOnInQueries=false] Wether to process on-conditions in queries (joins and mixins)
112
114
  * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
113
115
  */
114
- function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries = false ) {
116
+ function getFKAccessFinalizer( csn, options, csnUtils, pathDelimiter, processOnInQueries = false ) {
115
117
  const {
116
118
  inspectRef,
117
119
  } = csnUtils;
118
120
 
119
121
  return handleManagedAssocSteps;
120
122
 
123
+ /**
124
+ *
125
+ * @param {object} obj Object with annotations.
126
+ * @param {object} transformer Annotation expression transformers.
127
+ * @param {CSN.Path} path CSN path for locations.
128
+ */
129
+ function processRefsInAnnotations(obj, transformer, path) {
130
+ Object.keys(obj)
131
+ .filter(pn => pn.startsWith('@') && obj[pn])
132
+ .forEach((anno) => {
133
+ // TODO: Ensure we only do processing here for annotations that have refs, to save time
134
+ const annoBefore = JSON.stringify(obj[anno]);
135
+ transformAnnotationExpression(obj, anno, transformer, path);
136
+ if (obj[anno].ref)
137
+ transformer.ref(obj[anno], 'ref', obj[anno].ref, path.concat(anno));
138
+ const annoAfter = JSON.stringify(obj[anno]);
139
+ if (annoBefore !== annoAfter)
140
+ obj[anno]['='] = true;
141
+ });
142
+ }
143
+
121
144
  /**
122
145
  * Loop over all elements and for all unmanaged associations translate
123
146
  * <assoc base>.<managed assoc>.<fk> to <assoc base>.<managed assoc>_<fk>
@@ -133,6 +156,11 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
133
156
  function handleManagedAssocSteps( artifact, artifactName ) {
134
157
  const transformer = getTransformer();
135
158
  const inColumnsTransformer = getTransformer(true);
159
+
160
+ if (options.transformation === 'effective')
161
+ processRefsInAnnotations(artifact, transformer, [ 'definitions', artifactName ]);
162
+
163
+
136
164
  for (const elemName in artifact.elements) {
137
165
  const elem = artifact.elements[elemName];
138
166
  // The association is an unmanaged one
@@ -140,6 +168,10 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
140
168
  applyTransformationsOnNonDictionary(elem, 'on', transformer, {}, [ 'definitions', artifactName, 'elements', elemName ]);
141
169
  else if (elem.value?.stored)
142
170
  applyTransformationsOnNonDictionary(elem, 'value', transformer, {}, [ 'definitions', artifactName, 'elements', elemName ]);
171
+
172
+ // TODO: Is this enough? I suppose that these annotations can be in places that are not an element.
173
+ if (options.transformation === 'effective')
174
+ processRefsInAnnotations(elem, transformer, [ 'definitions', artifactName, 'elements', elemName ]);
143
175
  }
144
176
 
145
177
  if (artifact.query || artifact.projection) {
@@ -165,6 +197,10 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
165
197
  };
166
198
  queryTransformers.on = transform;
167
199
  }
200
+
201
+ if (options.transformation === 'effective' || options.transformation === 'odata')
202
+ queryTransformers.xpr = transform;
203
+
168
204
  applyTransformationsOnNonDictionary(artifact, artifact.query ? 'query' : 'projection', queryTransformers, { drillRef: true }, [ 'definitions', artifactName ]);
169
205
  }
170
206
 
@@ -26,30 +26,29 @@ const { getHelpers } = require('./utils');
26
26
  * + Is it part of the source side: <path> is turned into <query source>.<path> - a leading $self is stripped-off
27
27
  * + Is it something else: Don't touch it, leave as is
28
28
  *
29
- * Given that `assoc` from above has the on-condition assoc.id = id, we would generate the following:
29
+ * Given that `assoc` from above has the on-condition `assoc.id = id`, we would generate the following:
30
30
  * - F.id = E.id
31
31
  *
32
- * The final subselect looks like (select 1 as dummy from E where F.id = E.id and filter = 100).
32
+ * The final subselect looks like `(select 1 as dummy from E where F.id = E.id and filter = 100)`.
33
33
  *
34
34
  * For a $self backlink:
35
35
  * - For $self = <assoc>.<another-assoc>, we do the following for each foreign key of <another-assoc>
36
36
  * + <assoc>.<another-assoc>.<fk> -> <assoc.target>.<another-assoc>.<fk>
37
- * + Afterwards, we get the corresponding key from the source side: <query-source>.<fk>
37
+ * + Afterward, we get the corresponding key from the source side: <query-source>.<fk>
38
38
  * + And turn this into a comparison: <assoc.target>.<another-assoc>.<fk> = <query-source>.<fk>
39
39
  *
40
40
  * So for the sample above, given an on-condition like $self = assoc.backToE, we would generate:
41
41
  * - F.backToE.id = E.id
42
42
  *
43
- * The final subselect looks like (select 1 as dummy from E where F.backToE.id = E.id and filter = 100).
43
+ * The final subselect looks like `(select 1 as dummy from E where F.backToE.id = E.id and filter = 100)`.
44
44
  *
45
45
  * @param {CSN.Model} csn
46
46
  * @param {CSN.Options} options
47
- * @param {Function} error
48
- * @param {Function} inspectRef
49
- * @param {Function} initDefinition
50
- * @param {Function} dropDefinitionCache
47
+ * @param {object} messageFunctions
48
+ * @param {object} csnUtils
51
49
  */
52
- function handleExists( csn, options, error, inspectRef, initDefinition, dropDefinitionCache ) {
50
+ function handleExists( csn, options, messageFunctions, csnUtils ) {
51
+ const { error } = messageFunctions;
53
52
  const {
54
53
  getBase,
55
54
  firstLinkIsEntityOrQuerySource,
@@ -57,17 +56,12 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
57
56
  translateManagedAssocToWhere,
58
57
  getQuerySources,
59
58
  translateUnmanagedAssocToWhere,
60
- } = getHelpers(csn, inspectRef, error);
59
+ } = getHelpers(csn, csnUtils.inspectRef, error);
60
+
61
61
  const generatedExists = new WeakMap();
62
62
  forEachDefinition(csn, (artifact, artifactName) => {
63
- // drop cache: Otherwise, the projection/query hack below won't work, because csnRefs
64
- // thinks that the artifact was already initialized (including all queries).
65
- dropDefinitionCache(artifact);
66
- if (artifact.projection) // do the same hack we do for the other stuff...
67
- artifact.query = { SELECT: artifact.projection };
68
-
69
- if (artifact.query) {
70
- forAllQueries(artifact.query, function handleExistsQuery(query, path) {
63
+ if (artifact.query || artifact.projection) {
64
+ forAllQueries(artifact.query || { SELECT: artifact.projection }, function handleExistsQuery(query, path) {
71
65
  if (!generatedExists.has(query)) {
72
66
  const toProcess = []; // Collect all expressions we need to process here
73
67
  if (query.SELECT?.where?.length > 1)
@@ -90,8 +84,8 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
90
84
  while (toProcess.length > 0) {
91
85
  const [ queryPath, exprPath ] = toProcess.pop();
92
86
  // Re-init caches for this artifact
93
- dropDefinitionCache(artifact);
94
- initDefinition(artifact);
87
+ csnUtils.dropDefinitionCache(artifact);
88
+ csnUtils.initDefinition(artifact);
95
89
  // leftovers can happen with nested exists - we then need to drill down into the created SELECT
96
90
  // to check for further exists
97
91
  const { result, leftovers } = processExists(queryPath, exprPath);
@@ -100,16 +94,10 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
100
94
  toProcess.push(...leftovers); // any leftovers - schedule for further processing
101
95
  }
102
96
  // Make sure we leave csnRefs usable
103
- dropDefinitionCache(artifact);
104
- initDefinition(artifact);
97
+ csnUtils.dropDefinitionCache(artifact);
98
+ csnUtils.initDefinition(artifact);
105
99
  }
106
- }, [ 'definitions', artifactName, 'query' ]);
107
- }
108
-
109
- if (artifact.projection) { // undo our hack
110
- artifact.projection = artifact.query.SELECT;
111
-
112
- delete artifact.query;
100
+ }, [ 'definitions', artifactName, artifact.projection ? 'projection' : 'query' ]);
113
101
  }
114
102
  });
115
103
 
@@ -168,7 +156,7 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
168
156
  }
169
157
  }
170
158
  const stack = [ [ null, startAssoc, startRest, startIndex ] ];
171
- const { links } = inspectRef(path);
159
+ const { links } = csnUtils.inspectRef(path);
172
160
  while (stack.length > 0) {
173
161
  // previous: to nest "up" if the previous assoc did not originally have a filter
174
162
  // assoc: the assoc path step
@@ -258,8 +246,9 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
258
246
  const newExpr = [];
259
247
  const query = walkCsnPath(csn, queryPath);
260
248
  const expr = walkCsnPath(csn, exprPath);
261
- const queryBase = query.SELECT.from.ref ? (query.SELECT.from.as || query.SELECT.from.ref) : null;
262
- const sources = getQuerySources(query.SELECT);
249
+ const select = query.projection ?? query.SELECT;
250
+ const queryBase = select?.from?.ref ? (select.from.as || select.from.ref) : null;
251
+ const sources = getQuerySources(select);
263
252
 
264
253
  for (let i = 0; i < expr.length; i++) {
265
254
  if (i < expr.length - 1 && expr[i] === 'exists' && expr[i + 1].ref) {
@@ -430,7 +430,7 @@ function getHelpers( csn, inspectRef, error ) {
430
430
  }
431
431
 
432
432
  /**
433
- * Use cacjed _links and _art or calculate via inspectRef
433
+ * Use cached _links and _art or calculate via inspectRef
434
434
  * @param {object} obj
435
435
  * @param {CSN.Path} objPath
436
436
  * @returns {object}
@@ -125,7 +125,7 @@ function getPersistenceTableProcessor( csn, options, messageFunctions ) {
125
125
 
126
126
  recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
127
127
  // All elements must have a type for this to work
128
- if (!member.$ignore && !member.kind && !member.type) {
128
+ if (!member.$ignore && !member.kind && !member.type && !member.elements) { // .items? Probably resolved at this point
129
129
  error(null, path, { anno: '@cds.persistence.table' },
130
130
  'Expecting element to have a type if view is annotated with $(ANNO)');
131
131
  }
@@ -38,18 +38,16 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
38
38
  columns: (parent, name, columns, path) => {
39
39
  const artifact = csn.definitions[path[1]];
40
40
  csnUtils.initDefinition(artifact); // potentially not initialized, yet
41
- if (!artifact['@cds.persistence.table']) {
42
- const root = csnUtils.get$combined({ SELECT: parent });
43
- // TODO: replace with the correct options.transformation?
44
- // Do not expand the * in OData for a moment, not to introduce changes
45
- // while the OData CSN is still official
46
- const isComplexQuery = parent.from.join !== undefined;
47
- if (!options.toOdata)
48
- parent.columns = replaceStar(root, columns, parent.excluding, isComplexQuery);
49
- // FIXME(v6): Remove argument "isComplexOrNestedQuery"; we use path.length > 4 to check
50
- // if we're inside the outermost "columns". If so, always prepend a table alias. See #11662
51
- parent.columns = expand(parent.columns, path.concat('columns'), true, isComplexQuery || path.length > 4);
52
- }
41
+ const root = csnUtils.get$combined({ SELECT: parent });
42
+ // TODO: replace with the correct options.transformation?
43
+ // Do not expand the * in OData for a moment, not to introduce changes
44
+ // while the OData CSN is still official
45
+ const isComplexQuery = parent.from.join !== undefined;
46
+ if (!options.toOdata)
47
+ parent.columns = replaceStar(root, columns, parent.excluding, isComplexQuery);
48
+ // FIXME(v6): Remove argument "isComplexOrNestedQuery"; we use path.length > 4 to check
49
+ // if we're inside the outermost "columns". If so, always prepend a table alias. See #11662
50
+ parent.columns = expand(parent.columns, path.concat('columns'), true, isComplexQuery || path.length > 4);
53
51
  },
54
52
  groupBy: (parent, name, groupBy, path) => {
55
53
  parent.groupBy = expand(groupBy, path.concat('groupBy'));
@@ -86,24 +84,22 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
86
84
  // (which is the thing inside SET/SELECT)
87
85
  // We can directly use SELECT here, as only projections and SELECT can have .columns
88
86
  const root = csnUtils.get$combined({ SELECT: parent });
89
- if (!artifact['@cds.persistence.table']) {
90
- // Make root look like normal .elements - we never cared about conflict afaik anyway
91
- Object.keys(root).forEach((key) => {
92
- root[key] = root[key][0].element;
87
+ // Make root look like normal .elements - we never cared about conflict afaik anyway
88
+ Object.keys(root).forEach((key) => {
89
+ root[key] = root[key][0].element;
90
+ });
91
+ const rewritten = rewrite(root, parent.columns, parent.excluding);
92
+ /*
93
+ * Do not remove unexpandable many columns in OData
94
+ */
95
+ if (rewritten.toMany.length > 0 && !options.toOdata) {
96
+ markAsToDummify(artifact, path[1]);
97
+ rewritten.toMany.forEach(({ art }) => {
98
+ error( null, art.$path || [ 'definitions', path[1] ], { name: `${ art.$env || path[1] }:${ art.ref.map(r => r.id || r) }` }, 'Unexpected .expand with to-many association $(NAME)');
93
99
  });
94
- const rewritten = rewrite(root, parent.columns, parent.excluding);
95
- /*
96
- * Do not remove unexpandable many columns in OData
97
- */
98
- if (rewritten.toMany.length > 0 && !options.toOdata) {
99
- markAsToDummify(artifact, path[1]);
100
- rewritten.toMany.forEach(({ art }) => {
101
- error( null, art.$path || [ 'definitions', path[1] ], { name: `${ art.$env || path[1] }:${ art.ref.map(r => r.id || r) }` }, 'Unexpected .expand with to-many association $(NAME)');
102
- });
103
- }
104
- else {
105
- parent.columns = rewritten.columns;
106
- }
100
+ }
101
+ else {
102
+ parent.columns = rewritten.columns;
107
103
  }
108
104
  },
109
105
  });
@@ -280,11 +276,13 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
280
276
  const allToMany = [];
281
277
  const newThing = [];
282
278
  const containsExpandInline = columns.some(col => col.expand || col.inline);
283
- if (containsExpandInline) // Replace stars - needs to happen before resolving .expand/.inline since the .expand/.inline first path step affects the root *
284
- columns = replaceStar(root, columns, excluding);
285
- else
279
+ if (!containsExpandInline)
286
280
  return { columns, toMany: [] };
287
281
 
282
+ // Replace stars - needs to happen before resolving .expand/.inline since the
283
+ // .expand/.inline first path step affects the root *
284
+ columns = replaceStar(root, columns, excluding);
285
+
288
286
  for (const col of columns) {
289
287
  if (col.expand || col.inline) {
290
288
  const { expanded, toManys } = expandInline(root, col, col.ref || [], col.expand ? [ dbName(col) ] : []);
@@ -364,7 +362,10 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
364
362
  }
365
363
  else if (current.on || current.cast?.on) {
366
364
  rewriteOn(current, [ currentAlias.slice(0, -1).join(pathDelimiter) ]);
367
- expanded.push(Object.assign({}, current, { ref: currentRef, as: currentAlias.join(pathDelimiter) } ));
365
+ const expandedCol = Object.assign({}, current, { as: currentAlias.join(pathDelimiter) } );
366
+ if (currentRef.length)
367
+ expandedCol.ref = currentRef;
368
+ expanded.push(expandedCol);
368
369
  }
369
370
  else if (current.val !== undefined || current.func !== undefined) {
370
371
  expanded.push(Object.assign(current, { as: currentAlias.join(pathDelimiter) }));
@@ -440,7 +441,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
440
441
  function rewriteOnCondition( on, currentRef, stack ) {
441
442
  for (let i = 0; i < on.length; i++) {
442
443
  const part = on[i];
443
- if (part.ref && part.ref[0] !== '$self' && part.ref[0] !== '$projection') {
444
+ if (part.ref && part.$scope !== '$magic' && part.$scope !== '$self' && part.$scope !== '$projection') {
444
445
  part.ref = currentRef[0] ? [ currentRef[0], ...part.ref ] : part.ref;
445
446
  on[i] = part;
446
447
  stack.push([ part, part.ref ]);
@@ -461,7 +462,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
461
462
  function rewriteSingleExpressionArray( expressionArray, currentRef, stack ) {
462
463
  for (let i = 0; i < expressionArray.length; i++) {
463
464
  const part = expressionArray[i];
464
- if (part.ref) {
465
+ if (part.ref && part.$scope !== '$magic' && part.$scope !== '$self') {
465
466
  part.ref = currentRef.concat(part.ref);
466
467
  expressionArray[i] = part;
467
468
  stack.push([ part, part.ref ]);
@@ -657,7 +658,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
657
658
  obj.ref = [ root.$env, ...obj.ref ];
658
659
 
659
660
  if (iterateOptions.keepKeysOrigin) {
660
- setProp(obj, '$originalKeyRef', { ref: root.ref, as: root.as });
661
+ setProp(obj, '$originalKeyRef', root);
661
662
  setProp(obj, '$path', root.$path);
662
663
  }
663
664