@sap/cds-compiler 6.3.4 → 6.4.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 (55) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +32 -0
  3. package/README.md +14 -2
  4. package/bin/cdsse.js +0 -3
  5. package/doc/CHANGELOG_BETA.md +1 -1
  6. package/doc/CHANGELOG_DEPRECATED.md +1 -1
  7. package/lib/base/message-registry.js +7 -0
  8. package/lib/base/messages.js +1 -1
  9. package/lib/base/model.js +2 -0
  10. package/lib/compiler/assert-consistency.js +1 -0
  11. package/lib/compiler/checks.js +37 -26
  12. package/lib/compiler/define.js +1 -1
  13. package/lib/compiler/extend.js +39 -50
  14. package/lib/compiler/finalize-parse-cdl.js +1 -1
  15. package/lib/compiler/lsp-api.js +1 -1
  16. package/lib/compiler/populate.js +2 -2
  17. package/lib/compiler/propagator.js +29 -6
  18. package/lib/compiler/resolve.js +13 -3
  19. package/lib/compiler/shared.js +31 -25
  20. package/lib/compiler/tweak-assocs.js +86 -28
  21. package/lib/compiler/xpr-rewrite.js +70 -38
  22. package/lib/edm/annotations/edmJson.js +206 -37
  23. package/lib/edm/csn2edm.js +13 -0
  24. package/lib/edm/edmUtils.js +2 -2
  25. package/lib/gen/BaseParser.js +106 -72
  26. package/lib/gen/CdlGrammar.checksum +1 -1
  27. package/lib/gen/CdlParser.js +1500 -1509
  28. package/lib/json/to-csn.js +8 -5
  29. package/lib/language/genericAntlrParser.js +0 -0
  30. package/lib/main.js +19 -16
  31. package/lib/model/csnRefs.js +589 -521
  32. package/lib/model/csnUtils.js +26 -7
  33. package/lib/model/enrichCsn.js +1 -0
  34. package/lib/parsers/AstBuildingParser.js +72 -27
  35. package/lib/render/toCdl.js +2 -1
  36. package/lib/render/toHdbcds.js +6 -3
  37. package/lib/render/toSql.js +5 -0
  38. package/lib/transform/db/applyTransformations.js +1 -1
  39. package/lib/transform/db/assertUnique.js +4 -1
  40. package/lib/transform/db/cdsPersistence.js +17 -18
  41. package/lib/transform/db/expansion.js +179 -3
  42. package/lib/transform/db/flattening.js +16 -5
  43. package/lib/transform/db/rewriteCalculatedElements.js +79 -283
  44. package/lib/transform/effective/main.js +8 -1
  45. package/lib/transform/forOdata.js +1 -1
  46. package/lib/transform/forRelationalDB.js +21 -80
  47. package/lib/transform/localized.js +65 -110
  48. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
  49. package/lib/transform/transformUtils.js +23 -21
  50. package/lib/transform/translateAssocsToJoins.js +7 -5
  51. package/lib/transform/tupleExpansion.js +16 -3
  52. package/package.json +1 -1
  53. package/doc/DeprecatedOptions_v2.md +0 -150
  54. package/doc/NameResolution.md +0 -837
  55. package/lib/transform/parseExpr.js +0 -415
@@ -9,7 +9,6 @@ const {
9
9
  implicitAs,
10
10
  } = require('../../model/csnUtils');
11
11
  const { getBranches } = require('./flattening');
12
- const { getColumnMap } = require('./views');
13
12
  const { cloneCsnNonDict } = require('../../model/cloneCsn');
14
13
 
15
14
  const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '$scope' ] };
@@ -23,11 +22,10 @@ const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '
23
22
  * @param {CSN.Options} options
24
23
  * @param {object} csnUtils
25
24
  * @param {string} pathDelimiter
26
- * @param {object} messageFunctions
25
+ * @param {object} _messageFunctions
27
26
  */
28
- function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter, messageFunctions ) {
27
+ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter, _messageFunctions ) {
29
28
  const { inspectRef, effectiveType } = csnUtils;
30
- const { error } = messageFunctions;
31
29
 
32
30
  const views = [];
33
31
  const entities = [];
@@ -119,9 +117,6 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
119
117
  * Rewrite calculated-elements-columns in views/projections and replace them
120
118
  * with their "root"-expression.
121
119
  *
122
- * As a first step, we ensure that all views/projections have a .columns (see {@link calculateColumns}) and that
123
- * all calculated elements are addressed explicitly and not via a * (see {@link makeAllCalculatedElementsExplicitColumns}).
124
- *
125
120
  * Then, we check the `art` of each ref for a `.value` and rewrite accordingly.
126
121
  * We need to ensure that the scope of the rewritten expressions is still correct!
127
122
  * An `id` in the `.value` needs to point to the entity containing the element,
@@ -133,73 +128,64 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
133
128
  * @param {CSN.Path} path
134
129
  */
135
130
  function rewriteInView( SELECT, elements, path ) {
136
- const containsExpandInline = hasExpandInline(SELECT);
137
- let cleanupCallbacks;
138
- if (!SELECT.columns) // needs to happen for all subqueries!
139
- cleanupCallbacks = calculateColumns(elements, SELECT);
140
- else
141
- cleanupCallbacks = makeAllCalculatedElementsExplicitColumns(elements, SELECT, containsExpandInline);
142
-
143
- const name = SELECT.from.args ? undefined : SELECT.from.as || (SELECT.from.ref && implicitAs(SELECT.from.ref));
144
-
145
- if (!containsExpandInline) {
146
- applyTransformationsOnNonDictionary({ SELECT }, 'SELECT', {
147
- ref: (parent, prop, ref, p, root) => {
148
- const {
149
- art, env, links, scope,
150
- } = getRefInfo(parent, p);
151
-
152
- // calc element publishes association, treat as regular
153
- // unmanaged association
154
- const calcElementIsAssoc = art?.value && art.target;
155
- // TODO: Calculated elements on-write
156
- if (art?.value && !art.value.stored && !calcElementIsAssoc) {
157
- const alias = parent.as || implicitAs(parent.ref);
158
- // TODO: What about other scopes? expand/inline?
159
- const value = (scope !== 'ref-target') ? absolutifyPaths(env, art, ref, links, name).value : keepAssocStepsInRef(ref, links, art).value;
160
-
161
- // Is a shallow copy enough?
162
- if (art.value.cast)
163
- root[p[p.length - 1]] = { xpr: [ value ] };
164
- else
165
- root[p[p.length - 1]] = { ...value };
131
+ expandStructSelectItems(SELECT);
132
+
133
+ const name = SELECT.from.args
134
+ ? undefined
135
+ : SELECT.from.as || (SELECT.from.ref && implicitAs(SELECT.from.ref));
136
+
137
+ applyTransformationsOnNonDictionary({ SELECT }, 'SELECT', {
138
+ ref: transformRef,
139
+ }, {}, path);
140
+
141
+ /**
142
+ * @param {object} parent
143
+ * @param {string} prop
144
+ * @param ref
145
+ * @param p
146
+ * @param root
147
+ */
148
+ function transformRef(parent, prop, ref, p, root) {
149
+ const {
150
+ art, env, links, scope,
151
+ } = getRefInfo(parent, p);
152
+
153
+ // calc element publishes association, treat as regular unmanaged association
154
+ const calcElementIsAssoc = art?.value && art.target;
155
+
156
+ if (!art?.value || art.value.stored || calcElementIsAssoc)
157
+ return;
158
+
159
+ if (scope === 'inline' || scope === 'expand') {
160
+ // Calculated elements in expand/inline are not supported, yet.
161
+ // Error is reported in expansion.js
162
+ return;
163
+ }
166
164
 
167
- if (p[p.length - 2] === 'columns')
168
- root[p[p.length - 1]].as = alias;
169
- else
170
- delete root[p[p.length - 1]].as;
165
+ const alias = parent.as || implicitAs(parent.ref);
171
166
 
172
- // If the calculated element has a type, use it. But only if the column did not have an explicit type.
173
- // Note: We should not check `art.type`, because we only need the type for columns, not filters.
174
- if (parent.cast)
175
- root[p[p.length - 1]].cast = parent.cast;
176
- else if (parent._element?.type)
177
- root[p[p.length - 1]].cast = { type: parent._element.type };
167
+ // TODO: What about other scopes? expand/inline may become relevant again if we allow calc elements in them.
168
+ const value = (scope !== 'ref-target')
169
+ ? absolutifyPaths(env, art, ref, links, name).value
170
+ : keepAssocStepsInRef(ref, links, art).value;
178
171
 
179
- // TODO: Copy annotations? May become relevant in the future
180
- }
181
- },
182
- }, {}, path);
183
- }
172
+ // TODO: Is a shallow copy enough?
173
+ root[p.at(-1)] = art.value.cast ? { xpr: [ value ] } : { ...value };
184
174
 
185
- cleanupCallbacks.forEach(fn => fn());
186
- }
175
+ if (p.at(-2) === 'columns' || p.at(-2) === 'expand')
176
+ root[p.at(-1)].as = alias;
177
+ else
178
+ delete root[p.at(-1)].as;
187
179
 
188
- /**
189
- *
190
- * @param {CSN.QuerySelect} SELECT
191
- * @returns {boolean}
192
- */
193
- function hasExpandInline( SELECT ) {
194
- if (!SELECT.columns)
195
- return false;
180
+ // If the calculated element has a type, use it. But only if the column did not have an explicit type.
181
+ // Note: We should not check `art.type`, because we only need the type for columns, not filters.
182
+ if (parent.cast)
183
+ root[p.at(-1)].cast = parent.cast;
184
+ else if (parent._element?.type)
185
+ root[p.at(-1)].cast = { type: parent._element.type };
196
186
 
197
- for (const column of SELECT.columns) {
198
- if (column.expand || column.inline)
199
- return true;
187
+ // TODO: Copy annotations? May become relevant in the future
200
188
  }
201
-
202
- return false;
203
189
  }
204
190
 
205
191
  /**
@@ -329,228 +315,38 @@ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter
329
315
  }
330
316
 
331
317
  /**
332
- * For a `view V as select from E;` or a `entity P as projection on E;` calculate and
333
- * attach the .columns if they contain a calculated element so we can rewrite them in
334
- * the later steps.
335
- *
336
- * @param {CSN.Elements} elements Artifact elements
337
- * @param {object} carrier The thing that will "carry" the columns - .SELECT or .projection
338
- * @returns {Function[]} Cleanup callbacks that remove `_`-links.
339
- */
340
- function calculateColumns( elements, carrier ) {
341
- carrier.columns = [ '*' ];
342
- const cleanupCallbacks = makeAllCalculatedElementsExplicitColumns(elements, carrier, false);
343
- if (carrier.columns.length === 1 && carrier.columns[0] === '*')
344
- delete carrier.columns;
345
- return cleanupCallbacks;
346
- }
347
-
348
- /**
349
- *
350
- * @param {CSN.QuerySelect} SELECT
351
- * @returns {object}
352
- */
353
- function getDirectlyAddressableElements( SELECT ) {
354
- const { from } = SELECT;
355
- if (from.ref) {
356
- return from._art.elements;
357
- }
358
- else if (from.SELECT) {
359
- return from.SELECT.elements;
360
- }
361
- else if (from.SET) {
362
- // args[0] could be SELECT or UNION
363
- return getDirectlyAddressableElements({ from: from.SET.args[0] });
364
- }
365
- else if (from.args) {
366
- const mergedElements = Object.create(null);
367
- for (const arg of from.args) {
368
- if (arg.ref) {
369
- for (const elementName in arg._art.elements)
370
- mergedElements[elementName] = arg._art.elements[elementName];
371
- }
372
- else if (arg.SET) {
373
- return getDirectlyAddressableElements({ from: arg.SET.args[0] });
374
- }
375
- else if (arg.SELECT) { // TODO: UNION
376
- for (const elementName in arg.SELECT.elements)
377
- mergedElements[elementName] = arg.SELECT.elements[elementName];
378
- }
379
- else if (arg.args) { // TODO: Is it safe to do recursion here?
380
- for (const subarg of arg.args) {
381
- const elements = getDirectlyAddressableElements({ from: subarg });
382
- for (const elementName in elements)
383
- mergedElements[elementName] = elements[elementName];
384
- }
385
- }
386
- else {
387
- throw new CompilerAssertion(`Unhandled arg type: ${ JSON.stringify(arg, null, 2) }`);
388
- }
389
- }
390
- return mergedElements;
391
- }
392
- throw new CompilerAssertion(`Unhandled query type: ${ JSON.stringify(SELECT, null, 2) }`);
393
- }
394
-
395
- /**
396
- * Ensure that all elements of the query that are calculated elements have an explicit column that we can rewrite.
397
- * If a field originally comes in via the *, then we need to add an explicit column for it.
318
+ * Expands all references to structures to its separate leaf elements, adding columns if needed.
398
319
  *
399
- * @param {CSN.Elements} elements
400
- * @param {CSN.QuerySelect} SELECT
401
- * @param {boolean} containsExpandInline
402
- * @returns {Function[]} Cleanup callbacks that remove `_`-links.
320
+ * @param {object} SELECT
403
321
  */
404
- function makeAllCalculatedElementsExplicitColumns( elements, SELECT, containsExpandInline ) {
405
- const cleanupCallbacks = [];
406
- const root = getDirectlyAddressableElements(SELECT);
407
- const columnMap = getColumnMap( { SELECT }, csnUtils );
408
- const hasStar = SELECT.columns.includes('*');
409
- const unfoldingMap = {};
410
- let starContainsCalculated = false;
411
- let containsCalcOnRead = false;
412
- for (const name in elements) {
413
- const originalRef = columnMap[name] && columnMap[name].ref || [ name ];
414
-
415
- if (columnMap[name] || hasStar) {
416
- let element;
417
- if (columnMap[name]?.expand || columnMap[name]?.inline)
418
- element = elements[name]; // only the direct thing in .elements has the .excluding respected properly!
419
- else
420
- element = columnMap[name]?._art || columnMap[name]?._element || root[name] || elements[name];
421
- const branches = getBranches(element, name, effectiveType, pathDelimiter); // TODO: is our elements[name] really the root[name]?
422
- if (hasCalcOnReadLeaf(branches)) {
423
- containsCalcOnRead = true;
424
- const columns = [];
425
- for (const branchName in branches) {
426
- const branch = branches[branchName];
427
- const leafElement = branch.steps[branch.steps.length - 1];
428
- if (columnMap[branchName]) { // Existing column - don't overwrite, we need $env!
429
- columns.push(columnMap[branchName]);
430
- }
431
- else {
432
- // TODO: Hm, will we have a $env in the leaf of the thing then?
433
- const column = { ref: [ ...originalRef, ...branches[branchName].ref.slice(1) ], as: branchName };
434
- setProp(column, '_element', leafElement);
435
- cleanupCallbacks.push(() => delete column._element);
436
- columns.push(column);
437
- }
438
- }
439
- if (columnMap[name]) {
440
- unfoldingMap[name] = [ false, [ ...columns ] ];
441
- }
442
- else if (hasStar) { // Via * - just append
443
- starContainsCalculated = true;
444
- unfoldingMap[name] = [ true, [ ...columns ] ];
445
- }
446
- }
447
- else {
448
- if (usesCalcOnRead(branches))
449
- containsCalcOnRead = true;
450
- if (!columnMap[name] && hasStar) { // Via * - just append
451
- unfoldingMap[name] = [ true, [ { ref: [ name ] } ] ];
452
- }
453
- else { // just a random column - keep
454
- unfoldingMap[name] = [ false, [ columnMap[name] ] ];
455
- }
322
+ function expandStructSelectItems(SELECT) {
323
+ for (let i = 0; i < SELECT.columns?.length; i++) {
324
+ const column = SELECT.columns[i];
325
+ if (column.expand)
326
+ continue; // skip expand
327
+
328
+ if (column.ref && column._element) {
329
+ const columnName = column.as || implicitAs(column.ref);
330
+ const branches = getBranches(column._element, columnName, effectiveType, pathDelimiter);
331
+ const paths = Object.keys(branches);
332
+ if (paths.length > 1 || paths[0] !== columnName) {
333
+ SELECT.columns[i] = Object.entries(branches).map(([ name, branch ]) => {
334
+ const elem = branch.steps.at(-1);
335
+ // TODO: Table alias somehow?
336
+ const ref = [ ...column.ref, ...branch.ref.slice(1) ];
337
+ const newColumn = { ref, as: name };
338
+
339
+ // If the calculated element has a type, use it.
340
+ if (elem['@Core.Computed'] && elem.type) // very crude - we could walk the branches to see if we are dealing with a real calc element?
341
+ newColumn.cast = { type: elem.type };
342
+
343
+ return newColumn;
344
+ });
456
345
  }
457
346
  }
458
347
  }
459
348
 
460
- if (containsExpandInline && containsCalcOnRead) {
461
- error('query-unsupported-calc', SELECT.$path, { '#': 'std' });
462
- }
463
- else if (containsCalcOnRead) {
464
- const newColumns = [];
465
- if (hasStar && !starContainsCalculated)
466
- newColumns.push('*');
467
- for (const name in elements) {
468
- const [ isViaStar, columns ] = unfoldingMap[name];
469
- if (isViaStar && starContainsCalculated || !isViaStar)
470
- newColumns.push(...columns);
471
- }
472
-
473
- SELECT.columns = newColumns;
474
- }
475
- return cleanupCallbacks;
476
- }
477
-
478
- /**
479
- * Returns true if any leaf node is a calculated element on-read.
480
- * On-write behaves like regular elements, hence they do not count here.
481
- *
482
- * @param {object} branches
483
- * @returns {boolean}
484
- */
485
- function hasCalcOnReadLeaf( branches ) {
486
- for (const branchName in branches) {
487
- const branch = branches[branchName].steps;
488
- const leaf = branch[branch.length - 1];
489
- if (hasOnReadValue(leaf))
490
- return true;
491
- }
492
-
493
- return false;
494
- }
495
-
496
- /**
497
- * Returns true if the branch/column uses a calc-on-read,
498
- * for example in a filter.
499
- *
500
- * TODO: Enable calculated elements next to nested projections
501
- *
502
- * @param {object} branches
503
- * @returns {boolean}
504
- */
505
- function usesCalcOnRead( branches ) {
506
- let returnValue = false;
507
- for (const branchName in branches) {
508
- const column = branches[branchName]?.steps[0]?._column;
509
- if (column) {
510
- applyTransformationsOnNonDictionary({ column }, 'column', {
511
- // eslint-disable-next-line no-loop-func
512
- ref: (parent) => {
513
- if (hasOnReadValue(parent))
514
- returnValue = true;
515
- },
516
- }, {
517
- drillRef: true,
518
- // skip subqueries and nested projections
519
- // calculated elements and nested projections
520
- // only conflict on same level
521
- skipStandard: [ 'SELECT', 'expand', 'inline' ],
522
- });
523
- }
524
- }
525
-
526
- return returnValue;
527
- }
528
-
529
- /**
530
- * A leaf can reference a column which in turn references a real element - that might have a .value.
531
- * Find such cases.
532
- *
533
- * @param {object} baseLeaf Leaf to start at
534
- * @returns {boolean}
535
- */
536
- function hasOnReadValue( baseLeaf ) {
537
- const visited = new WeakSet();
538
- const stack = [ baseLeaf ];
539
- while (stack.length > 0) {
540
- const leaf = stack.pop();
541
- if (!visited.has(leaf)) { // Don't re-process things
542
- if (leaf.value && !leaf.value.stored)
543
- return true;
544
- else if (leaf._art)
545
- stack.push(leaf._art);
546
- else if (leaf['@Core.Computed'] && leaf._column && leaf._column !== baseLeaf)
547
- stack.push(leaf._column);
548
- }
549
-
550
- visited.add(leaf);
551
- }
552
-
553
- return false;
349
+ SELECT.columns = SELECT.columns.flat(Infinity);
554
350
  }
555
351
 
556
352
  /**
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- getUtils, mergeTransformers, applyTransformations,
4
+ getUtils, mergeTransformers, applyTransformations, forEachDefinition,
5
5
  } = require('../../model/csnUtils');
6
6
  const transformUtils = require('../transformUtils');
7
7
  const effectiveFlattening = require('./flattening');
@@ -19,6 +19,8 @@ const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities }
19
19
  const { cloneFullCsn } = require('../../model/cloneCsn');
20
20
  const { featureFlags } = require('../featureFlags');
21
21
  const getServiceFilterFunction = require('./service');
22
+ const { traverseQuery } = require('../../model/csnRefs');
23
+ const { expandWildcard } = require('../db/expansion');
22
24
 
23
25
  /**
24
26
  * This is just a PoC for now!
@@ -46,6 +48,11 @@ function effectiveCsn( model, options, messageFunctions ) {
46
48
  let { csnUtils } = transformerUtils;
47
49
  csnUtils.initAllDefinitions();
48
50
 
51
+ forEachDefinition(csn, (def) => {
52
+ if (def.query || def.projection)
53
+ traverseQuery(def.query || def, null, null, query => expandWildcard(query, csnUtils, options));
54
+ });
55
+
49
56
  // Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
50
57
  const cleanup = validate.forRelationalDB(csn, {
51
58
  ...messageFunctions, csnUtils, ...csnUtils, csn, options,
@@ -210,7 +210,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
210
210
 
211
211
  // needs to be performed after creating foreign keys for the entire model,
212
212
  // because of multiple managed associations in refs
213
- replaceForeignKeyRefsInExpressionAnnotations(csn, options, messageFunctions, csnUtils, { skipArtifact: isExternalServiceMember });
213
+ replaceForeignKeyRefsInExpressionAnnotations(csn, csnUtils, { skipArtifact: isExternalServiceMember });
214
214
 
215
215
  bindCsnReferenceOnly();
216
216
 
@@ -37,6 +37,7 @@ const { getDefaultTypeLengths } = require('../render/utils/common');
37
37
  const { featureFlags } = require('./featureFlags');
38
38
  const { cloneCsnNonDict, cloneFullCsn } = require('../model/cloneCsn');
39
39
  const { processSqlServices, createServiceDummy } = require('./db/processSqlServices');
40
+ const { expandWildcard } = require('./db/expansion');
40
41
 
41
42
  // By default: Do not process non-entities/views
42
43
  function forEachDefinition(csn, cb) {
@@ -44,68 +45,12 @@ function forEachDefinition(csn, cb) {
44
45
  }
45
46
 
46
47
  /**
47
- * Return a copy of the compact CSN model with a number of transformations made for rendering
48
- * in HANA CDS style, used by 'toHana', toSql' and 'toRename'.
49
- * The behavior is controlled by the following options:
50
- * options = {
51
- * sqlMapping // See the behavior of 'sqlMapping' in toHana, toSql and toRename
52
- * }
53
- * The result model will always have 'options.forHana' set, to indicate that these transformations have happened.
54
- * The following transformations are made:
55
- * - (000) Some primitive type names are mapped to HANA type names (e.g. DateTime => UTCDateTime,
56
- * Date => LocalDate, ...).The primitive type 'UUID' is renamed to 'String' (see also 060 below).
57
- * - (001) Add a temporal where condition to views where applicable before assoc2join
58
- * - (010) (not for to.hdbcds with hdbcds names): Transform associations to joins
59
- * - (015) Draft shadow entities are generated for entities/views annotated with '@odata.draft.enabled'.
60
- * - (020) Check: in "plain" mode, quoted ids are not allowed.
61
- * (a) check in namespace declarations
62
- * (b) check in artifact/element definitions.
63
- * - (040) Abstract entities and entities 'implemented in' something are ignored, as well
64
- * as entities annotated with '@cds.persistence.skip' or '@cds.persistence.exists'.
65
- * - (050) Checks on the hierarchical model (pre-flattening)
66
- * array of, @cds.valid.from/to
67
- * - (045) The query is stripped from entities that are annotated with '@cds.persistence.table',
68
- * essentially converting views to entities.
69
- * - (060) Users of primitive type 'UUID' (which is renamed to 'String' in 000) get length 36'.
70
- * - (070) Default length N is supplied for strings if not specified.
71
- * - (080) Annotation definitions are ignored (note that annotation assignments are filtered out by toCdl).
72
- * - (090) Compositions become associations.
73
- * - (100) 'masked' is ignored (a), and attribute 'localized' is removed (b)
74
- * - (110) Actions and functions (bound or unbound) are ignored.
75
- * - (120) (a) Services become contexts.
76
- * - (130) (not for to.hdbcds with hdbcds names): Elements having structured types are flattened into
77
- * multiple elements (using '_' or '.' as name separator, depending on 'sqlMapping').
78
- * - (140) (not for to.hdbcds with hdbcds names): Managed associations get explicit ON-conditions, with
79
- * generated foreign key elements (also using '_' or '.' as name separator, depending on 'sqlMapping').
80
- * - (150) (a) Elements from inherited (included) entities are copied into the receiving entity
81
- * (b) The 'include' property is removed from entities.
82
- * - (160) Projections become views, with MIXINs for association elements (adding $projection where
83
- * appropriate for ON-conditions).
84
- * - (170) ON-conditions referring to '$self' are transformed to compare explicit keys instead.
85
- * - (180) In projections and views, ...
86
- * (a) association elements that are mixins must not be explicitly redirected
87
- * (b) MIXINs are created for association elements in the select list that are not mixins by themselves.
88
- * - (190) For all enum types, ...
89
- * (a) enum constants in defaults are replaced by their values (assuming a matching enum as element type)
90
- * (b) the enum-ness is stripped off (i.e. the enum type is replaced by its final base type).
91
- * - (200) The 'key' property is removed from all elements of types.
92
- * - (210) (not for to.hdbcds with hdbcds names): Managed associations in GROUP BY and ORDER BY are
93
- * replaced by by their foreign key fields.
94
- * - (220) Contexts that contain no artifacts or only ignored artifacts are ignored.
95
- * - (230) (only for to.hdbcds with hdbcds names): The following are rejected in views
96
- * (a) Structured elements
97
- * (b) Managed association elements
98
- * (c) Managed association entries in GROUP BY
99
- * (d) Managed association entries in ORDER BY
100
- * - (240) All artifacts (a), elements, foreign keys, parameters (b) that have a DB representation are annotated
101
- * with their database name (as '@cds.persistence.name') according to the naming convention chosen
102
- * in 'options.sqlMapping'.
103
- * - (250) Remove name space definitions again (only in forRelationalDB). Maybe we can omit inserting namespace definitions
104
- * completely (TODO)
48
+ * Transform the given `csn` into a CSN that has SQL/HANA related transformations applied,
49
+ * such as flattening, wildcard expansion, etc.
105
50
  *
106
- * @param {CSN.Model} csn
107
- * @param {CSN.Options} options
108
- * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
51
+ * @param {CSN.Model} csn
52
+ * @param {CSN.SqlOptions} options
53
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
109
54
  */
110
55
  function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
111
56
  // copy the model as we don't want to change the input model
@@ -148,6 +93,12 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
148
93
 
149
94
  ensureColumnNames(csn, options, csnUtils);
150
95
 
96
+ forEachDefinition(csn, (def) => {
97
+ // TODO: Combine query traversal with ensureColumnNames()
98
+ if (def.query || def.projection)
99
+ traverseQuery(def.query || def, null, null, query => expandWildcard(query, csnUtils, options));
100
+ });
101
+
151
102
  const dialect = options.sqlDialect;
152
103
  const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
153
104
  if (!doA2J)
@@ -185,8 +136,8 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
185
136
  expandStructsInExpression({ drillRef: true });
186
137
 
187
138
  forEachDefinition(csn, [
188
- // (001) Add a temporal where condition to views where applicable before assoc2join
189
- // assoc2join eventually rewrites the table aliases
139
+ // Add a temporal where condition to views where applicable before assoc2join
140
+ // assoc2join eventually rewrites the table aliases
190
141
  temporal.getViewDecorator(csn, messageFunctions, csnUtils, options),
191
142
  // check unique constraints - further processing is done in rewriteUniqueConstraints
192
143
  assertUnique.prepare(csn, options, messageFunctions),
@@ -242,7 +193,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
242
193
  // With flattening errors, it makes little sense to continue.
243
194
  throwWithAnyError();
244
195
 
245
- // (010) If requested, translate associations to joins
246
196
  if (doA2J)
247
197
  handleAssocToJoins();
248
198
 
@@ -287,12 +237,12 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
287
237
 
288
238
 
289
239
  forEachDefinition(csn, [
290
- // (040) Ignore entities and views that are abstract or implemented
240
+ // Ignore entities and views that are abstract or implemented
291
241
  // or carry the annotation cds.persistence.skip/exists
292
242
  // These entities are not removed from the csn, but flagged as "to be ignored"
293
243
  cdsPersistence.getAnnoProcessor(),
294
- // (050) Check @cds.valid.from/to only on entity
295
- // Views are checked in (001), unbalanced valid.from/to's or mismatching origins
244
+ // Check @cds.valid.from/to only on entity
245
+ // Views are checked in (001), unbalanced valid.from/to's or mismatching origins
296
246
  temporal.getAnnotationHandler(csn, options, pathDelimiter, messageFunctions),
297
247
  ]);
298
248
 
@@ -308,7 +258,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
308
258
  }
309
259
 
310
260
  {
311
- // (045) Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',
261
+ // Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',
312
262
  // and make them entities
313
263
  const fns = [ cdsPersistence.getPersistenceTableProcessor(csn, options, messageFunctions) ];
314
264
  // Allow using managed associations as steps in on-conditions to access their fks
@@ -338,7 +288,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
338
288
  rejectManagedAssociationsAndStructuresForHdbcdsNames.call(that, definition, path);
339
289
  }
340
290
  },
341
- // (170) Transform '$self' in backlink associations to appropriate key comparisons
291
+ // Transform '$self' in backlink associations to appropriate key comparisons
342
292
  // Must happen before draft processing because the artificial ON-conditions in generated
343
293
  // draft shadow entities have crooked '_artifact' links, confusing the backlink processing.
344
294
  // But it must also happen after flattenForeignKeys has been called for all artifacts,
@@ -371,7 +321,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
371
321
  const findAndMarkSqlServiceArtifacts = options.sqlDialect === 'hana' && options.src === 'hdi' && (csn.meta?.[featureFlags]?.$sqlService || csn.meta?.[featureFlags]?.$dummyService || csn.meta?.[featureFlags]?.$dataProductService) ? processSqlServices(csn, options) : () => {};
372
322
 
373
323
  // Apply view-specific transformations
374
- // (160) Projections now finally become views
324
+ // Projections now finally become views
375
325
  // Replace managed association in group/order by with foreign keys
376
326
  const transformEntityOrViewPass2 = getViewTransformer(csn, options, messageFunctions);
377
327
  forEachDefinition(csn, [ (artifact, artifactName) => {
@@ -382,7 +332,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
382
332
 
383
333
  if (!doA2J) {
384
334
  forEachDefinition(csn, [
385
- // (200) Strip 'key' property from type elements
335
+ // Strip 'key' property from type elements
386
336
  removeKeyPropInType,
387
337
  (artifact, artifactName) => {
388
338
  if (artifact.kind === 'type') {
@@ -697,15 +647,6 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
697
647
  // Length/Precision/Scale is done in addDefaultTypeFacets
698
648
  }
699
649
 
700
- // If 'obj' has final type 'cds.UUID' (renamed to String in 000), set its length to 36.
701
- // function setLengthForFormerUuid(obj) {
702
- // if (!obj || !obj.type)
703
- // return;
704
- // if (obj.type === 'cds.UUID' && !obj.length) {
705
- // obj.length = 36;
706
- // }
707
- // }
708
-
709
650
  /**
710
651
  * Check that required actual parameters on 'node.type' are set, that their values are in the correct range etc.
711
652