@sap/cds-compiler 6.2.2 → 6.3.4

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 (63) hide show
  1. package/CHANGELOG.md +49 -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/enricher.js +15 -3
  11. package/lib/checks/onConditions.js +2 -2
  12. package/lib/checks/queryNoDbArtifacts.js +16 -15
  13. package/lib/checks/types.js +1 -1
  14. package/lib/checks/utils.js +30 -6
  15. package/lib/checks/validator.js +36 -37
  16. package/lib/compiler/assert-consistency.js +1 -1
  17. package/lib/compiler/checks.js +47 -18
  18. package/lib/compiler/extend.js +1 -1
  19. package/lib/compiler/index.js +88 -6
  20. package/lib/compiler/populate.js +1 -1
  21. package/lib/compiler/resolve.js +7 -7
  22. package/lib/compiler/tweak-assocs.js +48 -25
  23. package/lib/edm/annotations/edmJson.js +19 -19
  24. package/lib/gen/BaseParser.js +1 -1
  25. package/lib/gen/CdlGrammar.checksum +1 -1
  26. package/lib/gen/CdlParser.js +384 -383
  27. package/lib/gen/Dictionary.json +0 -2
  28. package/lib/json/to-csn.js +3 -2
  29. package/lib/model/csnRefs.js +9 -4
  30. package/lib/model/csnUtils.js +67 -2
  31. package/lib/optionProcessor.js +2 -3
  32. package/lib/parsers/AstBuildingParser.js +12 -11
  33. package/lib/render/toCdl.js +10 -4
  34. package/lib/render/utils/common.js +4 -2
  35. package/lib/transform/db/assertUnique.js +2 -1
  36. package/lib/transform/db/associations.js +37 -1
  37. package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
  38. package/lib/transform/db/assocsToQueries/utils.js +1 -1
  39. package/lib/transform/db/cdsPersistence.js +1 -1
  40. package/lib/transform/db/expansion.js +37 -36
  41. package/lib/transform/draft/db.js +20 -20
  42. package/lib/transform/draft/odata.js +38 -40
  43. package/lib/transform/effective/associations.js +1 -1
  44. package/lib/transform/effective/flattening.js +40 -47
  45. package/lib/transform/effective/main.js +6 -4
  46. package/lib/transform/forOdata.js +135 -115
  47. package/lib/transform/forRelationalDB.js +151 -142
  48. package/lib/transform/localized.js +116 -109
  49. package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
  50. package/lib/transform/odata/createForeignKeys.js +73 -70
  51. package/lib/transform/odata/flattening.js +216 -200
  52. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
  53. package/lib/transform/odata/toFinalBaseType.js +40 -39
  54. package/lib/transform/odata/typesExposure.js +151 -133
  55. package/lib/transform/odata/utils.js +7 -6
  56. package/lib/transform/parseExpr.js +165 -162
  57. package/lib/transform/transformUtils.js +184 -551
  58. package/lib/transform/translateAssocsToJoins.js +510 -571
  59. package/lib/transform/tupleExpansion.js +495 -0
  60. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  61. package/package.json +1 -1
  62. package/lib/base/cleanSymbols.js +0 -17
  63. 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"
@@ -31,6 +31,7 @@ const normalizedKind = {
31
31
  let gensrcFlavor = true; // good enough here...
32
32
  let universalCsn = false;
33
33
  let strictMode = false; // whether to dump with unknown properties (in standard)
34
+ /** @type {boolean|string} */
34
35
  let withLocations = false;
35
36
  let withDocComments = false;
36
37
  let structXpr = false;
@@ -252,7 +253,7 @@ function compactModel( model, options = model.options || {} ) {
252
253
  const loc = srcDict[first].location;
253
254
  if (loc && loc.file) {
254
255
  Object.defineProperty( csn, '$location', {
255
- value: { file: loc.file }, configurable: true, writable: true, enumerable: withLocations,
256
+ value: { file: loc.file }, configurable: true, writable: true, enumerable: !!withLocations,
256
257
  } );
257
258
  }
258
259
  set( '$extra', csn, srcDict[first] );
@@ -611,7 +612,7 @@ function addLocation( loc, csn ) {
611
612
  val.endCol = loc.endCol;
612
613
  }
613
614
  Object.defineProperty( csn, '$location', {
614
- value: val, configurable: true, writable: true, enumerable: withLocations,
615
+ value: val, configurable: true, writable: true, enumerable: !!withLocations,
615
616
  } );
616
617
  }
617
618
  return csn;
@@ -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
  */
@@ -868,7 +867,7 @@ class AstBuildingParser extends BaseParser {
868
867
  // if (args.length !== 1) throw new CompilerAssertion()
869
868
  const sign = ixpr.args[0];
870
869
  const nval
871
- = (sign.val === '-' &&
870
+ = (sign.val === '-' &&
872
871
  expr && // expr may be null if `-` rule can't be parsed
873
872
  expr.literal === 'number' &&
874
873
  sign.location.endLine === expr.location.line &&
@@ -1167,10 +1166,12 @@ class AstBuildingParser extends BaseParser {
1167
1166
  secureParens( expr ) {
1168
1167
  const op = expr?.op?.val;
1169
1168
  const $parens = expr?.$parens;
1170
- if (!$parens || expr.query || op && op !== 'call' && op !== 'cast')
1169
+ if (!$parens || expr.query || op && op !== 'call' && op !== 'cast' && op !== 'list')
1171
1170
  return expr;
1172
- // ensure that references, literals and functions keep their surrounding parentheses
1173
- // (is for expressions the case anyway)
1171
+ // Ensure that references, literals, functions and lists keep their
1172
+ // surrounding parentheses (is for expressions the case anyway).
1173
+ // (Remark: as opposed to product types in type theory, a 1-tupel of an
1174
+ // n-tupel in SQL is different from an n-tupel → keep parens around `list`.)
1174
1175
  const location = $parens.pop();
1175
1176
  if (!$parens.length)
1176
1177
  delete expr.$parens;
@@ -1356,7 +1357,7 @@ class AstBuildingParser extends BaseParser {
1356
1357
 
1357
1358
  if (def.kind !== 'annotate') {
1358
1359
  const numDefines
1359
- = def.$duplicates.reduce( addOneForDefinition, addOneForDefinition( 0, def ) );
1360
+ = def.$duplicates.reduce( addOneForDefinition, addOneForDefinition( 0, def ) );
1360
1361
  this.handleDuplicateExtension( def, name, numDefines );
1361
1362
  for (const dup of def.$duplicates)
1362
1363
  this.handleDuplicateExtension( dup, name, numDefines );
@@ -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
  }