@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.
- package/CHANGELOG.md +54 -0
- package/LICENSE +32 -0
- package/README.md +14 -2
- package/bin/cdsse.js +0 -3
- package/doc/CHANGELOG_BETA.md +1 -1
- package/doc/CHANGELOG_DEPRECATED.md +1 -1
- package/lib/base/message-registry.js +7 -0
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +2 -0
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/checks.js +37 -26
- package/lib/compiler/define.js +1 -1
- package/lib/compiler/extend.js +39 -50
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/lsp-api.js +1 -1
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +29 -6
- package/lib/compiler/resolve.js +13 -3
- package/lib/compiler/shared.js +31 -25
- package/lib/compiler/tweak-assocs.js +86 -28
- package/lib/compiler/xpr-rewrite.js +70 -38
- package/lib/edm/annotations/edmJson.js +206 -37
- package/lib/edm/csn2edm.js +13 -0
- package/lib/edm/edmUtils.js +2 -2
- package/lib/gen/BaseParser.js +106 -72
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1500 -1509
- package/lib/json/to-csn.js +8 -5
- package/lib/language/genericAntlrParser.js +0 -0
- package/lib/main.js +19 -16
- package/lib/model/csnRefs.js +589 -521
- package/lib/model/csnUtils.js +26 -7
- package/lib/model/enrichCsn.js +1 -0
- package/lib/parsers/AstBuildingParser.js +72 -27
- package/lib/render/toCdl.js +2 -1
- package/lib/render/toHdbcds.js +6 -3
- package/lib/render/toSql.js +5 -0
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +4 -1
- package/lib/transform/db/cdsPersistence.js +17 -18
- package/lib/transform/db/expansion.js +179 -3
- package/lib/transform/db/flattening.js +16 -5
- package/lib/transform/db/rewriteCalculatedElements.js +79 -283
- package/lib/transform/effective/main.js +8 -1
- package/lib/transform/forOdata.js +1 -1
- package/lib/transform/forRelationalDB.js +21 -80
- package/lib/transform/localized.js +65 -110
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
- package/lib/transform/transformUtils.js +23 -21
- package/lib/transform/translateAssocsToJoins.js +7 -5
- package/lib/transform/tupleExpansion.js +16 -3
- package/package.json +1 -1
- package/doc/DeprecatedOptions_v2.md +0 -150
- package/doc/NameResolution.md +0 -837
- 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}
|
|
25
|
+
* @param {object} _messageFunctions
|
|
27
26
|
*/
|
|
28
|
-
function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter,
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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 {
|
|
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
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
*
|
|
48
|
-
*
|
|
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}
|
|
107
|
-
* @param {CSN.
|
|
108
|
-
* @param {object}
|
|
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
|
-
//
|
|
189
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
295
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|