@sap/cds-compiler 3.7.2 → 3.8.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 +71 -4
- package/bin/cdsc.js +3 -0
- package/doc/CHANGELOG_ARCHIVE.md +6 -6
- package/doc/CHANGELOG_BETA.md +15 -0
- package/doc/DeprecatedOptions_v2.md +1 -1
- package/doc/NameResolution.md +1 -1
- package/lib/api/main.js +61 -22
- package/lib/api/options.js +1 -0
- package/lib/api/validate.js +5 -0
- package/lib/base/dictionaries.js +5 -3
- package/lib/base/keywords.js +2 -0
- package/lib/base/message-registry.js +64 -22
- package/lib/base/messages.js +12 -7
- package/lib/base/model.js +3 -2
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/defaultValues.js +1 -1
- package/lib/checks/hasPersistedElements.js +1 -1
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/onConditions.js +9 -6
- package/lib/checks/sql-snippets.js +2 -2
- package/lib/checks/types.js +1 -2
- package/lib/compiler/assert-consistency.js +25 -6
- package/lib/compiler/base.js +51 -2
- package/lib/compiler/builtins.js +15 -6
- package/lib/compiler/checks.js +4 -4
- package/lib/compiler/define.js +59 -80
- package/lib/compiler/extend.js +717 -498
- package/lib/compiler/finalize-parse-cdl.js +4 -3
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/kick-start.js +2 -2
- package/lib/compiler/populate.js +17 -9
- package/lib/compiler/propagator.js +12 -5
- package/lib/compiler/resolve.js +26 -173
- package/lib/compiler/shared.js +20 -58
- package/lib/compiler/tweak-assocs.js +1 -1
- package/lib/compiler/utils.js +2 -2
- package/lib/edm/annotations/genericTranslation.js +124 -46
- package/lib/edm/csn2edm.js +22 -1
- package/lib/edm/edmPreprocessor.js +41 -21
- package/lib/gen/Dictionary.json +4 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageLexer.js +1 -1
- package/lib/gen/languageParser.js +4844 -4508
- package/lib/inspect/inspectPropagation.js +20 -36
- package/lib/json/from-csn.js +56 -7
- package/lib/json/to-csn.js +71 -110
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +49 -9
- package/lib/language/language.g4 +106 -83
- package/lib/language/textUtils.js +13 -0
- package/lib/main.d.ts +43 -3
- package/lib/main.js +4 -2
- package/lib/model/csnRefs.js +19 -4
- package/lib/model/csnUtils.js +11 -74
- package/lib/model/revealInternalProperties.js +3 -0
- package/lib/optionProcessor.js +3 -0
- package/lib/render/toCdl.js +203 -104
- package/lib/render/toHdbcds.js +0 -1
- package/lib/render/toRename.js +14 -51
- package/lib/transform/braceExpression.js +6 -0
- package/lib/transform/db/rewriteCalculatedElements.js +55 -14
- package/lib/transform/forOdataNew.js +20 -15
- package/lib/transform/forRelationalDB.js +21 -14
- package/lib/transform/parseExpr.js +2 -0
- package/lib/transform/transformUtilsNew.js +36 -9
- package/lib/transform/translateAssocsToJoins.js +11 -4
- package/lib/transform/universalCsn/coreComputed.js +15 -7
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +2 -1
package/lib/render/toRename.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
const { makeMessageFunction } = require('../base/messages');
|
|
5
5
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
6
|
-
const {
|
|
6
|
+
const { forEachDefinition } = require('../model/csnUtils');
|
|
7
7
|
const { optionProcessor } = require('../optionProcessor');
|
|
8
8
|
const { isBetaEnabled } = require('../base/model');
|
|
9
9
|
const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');
|
|
10
|
+
const { getIdentifierUtils } = require('./utils/sql');
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -46,6 +47,9 @@ function toRename( inputCsn, options ) {
|
|
|
46
47
|
|
|
47
48
|
// FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to forRelationalDB)
|
|
48
49
|
const csn = transformForRelationalDBWithCsn(inputCsn, options, 'to.rename');
|
|
50
|
+
const hdbcdsOrQuotedIdentifiers = getIdentifierUtils(csn, options);
|
|
51
|
+
const plainIdentifiers = getIdentifierUtils(csn, { sqlDialect: 'hana', sqlMapping: 'plain' });
|
|
52
|
+
|
|
49
53
|
// forRelationalDB looses empty contexts and services, add them again so that toRename can calculate the namespaces
|
|
50
54
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
51
55
|
if ((artifact.kind === 'context' || artifact.kind === 'service') && csn.definitions[artifactName] === undefined)
|
|
@@ -82,10 +86,12 @@ function toRename( inputCsn, options ) {
|
|
|
82
86
|
function renameTableAndColumns( artifactName, art ) {
|
|
83
87
|
let resultStr = '';
|
|
84
88
|
if (art.kind === 'entity' && !art.query) {
|
|
85
|
-
const beforeTableName =
|
|
86
|
-
const afterTableName =
|
|
89
|
+
const beforeTableName = hdbcdsOrQuotedIdentifiers.renderArtifactName(artifactName);
|
|
90
|
+
const afterTableName = plainIdentifiers.renderArtifactName(artifactName);
|
|
87
91
|
|
|
88
|
-
if (beforeTableName
|
|
92
|
+
if (beforeTableName.toUpperCase() === `"${afterTableName}"`)
|
|
93
|
+
resultStr += ` --EXEC 'RENAME TABLE ${beforeTableName} TO ${afterTableName}';\n`;
|
|
94
|
+
else if (beforeTableName !== afterTableName)
|
|
89
95
|
resultStr += ` EXEC 'RENAME TABLE ${beforeTableName} TO ${afterTableName}';\n`;
|
|
90
96
|
|
|
91
97
|
|
|
@@ -93,13 +99,14 @@ function toRename( inputCsn, options ) {
|
|
|
93
99
|
const e = art.elements[name];
|
|
94
100
|
let str = '';
|
|
95
101
|
|
|
96
|
-
const beforeColumnName = quoteSqlId(name);
|
|
97
|
-
const afterColumnName =
|
|
102
|
+
const beforeColumnName = hdbcdsOrQuotedIdentifiers.quoteSqlId(name);
|
|
103
|
+
const afterColumnName = plainIdentifiers.quoteSqlId(name);
|
|
98
104
|
|
|
99
105
|
if (!e._ignore) {
|
|
100
106
|
if (e.target)
|
|
101
107
|
str = ` EXEC 'ALTER TABLE ${afterTableName} DROP ASSOCIATION ${beforeColumnName}';\n`;
|
|
102
|
-
|
|
108
|
+
else if (beforeColumnName.toUpperCase() === `"${afterColumnName}"` ) // Basically a no-op - render commented out
|
|
109
|
+
str = ` --EXEC 'RENAME COLUMN ${afterTableName}.${beforeColumnName} TO ${afterColumnName}';\n`;
|
|
103
110
|
else if (beforeColumnName !== afterColumnName)
|
|
104
111
|
str = ` EXEC 'RENAME COLUMN ${afterTableName}.${beforeColumnName} TO ${afterColumnName}';\n`;
|
|
105
112
|
}
|
|
@@ -108,50 +115,6 @@ function toRename( inputCsn, options ) {
|
|
|
108
115
|
}
|
|
109
116
|
return resultStr;
|
|
110
117
|
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Return 'name' in the form of an absolute CDS name - for the 'hdbcds' naming convention,
|
|
114
|
-
* this means converting '.' to '::' on the border between namespace and top-level artifact.
|
|
115
|
-
* For all other naming conventions, this is a no-op.
|
|
116
|
-
*
|
|
117
|
-
* @param {string} name Name to absolutify
|
|
118
|
-
* @returns {string} Absolute name
|
|
119
|
-
*/
|
|
120
|
-
function absoluteCdsName( name ) {
|
|
121
|
-
if (options.sqlMapping !== 'hdbcds')
|
|
122
|
-
return name;
|
|
123
|
-
|
|
124
|
-
const namespaceName = getNamespace(inputCsn, name);
|
|
125
|
-
if (namespaceName)
|
|
126
|
-
return `${namespaceName}::${name.substring(namespaceName.length + 1)}`;
|
|
127
|
-
|
|
128
|
-
return name;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Return 'name' with appropriate "-quotes, also replacing '::' by '.' if 'options.sqlMapping'
|
|
133
|
-
* is 'quoted'
|
|
134
|
-
*
|
|
135
|
-
* @param {string} name Name to quote
|
|
136
|
-
* @returns {string} Quoted string
|
|
137
|
-
*/
|
|
138
|
-
function quoteSqlId( name ) {
|
|
139
|
-
if (options.sqlMapping === 'quoted')
|
|
140
|
-
name = name.replace(/::/g, '.');
|
|
141
|
-
|
|
142
|
-
return `"${name.replace(/"/g, '""')}"`;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Return 'name' with uppercasing and appropriate "-quotes, also replacing '::' and '.' by '_'
|
|
147
|
-
* (to be used by 'plain' naming convention).
|
|
148
|
-
*
|
|
149
|
-
* @param {string} name Name to turn into a plain identifier
|
|
150
|
-
* @returns {string} A plain SQL identifier
|
|
151
|
-
*/
|
|
152
|
-
function plainSqlId( name ) {
|
|
153
|
-
return `"${name.toUpperCase().replace(/(::|\.)/g, '_').replace(/"/g, '""')}"`;
|
|
154
|
-
}
|
|
155
118
|
}
|
|
156
119
|
|
|
157
120
|
module.exports = {
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// Currently unused, but may become useful again if HDBCDS -> HDBTABLE
|
|
2
|
+
// handover becomes more prominent. Historically used with the no longer
|
|
3
|
+
// existent option `--compatibility`.
|
|
4
|
+
// If necessary, more complex expressions could be parsed with parseExpr.js
|
|
5
|
+
// and then stringified with parentheses again.
|
|
6
|
+
|
|
1
7
|
'use strict';
|
|
2
8
|
|
|
3
9
|
function isAlreadyBraced(expression, start, end){
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { setProp } = require('../../base/model');
|
|
4
4
|
const { CompilerAssertion } = require('../../base/error');
|
|
5
5
|
const {
|
|
6
6
|
forEachDefinition, applyTransformationsOnNonDictionary, applyTransformationsOnDictionary, implicitAs, cloneCsnNonDict, getUtils,
|
|
7
7
|
} = require('../../model/csnUtils');
|
|
8
8
|
const { getBranches } = require('./flattening');
|
|
9
9
|
const { getColumnMap } = require('./views');
|
|
10
|
+
|
|
10
11
|
const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '$scope' ] };
|
|
11
12
|
/**
|
|
12
13
|
* Rewrite usage of calculated Elements into the expression itself.
|
|
@@ -18,9 +19,6 @@ const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '
|
|
|
18
19
|
* @param {Function} error
|
|
19
20
|
*/
|
|
20
21
|
function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error ) {
|
|
21
|
-
if (!isBetaEnabled(options, 'calculatedElements'))
|
|
22
|
-
return;
|
|
23
|
-
|
|
24
22
|
const { inspectRef, effectiveType } = getUtils(csn, 'init-all');
|
|
25
23
|
|
|
26
24
|
const views = [];
|
|
@@ -107,7 +105,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
107
105
|
else
|
|
108
106
|
makeAllCalculatedElementsExplicitColumns(elements, SELECT, containsExpandInline);
|
|
109
107
|
|
|
110
|
-
const name = SELECT.from.args ? undefined : SELECT.from.as || implicitAs(SELECT.from.ref);
|
|
108
|
+
const name = SELECT.from.args ? undefined : SELECT.from.as || (SELECT.from.ref && implicitAs(SELECT.from.ref));
|
|
111
109
|
|
|
112
110
|
if (!containsExpandInline) {
|
|
113
111
|
applyTransformationsOnNonDictionary({ SELECT }, 'SELECT', {
|
|
@@ -119,7 +117,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
119
117
|
if (art?.value) {
|
|
120
118
|
const alias = parent.as || implicitAs(parent.ref);
|
|
121
119
|
// TODO: What about other scopes? expand/inline?
|
|
122
|
-
const value = (scope !== 'ref-target') ? absolutifyPaths(env, art, ref, links, name).value : art.value;
|
|
120
|
+
const value = (scope !== 'ref-target') ? absolutifyPaths(env, art, ref, links, name).value : keepAssocStepsInRef(ref, links, art).value;
|
|
123
121
|
|
|
124
122
|
// Is a shallow copy enough?
|
|
125
123
|
if (art.value.cast)
|
|
@@ -146,8 +144,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
146
144
|
if (!SELECT.columns)
|
|
147
145
|
return false;
|
|
148
146
|
|
|
149
|
-
for (
|
|
150
|
-
const column = SELECT.columns[i];
|
|
147
|
+
for (const column of SELECT.columns) {
|
|
151
148
|
if (column.expand || column.inline)
|
|
152
149
|
return true;
|
|
153
150
|
}
|
|
@@ -307,7 +304,9 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
307
304
|
return from.SELECT.elements;
|
|
308
305
|
}
|
|
309
306
|
else if (from.SET) {
|
|
310
|
-
|
|
307
|
+
// FIXME: Check if this is correct
|
|
308
|
+
// args[0] could be SELECT or UNION
|
|
309
|
+
return getDirectlyAdressableElements({ from: from.SET.args[0] });
|
|
311
310
|
}
|
|
312
311
|
else if (from.args) {
|
|
313
312
|
const mergedElements = Object.create(null);
|
|
@@ -316,10 +315,21 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
316
315
|
for (const elementName in arg._art.elements)
|
|
317
316
|
mergedElements[elementName] = arg._art.elements[elementName];
|
|
318
317
|
}
|
|
318
|
+
else if (arg.SET) {
|
|
319
|
+
// FIXME: Check if this is correct
|
|
320
|
+
return getDirectlyAdressableElements({ from: arg.SET.args[0] });
|
|
321
|
+
}
|
|
319
322
|
else if (arg.SELECT) { // TODO: UNION
|
|
320
323
|
for (const elementName in arg.SELECT.elements)
|
|
321
324
|
mergedElements[elementName] = arg.SELECT.elements[elementName];
|
|
322
325
|
}
|
|
326
|
+
else if (arg.args) { // TODO: Is it safe to do recursion here?
|
|
327
|
+
for (const subarg of arg.args) {
|
|
328
|
+
const elements = getDirectlyAdressableElements({ from: subarg });
|
|
329
|
+
for (const elementName in elements)
|
|
330
|
+
mergedElements[elementName] = elements[elementName];
|
|
331
|
+
}
|
|
332
|
+
}
|
|
323
333
|
else {
|
|
324
334
|
throw new CompilerAssertion(`Unhandled arg type: ${JSON.stringify(arg, null, 2)}`);
|
|
325
335
|
}
|
|
@@ -441,6 +451,40 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
441
451
|
return false;
|
|
442
452
|
}
|
|
443
453
|
|
|
454
|
+
/**
|
|
455
|
+
* We need to keep association steps in front of the paths - else they would lead into nothing
|
|
456
|
+
*
|
|
457
|
+
* @param {Array} artRef
|
|
458
|
+
* @param {Array} links
|
|
459
|
+
* @param {object} art
|
|
460
|
+
* @returns {object}
|
|
461
|
+
*/
|
|
462
|
+
function keepAssocStepsInRef( artRef, links, art ) {
|
|
463
|
+
let lastAssocIndex = -1;
|
|
464
|
+
for (let i = links.length - 1; i > -1; i--) {
|
|
465
|
+
if (links[i].art.target) {
|
|
466
|
+
lastAssocIndex = i;
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (lastAssocIndex > -1) {
|
|
472
|
+
const clone = { value: cloneCsnNonDict(art.value, cloneCsnOptions) };
|
|
473
|
+
applyTransformationsOnNonDictionary(clone, 'value', {
|
|
474
|
+
ref: (parent, prop, ref) => {
|
|
475
|
+
parent.ref = [ ...artRef.slice(0, lastAssocIndex + 1), ...ref ];
|
|
476
|
+
if (parent._links)
|
|
477
|
+
parent._links = [ ...links.slice(0, lastAssocIndex + 1), ...parent._links ];
|
|
478
|
+
},
|
|
479
|
+
}, {
|
|
480
|
+
skipStandard: { where: true }, // Do not rewrite refs inside of an association-where
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
return clone;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return art;
|
|
487
|
+
}
|
|
444
488
|
|
|
445
489
|
/**
|
|
446
490
|
* In order to just replace them in views, our calculated elements need to reference absolute things, i.e. have a table alias in front!
|
|
@@ -508,13 +552,10 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
508
552
|
}
|
|
509
553
|
|
|
510
554
|
/**
|
|
511
|
-
*
|
|
512
555
|
* @param {CSN.Model} csn
|
|
513
|
-
* @param {CSN.Options}
|
|
556
|
+
* @param {CSN.Options} _options
|
|
514
557
|
*/
|
|
515
|
-
function processCalculatedElementsInEntities( csn,
|
|
516
|
-
if (!isBetaEnabled(options, 'calculatedElements'))
|
|
517
|
-
return;
|
|
558
|
+
function processCalculatedElementsInEntities( csn, _options ) {
|
|
518
559
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
519
560
|
if (artifact.kind === 'entity' && !(artifact.query || artifact.projection))
|
|
520
561
|
killInEntity(artifact, [ 'definitions', artifactName ]);
|
|
@@ -60,7 +60,7 @@ const { addLocalizationViews } = require('./localized');
|
|
|
60
60
|
// - Mark fields with @odata.on.insert/update as @Core.Computed
|
|
61
61
|
// (EdmPreproc candidate, check with RT if @Core.Computed required by them)
|
|
62
62
|
// - Rename shorthand annotations according to a builtin list (EdmPreproc Candidate)
|
|
63
|
-
// e.g. @label -> @Common.Label
|
|
63
|
+
// e.g. @label -> @Common.Label
|
|
64
64
|
// - If the association target is annotated with @cds.odata.valuelist, annotate the
|
|
65
65
|
// association with @Common.ValueList.viaAssociation (EdmPreproc Candidate)
|
|
66
66
|
// - Check for @Analytics.Measure and @Aggregation.default (Linter check candidate, remove)
|
|
@@ -315,31 +315,36 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
315
315
|
// list.
|
|
316
316
|
function renameShorthandAnnotations(node) {
|
|
317
317
|
// FIXME: Verify this list - are they all still required? Do we need any more?
|
|
318
|
-
const
|
|
318
|
+
const setMappings = {
|
|
319
319
|
'@label': '@Common.Label',
|
|
320
320
|
'@title': '@Common.Label',
|
|
321
321
|
'@description': '@Core.Description',
|
|
322
|
+
};
|
|
323
|
+
const renameMappings = {
|
|
322
324
|
'@ValueList.entity': '@Common.ValueList.entity',
|
|
323
325
|
'@ValueList.type': '@Common.ValueList.type',
|
|
324
326
|
'@Capabilities.Deletable': '@Capabilities.DeleteRestrictions.Deletable',
|
|
325
327
|
'@Capabilities.Insertable': '@Capabilities.InsertRestrictions.Insertable',
|
|
326
328
|
'@Capabilities.Updatable': '@Capabilities.UpdateRestrictions.Updatable',
|
|
327
329
|
'@Capabilities.Readable': '@Capabilities.ReadRestrictions.Readable',
|
|
328
|
-
}
|
|
330
|
+
};
|
|
329
331
|
|
|
330
|
-
const
|
|
332
|
+
const setShortCuts = Object.keys(setMappings);
|
|
333
|
+
const renameShortCuts = Object.keys(renameMappings);
|
|
331
334
|
Object.keys(node).forEach( name => {
|
|
335
|
+
if (!name.startsWith('@'))
|
|
336
|
+
return;
|
|
332
337
|
// Rename according to map above
|
|
333
|
-
const
|
|
334
|
-
if(
|
|
335
|
-
renameAnnotation(node, name, name.replace(
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
338
|
+
const renamePrefix = (name in renameMappings) ? name : renameShortCuts.find(p => name.startsWith(p + '.'));
|
|
339
|
+
if(renamePrefix) {
|
|
340
|
+
renameAnnotation(node, name, name.replace(renamePrefix, renameMappings[renamePrefix]));
|
|
341
|
+
} else {
|
|
342
|
+
// The two mappings have no overlap, so no need to check for second map if first matched.
|
|
343
|
+
// Rename according to map above
|
|
344
|
+
const setPrefix = (name in setMappings) ? name : setShortCuts.find(p => name.startsWith(p + '.'));
|
|
345
|
+
if(setPrefix) {
|
|
346
|
+
setAnnotation(node, name.replace(setPrefix, setMappings[setPrefix]), node[name]);
|
|
347
|
+
}
|
|
343
348
|
}
|
|
344
349
|
|
|
345
350
|
// Special case: '@readonly' becomes a triplet of capability restrictions for entities,
|
|
@@ -351,7 +356,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
351
356
|
setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false);
|
|
352
357
|
setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
|
|
353
358
|
} else {
|
|
354
|
-
|
|
359
|
+
setAnnotation(node, '@Core.Computed', true);
|
|
355
360
|
}
|
|
356
361
|
}
|
|
357
362
|
// @insertonly is effective on entities/queries only
|
|
@@ -9,7 +9,7 @@ const { cloneCsnNonDict,
|
|
|
9
9
|
const { makeMessageFunction } = require('../base/messages');
|
|
10
10
|
const transformUtils = require('./transformUtilsNew');
|
|
11
11
|
const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');
|
|
12
|
-
const { csnRefs, pathId } = require('../model/csnRefs');
|
|
12
|
+
const { csnRefs, pathId, traverseQuery } = require('../model/csnRefs');
|
|
13
13
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
14
14
|
const validate = require('../checks/validator');
|
|
15
15
|
const { rejectManagedAssociationsAndStructuresForHdbcdsNames } = require('../checks/selectItems');
|
|
@@ -127,8 +127,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
127
127
|
get$combined,
|
|
128
128
|
getCsnDef,
|
|
129
129
|
isAssocOrComposition,
|
|
130
|
-
addStringAnnotationTo
|
|
131
|
-
cloneWithTransformations;
|
|
130
|
+
addStringAnnotationTo;
|
|
132
131
|
// transformUtils
|
|
133
132
|
let addDefaultTypeFacets,
|
|
134
133
|
expandStructsInExpression,
|
|
@@ -175,11 +174,9 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
175
174
|
|
|
176
175
|
throwWithAnyError();
|
|
177
176
|
|
|
178
|
-
// FIXME: This does something very similar to cloneWithTransformations -> refactor?
|
|
179
177
|
const transformCsn = transformUtils.transformModel;
|
|
180
178
|
|
|
181
|
-
|
|
182
|
-
forEachDefinition(csn, [
|
|
179
|
+
forEachDefinition(csn, [
|
|
183
180
|
// (001) Add a temporal where condition to views where applicable before assoc2join
|
|
184
181
|
// assoc2join eventually rewrites the table aliases
|
|
185
182
|
temporal.getViewDecorator(csn, {info}, csnUtils),
|
|
@@ -459,7 +456,6 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
459
456
|
getCsnDef,
|
|
460
457
|
isAssocOrComposition,
|
|
461
458
|
addStringAnnotationTo,
|
|
462
|
-
cloneWithTransformations
|
|
463
459
|
} = csnUtils);
|
|
464
460
|
}
|
|
465
461
|
|
|
@@ -540,6 +536,20 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
540
536
|
if (!artifact._ignore) {
|
|
541
537
|
// Do things specific for entities and views (pass 2)
|
|
542
538
|
if ((artifact.kind === 'entity') && artifact.query) {
|
|
539
|
+
|
|
540
|
+
// First pass: Set alias name for SELECTs without table alias. Required for setting proper table aliases
|
|
541
|
+
// for HDBCDS in naming mode HDBCDS. We use the same schema as the core-compiler, so duplicates should
|
|
542
|
+
// have already been reported.
|
|
543
|
+
let selectDepth = 0;
|
|
544
|
+
traverseQuery(artifact.query, null, null, (query, fromSelect) => {
|
|
545
|
+
if (!query.ref && !query.as && fromSelect) {
|
|
546
|
+
// Use +1; for UNION, it's the next select, for SELECT, it's increased later.
|
|
547
|
+
query.as = `$_select_${selectDepth + 1}__`;
|
|
548
|
+
}
|
|
549
|
+
if (query.SELECT)
|
|
550
|
+
++selectDepth;
|
|
551
|
+
});
|
|
552
|
+
|
|
543
553
|
const process = (parent, prop, query, path) => {
|
|
544
554
|
transformEntityOrViewPass2(parent, artifact, artifactName, path.concat(prop))
|
|
545
555
|
replaceAssociationsInGroupByOrderBy(parent, options, inspectRef, error, path.concat(prop));
|
|
@@ -987,25 +997,22 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
987
997
|
// this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
|
|
988
998
|
elemName = elemName.replace(/\./g, pathDelimiter);
|
|
989
999
|
const assocName = originalAssocName.replace(/\./g, pathDelimiter);
|
|
990
|
-
// clone the onCond for later use in the path transformation
|
|
991
|
-
|
|
992
|
-
const newOnCond = cloneWithTransformations(assoc.on, {
|
|
993
|
-
ref: (value) => cloneWithTransformations(value, {}),
|
|
994
|
-
});
|
|
1000
|
+
// clone the onCond for later use in the path transformation
|
|
1001
|
+
const newOnCond = cloneCsnNonDict(assoc.on, options);
|
|
995
1002
|
applyTransformationsOnNonDictionary({on: newOnCond}, 'on', {
|
|
996
1003
|
ref: (parent, prop, ref) => {
|
|
997
1004
|
if (ref[0] === assocName) // we are in the "path" from the forwarding assoc => need to remove the first part of the path
|
|
998
1005
|
{
|
|
999
1006
|
ref.shift();
|
|
1000
1007
|
} else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
|
|
1001
|
-
// We could also have a $self
|
|
1008
|
+
// We could also have a $self in front of the assoc name - so we would need to shift twice
|
|
1002
1009
|
ref.shift();
|
|
1003
1010
|
ref.shift();
|
|
1004
1011
|
}
|
|
1005
1012
|
else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
|
|
1006
1013
|
ref.unshift(elemName);
|
|
1007
1014
|
// if there was a $self identifier in the forwarding association onCond
|
|
1008
|
-
// we do not need it
|
|
1015
|
+
// we do not need it anymore, as we prepended in the previous step the back association's id
|
|
1009
1016
|
if (ref[1] === '$self')
|
|
1010
1017
|
ref.splice(1, 1);
|
|
1011
1018
|
}
|
|
@@ -346,6 +346,8 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
346
346
|
|
|
347
347
|
function isSimpleAnnoValue(val) {
|
|
348
348
|
// Expressions as annotation values always have a `=` and another property.
|
|
349
|
+
// TODO: There must be at least one known expression property, otherwise
|
|
350
|
+
// it could be `type: 'unchecked'`.
|
|
349
351
|
return !val?.['='] || Object.keys(val) < 2;
|
|
350
352
|
}
|
|
351
353
|
|
|
@@ -12,7 +12,6 @@ const { cloneCsnNonDict, cloneCsnDictionary, getUtils } = require('../model/csnU
|
|
|
12
12
|
const { typeParameters, isBuiltinType } = require('../compiler/builtins');
|
|
13
13
|
const { ModelError, CompilerAssertion} = require('../base/error');
|
|
14
14
|
const { forEach } = require('../utils/objectUtils');
|
|
15
|
-
const { pathName } = require('../compiler/utils');
|
|
16
15
|
|
|
17
16
|
const RestrictedOperators = ['<', '>', '>=', '<='];
|
|
18
17
|
const RelationalOperators = ['=', '!=', '<>', 'is' /*, 'like'*/,...RestrictedOperators];
|
|
@@ -1160,26 +1159,41 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1160
1159
|
const xrefvalues = Object.values(xref);
|
|
1161
1160
|
let cont = true;
|
|
1162
1161
|
|
|
1162
|
+
const prefix = (lhs, op, rhs) => {
|
|
1163
|
+
return `${lhsIsVal ? lhs.val : lhs.ref.join('.')} ${op} ${rhsIsVal ? rhs : rhs.ref.join('.')}`
|
|
1164
|
+
}
|
|
1163
1165
|
if(op === 'like' && xrefvalues.reduce((a, v) => {
|
|
1164
1166
|
return (v.lhs && v.rhs) ? a + 1: a;
|
|
1165
1167
|
}, 0) === 0) {
|
|
1166
1168
|
// error if intersection of paths is zero
|
|
1167
|
-
error(null, location,
|
|
1168
|
-
|
|
1169
|
+
error(null, location,
|
|
1170
|
+
{
|
|
1171
|
+
prefix: prefix(lhs, op, rhs)
|
|
1172
|
+
},
|
|
1173
|
+
'Expected compatible types for $(PREFIX)');
|
|
1169
1174
|
cont = false;
|
|
1170
1175
|
}
|
|
1171
1176
|
|
|
1172
1177
|
cont && xrefkeys.forEach(xn => {
|
|
1173
1178
|
const x = xref[xn];
|
|
1174
|
-
const prefix = `${pathName(lhs.ref)} ${op} ${pathName(rhs.ref)}`;
|
|
1175
1179
|
// do the paths match?
|
|
1176
1180
|
if(op !== 'like' && !(x.lhs && x.rhs)) {
|
|
1177
1181
|
if(xn.length) {
|
|
1178
|
-
error(null, location,
|
|
1182
|
+
error(null, location,
|
|
1183
|
+
{
|
|
1184
|
+
prefix: prefix(lhs, op, rhs),
|
|
1185
|
+
name: xn,
|
|
1186
|
+
alias: (x.lhs ? rhs : lhs).ref.join('.')
|
|
1187
|
+
},
|
|
1179
1188
|
'$(PREFIX): Sub path $(NAME) not found in $(ALIAS)');
|
|
1180
1189
|
}
|
|
1181
1190
|
else {
|
|
1182
|
-
error(null, location,
|
|
1191
|
+
error(null, location,
|
|
1192
|
+
{
|
|
1193
|
+
prefix: prefix(lhs, op, rhs),
|
|
1194
|
+
name: (x.lhs ? lhs : rhs).ref.join('.'),
|
|
1195
|
+
alias: (x.lhs ? rhs : lhs).ref.join('.')
|
|
1196
|
+
},
|
|
1183
1197
|
'$(PREFIX): Path $(NAME) does not match $(ALIAS)');
|
|
1184
1198
|
}
|
|
1185
1199
|
cont = false;
|
|
@@ -1189,13 +1203,21 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1189
1203
|
// is lhs scalar?
|
|
1190
1204
|
// eslint-disable-next-line sonarjs/no-gratuitous-expressions
|
|
1191
1205
|
if(!lhsIsVal && x.lhs && !isScalarOrNoType(x.lhs._art)) {
|
|
1192
|
-
error(null, location,
|
|
1206
|
+
error(null, location,
|
|
1207
|
+
{
|
|
1208
|
+
prefix: prefix(lhs, op, rhs),
|
|
1209
|
+
name: `${x.lhs.ref.join('.')}${(xn.length ? '.' + xn : '')}`
|
|
1210
|
+
},
|
|
1193
1211
|
'$(PREFIX): Path $(NAME) must end on a scalar type')
|
|
1194
1212
|
cont = false;
|
|
1195
1213
|
}
|
|
1196
1214
|
// is rhs scalar?
|
|
1197
1215
|
if(!rhsIsVal && x.rhs && !isScalarOrNoType(x.rhs._art)) {
|
|
1198
|
-
error(null, location,
|
|
1216
|
+
error(null, location,
|
|
1217
|
+
{
|
|
1218
|
+
prefix: prefix(lhs, op, rhs),
|
|
1219
|
+
name: `${x.rhs.ref.join('.')}${(xn.length ? '.' + xn : '')}`
|
|
1220
|
+
},
|
|
1199
1221
|
'$(PREFIX): Path $(NAME) must end on a scalar type');
|
|
1200
1222
|
cont = false;
|
|
1201
1223
|
}
|
|
@@ -1205,7 +1227,12 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1205
1227
|
const lhst = getType(x.lhs._art);
|
|
1206
1228
|
const rhst = getType(x.rhs._art);
|
|
1207
1229
|
if(lhst !== rhst) {
|
|
1208
|
-
info(null, location,
|
|
1230
|
+
info(null, location,
|
|
1231
|
+
{
|
|
1232
|
+
prefix: prefix(lhs, op, rhs),
|
|
1233
|
+
name: xn
|
|
1234
|
+
},
|
|
1235
|
+
'$(PREFIX): Types for sub path $(NAME) don\'t match');
|
|
1209
1236
|
}
|
|
1210
1237
|
}
|
|
1211
1238
|
}
|
|
@@ -196,7 +196,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
196
196
|
'leaf' QAT and to the respective $tableAlias which is used to link paths to the correct
|
|
197
197
|
table alias. Subqueries are not considered in the mergePathIntoQat(), so a subquery QA
|
|
198
198
|
must be created and added separately to the lead query $tableAlias'es.
|
|
199
|
-
Also the name of the subquery (the alias) needs to be set to the final QA alias name.
|
|
199
|
+
Also, the name of the subquery (the alias) needs to be set to the final QA alias name.
|
|
200
200
|
*/
|
|
201
201
|
function createQAForFromClauseSubQuery(query, env)
|
|
202
202
|
{
|
|
@@ -204,7 +204,14 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
204
204
|
if (query.$tableAliases[taName].kind !== '$self') {
|
|
205
205
|
let ta = query.$tableAliases[taName];
|
|
206
206
|
if(!ta.$QA) {
|
|
207
|
-
|
|
207
|
+
let alias = taName;
|
|
208
|
+
if (ta.name.$inferred === '$internal') {
|
|
209
|
+
// query has no explicit table alias, i.e. is internal: make it visible and remove `$`
|
|
210
|
+
alias = ta.name.alias.replace(/^[$]/, '_');
|
|
211
|
+
ta.$inferred = undefined;
|
|
212
|
+
ta.name.$inferred = undefined;
|
|
213
|
+
}
|
|
214
|
+
ta.$QA = createQA(env, ta._origin, alias, undefined);
|
|
208
215
|
incAliasCount(env, ta.$QA);
|
|
209
216
|
if(ta.name && ta.name.id) {
|
|
210
217
|
ta.name.id = ta.$QA.name.id;
|
|
@@ -307,7 +314,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
307
314
|
// pop ta ps
|
|
308
315
|
if(head._navigation.kind !== '$tableAlias')
|
|
309
316
|
tail = pathNode.path;
|
|
310
|
-
// if tail.
|
|
317
|
+
// if tail.length > 1, search bottom up for QA
|
|
311
318
|
// default to rootQA, _parent.$QA has precedence
|
|
312
319
|
let [QA, ps] = rightMostQA(tail, head._navigation._parent.$QA || head._navigation.$QA);
|
|
313
320
|
if(!QA) {
|
|
@@ -1005,7 +1012,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1005
1012
|
A QA (QueryArtifact) is a representative for a table/view that must appear
|
|
1006
1013
|
in the FROM clause either named directly or indirectly through an association.
|
|
1007
1014
|
*/
|
|
1008
|
-
function createQA(env, artifact, alias
|
|
1015
|
+
function createQA(env, artifact, alias, namedArgs=undefined)
|
|
1009
1016
|
{
|
|
1010
1017
|
if(alias === undefined) {
|
|
1011
1018
|
throw new CompilerAssertion('no alias provided');
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
forEachDefinition, forAllQueries, getNormalizedQuery,
|
|
4
|
+
forEachDefinition, forAllQueries, getNormalizedQuery, forEachMemberRecursively,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
const { setAnnotationIfNotDefined } = require('./utils');
|
|
7
7
|
const { CompilerAssertion } = require('../../base/error');
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Set @Core.Computed on the elements of views (and projections)
|
|
10
|
+
* Set @Core.Computed on the elements of views (and projections) as well
|
|
11
|
+
* as on calculated elements of entities and aspects.
|
|
11
12
|
*
|
|
12
13
|
* @param {CSN.Model} csn
|
|
13
14
|
* @param {object} csnUtils
|
|
14
15
|
*/
|
|
15
|
-
function
|
|
16
|
+
function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
|
|
16
17
|
const {
|
|
17
18
|
artifactRef, getColumn, getElement, getOrigin,
|
|
18
19
|
} = csnUtils;
|
|
@@ -24,6 +25,12 @@ function setCoreComputedOnViews( csn, csnUtils ) {
|
|
|
24
25
|
traverseQueryAndAttachCoreComputed(query, query.SELECT.elements || artifact.elements);
|
|
25
26
|
}, path);
|
|
26
27
|
}
|
|
28
|
+
else if (artifact.kind === 'entity' || artifact.kind === 'aspect') {
|
|
29
|
+
forEachMemberRecursively(artifact, (element) => {
|
|
30
|
+
if (element.value && !element.value?.ref) // calculated elements, but simple references are ignored
|
|
31
|
+
setAnnotationIfNotDefined(element, '@Core.Computed', true);
|
|
32
|
+
}, path);
|
|
33
|
+
}
|
|
27
34
|
});
|
|
28
35
|
/**
|
|
29
36
|
* Attach @Core.Computed to elements resulting from calculated fields
|
|
@@ -84,7 +91,7 @@ function setCoreComputedOnViews( csn, csnUtils ) {
|
|
|
84
91
|
* @todo cleanup throw(s) - but leave in during dev
|
|
85
92
|
*/
|
|
86
93
|
function getElementFromFrom( name, base ) {
|
|
87
|
-
if (base.SELECT
|
|
94
|
+
if (base.SELECT?.elements?.[name]) {
|
|
88
95
|
return getAncestor(base.SELECT.elements[name], name, base.SELECT);
|
|
89
96
|
}
|
|
90
97
|
else if (base.ref) {
|
|
@@ -100,7 +107,7 @@ function setCoreComputedOnViews( csn, csnUtils ) {
|
|
|
100
107
|
return checkJoinSources(base.args, name);
|
|
101
108
|
}
|
|
102
109
|
|
|
103
|
-
throw new CompilerAssertion(JSON.stringify(base));
|
|
110
|
+
throw new CompilerAssertion(`Element “${name}” not found in: ${JSON.stringify(base)}`);
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
/**
|
|
@@ -146,7 +153,8 @@ function setCoreComputedOnViews( csn, csnUtils ) {
|
|
|
146
153
|
function needsCoreComputed( column ) {
|
|
147
154
|
return column &&
|
|
148
155
|
(
|
|
149
|
-
column.xpr || column.func || column.val !== undefined || column.param ||
|
|
156
|
+
column.xpr || column.list || column.func || column.val !== undefined || column.param ||
|
|
157
|
+
column.SELECT || column.SET ||
|
|
150
158
|
column.ref && [ '$at', '$now', '$user', '$session' ].includes(column.ref[0])
|
|
151
159
|
);
|
|
152
160
|
}
|
|
@@ -175,5 +183,5 @@ function setCoreComputedOnViews( csn, csnUtils ) {
|
|
|
175
183
|
}
|
|
176
184
|
|
|
177
185
|
module.exports = {
|
|
178
|
-
|
|
186
|
+
setCoreComputedOnViewsAndCalculatedElements,
|
|
179
187
|
};
|