@sap/cds-compiler 4.9.2 → 5.0.6
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 +74 -0
- package/bin/cds_remove_invalid_whitespace.js +2 -1
- package/bin/cdsc.js +15 -11
- package/bin/cdshi.js +1 -0
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/main.js +7 -19
- package/lib/api/options.js +5 -11
- package/lib/api/trace.js +0 -1
- package/lib/base/builtins.js +1 -0
- package/lib/base/location.js +4 -1
- package/lib/base/message-registry.js +29 -29
- package/lib/base/messages.js +22 -26
- package/lib/base/model.js +0 -2
- package/lib/base/node-helpers.js +0 -1
- package/lib/checks/enricher.js +1 -5
- package/lib/checks/structuredAnnoExpressions.js +30 -0
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +4 -1
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +18 -2
- package/lib/compiler/checks.js +2 -5
- package/lib/compiler/define.js +7 -7
- package/lib/compiler/extend.js +68 -33
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/index.js +23 -6
- package/lib/compiler/lsp-api.js +501 -2
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +1 -4
- package/lib/compiler/resolve.js +2 -15
- package/lib/compiler/shared.js +112 -31
- package/lib/compiler/tweak-assocs.js +2 -16
- package/lib/compiler/utils.js +2 -1
- package/lib/compiler/xsn-model.js +4 -0
- package/lib/edm/annotations/genericTranslation.js +95 -42
- package/lib/edm/csn2edm.js +16 -4
- package/lib/edm/edm.js +2 -3
- package/lib/edm/edmAnnoPreprocessor.js +1 -2
- package/lib/edm/edmPreprocessor.js +1 -7
- package/lib/gen/Dictionary.json +29 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4995 -4817
- package/lib/json/csnVersion.js +1 -1
- package/lib/json/from-csn.js +4 -7
- package/lib/json/to-csn.js +23 -12
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/errorStrategy.js +0 -1
- package/lib/language/genericAntlrParser.js +35 -12
- package/lib/language/multiLineStringParser.js +3 -2
- package/lib/language/textUtils.js +1 -0
- package/lib/main.d.ts +28 -9
- package/lib/main.js +7 -4
- package/lib/model/csnRefs.js +20 -4
- package/lib/model/csnUtils.js +0 -2
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/modelCompare/compare.js +1 -1
- package/lib/optionProcessor.js +28 -9
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +36 -7
- package/lib/render/toSql.js +1 -0
- package/lib/render/utils/common.js +12 -9
- package/lib/render/utils/stringEscapes.js +1 -0
- package/lib/transform/db/applyTransformations.js +13 -8
- package/lib/transform/db/associations.js +62 -54
- package/lib/transform/db/constraints.js +23 -25
- package/lib/transform/db/expansion.js +1 -6
- package/lib/transform/db/flattening.js +89 -111
- package/lib/transform/db/temporal.js +3 -4
- package/lib/transform/db/views.js +0 -1
- package/lib/transform/draft/odata.js +51 -3
- package/lib/transform/effective/annotations.js +3 -2
- package/lib/transform/effective/flattening.js +135 -0
- package/lib/transform/effective/main.js +6 -6
- package/lib/transform/effective/types.js +13 -9
- package/lib/transform/forOdata.js +0 -2
- package/lib/transform/forRelationalDB.js +0 -19
- package/lib/transform/localized.js +7 -8
- package/lib/transform/odata/flattening.js +39 -31
- package/lib/transform/odata/typesExposure.js +5 -17
- package/lib/transform/transformUtils.js +1 -1
- package/lib/transform/translateAssocsToJoins.js +21 -3
- package/lib/utils/file.js +13 -7
- package/lib/utils/moduleResolve.js +59 -8
- package/lib/utils/term.js +3 -2
- package/package.json +7 -3
- package/share/messages/message-explanations.json +2 -0
- package/share/messages/type-unexpected-foreign-keys.md +52 -0
- package/share/messages/type-unexpected-on-condition.md +52 -0
package/lib/render/toCdl.js
CHANGED
|
@@ -1379,7 +1379,7 @@ function csnToCdl( csn, options, msg ) {
|
|
|
1379
1379
|
|
|
1380
1380
|
// Foreign keys (if any, unless we also have an ON_condition (which means we have been transformed from managed to unmanaged)
|
|
1381
1381
|
if (artifact.keys && !artifact.on)
|
|
1382
|
-
result += `
|
|
1382
|
+
result += ` ${ renderForeignKeys(artifact, env) }`;
|
|
1383
1383
|
|
|
1384
1384
|
if (artifact.notNull !== undefined && !artifact.on) // unmanaged associations can't be followed by "not null"
|
|
1385
1385
|
result += renderNullability(artifact);
|
|
@@ -1435,7 +1435,7 @@ function csnToCdl( csn, options, msg ) {
|
|
|
1435
1435
|
if (art.on)
|
|
1436
1436
|
result += ` on ${exprRenderer.renderExpr(art.on, env.withSubPath([ 'on' ]))}`;
|
|
1437
1437
|
else if (art.keys)
|
|
1438
|
-
result += `
|
|
1438
|
+
result += ` ${ renderForeignKeys(art, env) }`;
|
|
1439
1439
|
return result;
|
|
1440
1440
|
}
|
|
1441
1441
|
|
|
@@ -1813,15 +1813,43 @@ function csnToCdl( csn, options, msg ) {
|
|
|
1813
1813
|
}
|
|
1814
1814
|
|
|
1815
1815
|
/**
|
|
1816
|
-
* Render
|
|
1816
|
+
* Render foreign keys.
|
|
1817
1817
|
*
|
|
1818
|
-
* @param {object}
|
|
1818
|
+
* @param {object} art
|
|
1819
1819
|
* @param {CdlRenderEnvironment} env
|
|
1820
1820
|
* @return {string}
|
|
1821
1821
|
*/
|
|
1822
|
-
function
|
|
1823
|
-
const
|
|
1824
|
-
|
|
1822
|
+
function renderForeignKeys( art, env ) {
|
|
1823
|
+
const renderedKeys = [];
|
|
1824
|
+
let hasAnnotations = false;
|
|
1825
|
+
env = env.withSubPath([ 'keys', -1 ]);
|
|
1826
|
+
env.increaseIndent();
|
|
1827
|
+
|
|
1828
|
+
for (let i = 0; i < art.keys.length; ++i) {
|
|
1829
|
+
env.path[env.path.length - 1] = i;
|
|
1830
|
+
const fKey = art.keys[i];
|
|
1831
|
+
|
|
1832
|
+
const annos = renderAnnotationAssignmentsAndDocComment(fKey, env).trim();
|
|
1833
|
+
if (annos) {
|
|
1834
|
+
hasAnnotations = true;
|
|
1835
|
+
renderedKeys.push(annos);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
const alias = fKey.as ? renderAlias(fKey.as, env) : '';
|
|
1839
|
+
const key = exprRenderer.renderExpr(fKey, env);
|
|
1840
|
+
renderedKeys.push(`${key}${alias},`);
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
if (hasAnnotations) {
|
|
1844
|
+
const sep = `\n${env.indent}`;
|
|
1845
|
+
env.decreaseIndent();
|
|
1846
|
+
return `{${sep}${renderedKeys.join(sep)}\n${env.indent}}`;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
let result = renderedKeys.join(' ');
|
|
1850
|
+
if (result[result.length - 1] === ',') // remove trailing comma
|
|
1851
|
+
result = result.slice(0, -1);
|
|
1852
|
+
return `{ ${ result } }`;
|
|
1825
1853
|
}
|
|
1826
1854
|
|
|
1827
1855
|
/**
|
|
@@ -2427,6 +2455,7 @@ function isSimpleString( str ) {
|
|
|
2427
2455
|
// <https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf>
|
|
2428
2456
|
// On top, because (invalid) surrogate pairs need to be handled, we check for them as well.
|
|
2429
2457
|
// v3: Not a simple string if ' (\u0027) is in string.
|
|
2458
|
+
// eslint-disable-next-line no-control-regex
|
|
2430
2459
|
return str === '' || (/^[^\u{0000}-\u{001F}\u2028\u2029]+$/u.test(str) &&
|
|
2431
2460
|
!hasUnpairedUnicodeSurrogate(str));
|
|
2432
2461
|
}
|
package/lib/render/toSql.js
CHANGED
|
@@ -1645,6 +1645,7 @@ function renderStringForSql( str, sqlDialect ) {
|
|
|
1645
1645
|
// > Single quotation marks are used to delimit string literals.
|
|
1646
1646
|
// > A single quotation mark itself can be represented using two single quotation marks.
|
|
1647
1647
|
str = str.replace(/'/g, '\'\'')
|
|
1648
|
+
// eslint-disable-next-line no-control-regex
|
|
1648
1649
|
.replace(/\u{0}/ug, '\' || CHAR(0) || \'');
|
|
1649
1650
|
}
|
|
1650
1651
|
else {
|
|
@@ -372,7 +372,7 @@ const variablesToSql = {
|
|
|
372
372
|
'$valid.to': "current_setting('cap.valid_to')::timestamp",
|
|
373
373
|
$now: 'current_timestamp',
|
|
374
374
|
},
|
|
375
|
-
|
|
375
|
+
sqlite: {
|
|
376
376
|
'$user.id': "session_context( '$user.id' )",
|
|
377
377
|
'$user.locale': "session_context( '$user.locale' )",
|
|
378
378
|
$tenant: "session_context( '$tenant' )",
|
|
@@ -382,7 +382,7 @@ const variablesToSql = {
|
|
|
382
382
|
'$valid.to': "session_context( '$valid.to' )",
|
|
383
383
|
$now: 'CURRENT_TIMESTAMP',
|
|
384
384
|
},
|
|
385
|
-
sqlite: {
|
|
385
|
+
'old-sqlite': {
|
|
386
386
|
// For sqlite, we render the string-format-time (strftime) function.
|
|
387
387
|
// Because the format of `current_timestamp` is like that: '2021-05-14 09:17:19' whereas
|
|
388
388
|
// the format for timestamps (at least in Node.js) is like that: '2021-01-01T00:00:00.000Z'
|
|
@@ -394,11 +394,14 @@ const variablesToSql = {
|
|
|
394
394
|
'$valid.to': "strftime('%Y-%m-%dT%H:%M:%S.001Z', 'now')",
|
|
395
395
|
$now: 'CURRENT_TIMESTAMP',
|
|
396
396
|
},
|
|
397
|
-
plain: {
|
|
398
|
-
'$
|
|
399
|
-
'$
|
|
400
|
-
|
|
401
|
-
'$
|
|
397
|
+
plain: { // better-sqlite defaults
|
|
398
|
+
'$user.id': "session_context( '$user.id' )",
|
|
399
|
+
'$user.locale': "session_context( '$user.locale' )",
|
|
400
|
+
$tenant: "session_context( '$tenant' )",
|
|
401
|
+
'$at.from': "session_context( '$valid.from' )",
|
|
402
|
+
'$at.to': "session_context( '$valid.to' )",
|
|
403
|
+
'$valid.from': "session_context( '$valid.from' )",
|
|
404
|
+
'$valid.to': "session_context( '$valid.to' )",
|
|
402
405
|
$now: 'CURRENT_TIMESTAMP',
|
|
403
406
|
},
|
|
404
407
|
h2: {
|
|
@@ -423,8 +426,8 @@ const variablesToSql = {
|
|
|
423
426
|
* @return {string|null} `null` if the variable could not be found for the given dialect and in the fallback values.
|
|
424
427
|
*/
|
|
425
428
|
function variableForDialect( options, variable ) {
|
|
426
|
-
const dialect = options.sqlDialect === 'sqlite' && options.betterSqliteSessionVariables
|
|
427
|
-
? '
|
|
429
|
+
const dialect = options.sqlDialect === 'sqlite' && options.betterSqliteSessionVariables === false
|
|
430
|
+
? 'old-sqlite'
|
|
428
431
|
: options.sqlDialect;
|
|
429
432
|
return variablesToSql[dialect]?.[variable] || variablesToSql.fallback[variable] || null;
|
|
430
433
|
}
|
|
@@ -45,6 +45,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
const csnPath = [ ...path ];
|
|
48
|
+
const context = {};
|
|
48
49
|
if (prop === 'definitions') {
|
|
49
50
|
definitions( parent, 'definitions', parent.definitions );
|
|
50
51
|
}
|
|
@@ -84,9 +85,9 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
84
85
|
for (const name of Object.getOwnPropertyNames( node )) {
|
|
85
86
|
const trans = transformers[name] || transformers[name.charAt(0)] || standard;
|
|
86
87
|
if (customTransformers[name])
|
|
87
|
-
customTransformers[name](node, name, node[name], csnPath, _parent, _prop);
|
|
88
|
+
customTransformers[name](node, name, node[name], csnPath, _parent, _prop, context);
|
|
88
89
|
else if (options.processAnnotations && customTransformers['@'] && name.charAt(0) === '@')
|
|
89
|
-
customTransformers['@'](node, name, node[name], csnPath, _parent, _prop);
|
|
90
|
+
customTransformers['@'](node, name, node[name], csnPath, _parent, _prop, context);
|
|
90
91
|
trans( node, name, node[name], csnPath );
|
|
91
92
|
}
|
|
92
93
|
}
|
|
@@ -110,9 +111,9 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
110
111
|
for (const name of Object.getOwnPropertyNames( node )) {
|
|
111
112
|
const trans = transformers[name] || transformers[name.charAt(0)] || standard;
|
|
112
113
|
if (customTransformers[name])
|
|
113
|
-
customTransformers[name](node, name, node[name], csnPath, dict);
|
|
114
|
+
customTransformers[name](node, name, node[name], csnPath, dict, null, context);
|
|
114
115
|
else if (options.processAnnotations && customTransformers['@'] && name.charAt(0) === '@')
|
|
115
|
-
customTransformers['@'](node, name, node[name], csnPath, dict);
|
|
116
|
+
customTransformers['@'](node, name, node[name], csnPath, dict, null, context);
|
|
116
117
|
trans( node, name, node[name], csnPath );
|
|
117
118
|
}
|
|
118
119
|
csnPath.pop();
|
|
@@ -130,12 +131,14 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
130
131
|
if (options.skipDict?.[_prop] || dict == null) // with universal CSN, dictionaries might be null
|
|
131
132
|
return;
|
|
132
133
|
csnPath.push( _prop );
|
|
134
|
+
context[`$in_${_prop}`] = true;
|
|
133
135
|
for (const name of Object.getOwnPropertyNames( dict ))
|
|
134
136
|
dictEntry( dict, name, dict[name] );
|
|
135
137
|
|
|
136
138
|
if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
|
|
137
139
|
setProp(node, `$${_prop}`, dict);
|
|
138
140
|
csnPath.pop();
|
|
141
|
+
context[`$in_${_prop}`] = undefined;
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
/**
|
|
@@ -148,6 +151,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
148
151
|
*/
|
|
149
152
|
function annotation( _parent, _prop, node ) {
|
|
150
153
|
if (options.processAnnotations) {
|
|
154
|
+
context.$annotation = { name: _prop, value: node };
|
|
151
155
|
if (isAnnotationExpression(node)) {
|
|
152
156
|
standard(_parent, _prop, node);
|
|
153
157
|
}
|
|
@@ -164,6 +168,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
164
168
|
|
|
165
169
|
csnPath.pop();
|
|
166
170
|
}
|
|
171
|
+
context.$annotation = undefined;
|
|
167
172
|
}
|
|
168
173
|
}
|
|
169
174
|
|
|
@@ -359,18 +364,18 @@ function mergeTransformers( transformers, that ) {
|
|
|
359
364
|
remapped[n] = [];
|
|
360
365
|
|
|
361
366
|
if (Array.isArray(fns)) {
|
|
362
|
-
remapped[n].push((parent, name, prop, path, parentParent) => fns.forEach(
|
|
363
|
-
fn => fn.bind(that)(parent, name, prop, path, parentParent)
|
|
367
|
+
remapped[n].push((parent, name, prop, path, parentParent, opt, context) => fns.forEach(
|
|
368
|
+
fn => fn.bind(that)(parent, name, prop, path, parentParent, opt, context)
|
|
364
369
|
));
|
|
365
370
|
}
|
|
366
371
|
else {
|
|
367
|
-
remapped[n].push((parent, name, prop, path, parentParent) => fns.bind(that)(parent, name, prop, path, parentParent));
|
|
372
|
+
remapped[n].push((parent, name, prop, path, parentParent, opt, context) => fns.bind(that)(parent, name, prop, path, parentParent, opt, context));
|
|
368
373
|
}
|
|
369
374
|
}
|
|
370
375
|
}
|
|
371
376
|
|
|
372
377
|
for (const [ n, fns ] of Object.entries(remapped))
|
|
373
|
-
remapped[n] = (parent, name, prop, path, parentParent) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path, parentParent));
|
|
378
|
+
remapped[n] = (parent, name, prop, path, parentParent, opt, context) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path, parentParent, opt, context));
|
|
374
379
|
|
|
375
380
|
|
|
376
381
|
return remapped;
|
|
@@ -132,44 +132,8 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
|
|
|
132
132
|
* @param {string} artifactName Name of the artifact
|
|
133
133
|
*/
|
|
134
134
|
function handleManagedAssocSteps( artifact, artifactName ) {
|
|
135
|
-
const transformer =
|
|
136
|
-
|
|
137
|
-
// [<assoc base>.]<managed assoc>.<field>
|
|
138
|
-
if (ref.length > 1) {
|
|
139
|
-
const { links } = inspectRef(path);
|
|
140
|
-
if (links) {
|
|
141
|
-
let fkAlias = '';
|
|
142
|
-
// eslint-disable-next-line for-direction
|
|
143
|
-
for (let i = links.length - 1; i >= 0; i--) {
|
|
144
|
-
const link = links[i];
|
|
145
|
-
// We found the latest managed assoc path step
|
|
146
|
-
if (link.art && link.art.target && link.art.keys &&
|
|
147
|
-
// Doesn't work when ref-target (filter condition) or similar is used
|
|
148
|
-
!ref.slice(i).some(refElement => typeof refElement !== 'string')) {
|
|
149
|
-
const fkRef = ref[i + 1];
|
|
150
|
-
const fkName = (!fkAlias ? fkRef : `${fkRef}${pathDelimiter}${fkAlias}`);
|
|
151
|
-
const fks = link.art.keys.filter(key => key.ref[0] === fkName);
|
|
152
|
-
|
|
153
|
-
if (fks.length >= 1) { // after flattening, at most one FK will remain.
|
|
154
|
-
// `.as` is set for SQL, but not for OData -> fall back to implicit alias
|
|
155
|
-
fkAlias = fks[0].as || fks[0].ref[fks[0].ref.length - 1];
|
|
156
|
-
const source = findSource(links, i - 1) || artifact;
|
|
157
|
-
const managedAssocStepName = ref[i];
|
|
158
|
-
const newFkName = `${managedAssocStepName}${pathDelimiter}${fkAlias}`;
|
|
159
|
-
if (source?.elements[newFkName])
|
|
160
|
-
refOwner.ref = [ ...ref.slice(0, i), newFkName ];
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
fkAlias = '';
|
|
165
|
-
// Ignore last path step and unmanaged associations.
|
|
166
|
-
// Structures should have been already flattened.
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
};
|
|
135
|
+
const transformer = getTransformer();
|
|
136
|
+
const inColumnsTransformer = getTransformer(true);
|
|
173
137
|
for (const elemName in artifact.elements) {
|
|
174
138
|
const elem = artifact.elements[elemName];
|
|
175
139
|
// The association is an unmanaged one
|
|
@@ -187,28 +151,72 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
|
|
|
187
151
|
where: transform,
|
|
188
152
|
having: transform,
|
|
189
153
|
};
|
|
190
|
-
if (processOnInQueries)
|
|
154
|
+
if (processOnInQueries) {
|
|
155
|
+
queryTransformers.columns = (parent, prop, columns, path) => {
|
|
156
|
+
for (let i = 0; i < columns.length; i++) {
|
|
157
|
+
const column = columns[i];
|
|
158
|
+
if (column.ref) {
|
|
159
|
+
inColumnsTransformer.ref(column, 'ref', column.ref, path.concat([ 'columns', i ]));
|
|
160
|
+
column.ref.forEach((step, index) => {
|
|
161
|
+
if (step.where)
|
|
162
|
+
transform(step, 'where', step.where, path.concat([ 'columns', i, 'ref', index ]));
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
191
167
|
queryTransformers.on = transform;
|
|
192
|
-
|
|
168
|
+
}
|
|
169
|
+
applyTransformationsOnNonDictionary(artifact, artifact.query ? 'query' : 'projection', queryTransformers, { drillRef: true }, [ 'definitions', artifactName ]);
|
|
193
170
|
}
|
|
194
171
|
|
|
195
|
-
|
|
196
172
|
/**
|
|
197
|
-
* Find out where the managed association is
|
|
198
173
|
*
|
|
199
|
-
* @param {
|
|
200
|
-
* @
|
|
201
|
-
* @returns {object | undefined} CSN definition of the source of the managed association
|
|
174
|
+
* @param {boolean} isColumns Whether the transformation is taking place on a column
|
|
175
|
+
* @returns {object}
|
|
202
176
|
*/
|
|
203
|
-
function
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
177
|
+
function getTransformer( isColumns = false ) {
|
|
178
|
+
return {
|
|
179
|
+
ref: (refOwner, prop, ref, path) => {
|
|
180
|
+
// [<assoc base>.]<managed assoc>.<field>
|
|
181
|
+
if (ref.length > 1) {
|
|
182
|
+
const { links } = inspectRef(path);
|
|
183
|
+
if (links) {
|
|
184
|
+
let fkAlias = '';
|
|
185
|
+
for (let i = links.length - 1; i >= 0; i--) {
|
|
186
|
+
const link = links[i];
|
|
187
|
+
// We found the latest managed assoc path step
|
|
188
|
+
if (link.art && link.art.target && link.art.keys &&
|
|
189
|
+
// Doesn't work when ref-target (filter condition) or similar is used
|
|
190
|
+
!ref.slice(i).some(refElement => typeof refElement !== 'string')) {
|
|
191
|
+
const fkRef = ref[i + 1];
|
|
192
|
+
const fkName = (!fkAlias ? fkRef : `${fkRef}${pathDelimiter}${fkAlias}`);
|
|
193
|
+
const fks = link.art.keys.filter(key => key.ref[0] === fkName);
|
|
194
|
+
|
|
195
|
+
if (fks.length >= 1) { // after flattening, at most one FK will remain.
|
|
196
|
+
// `.as` is set for SQL, but not for OData -> fall back to implicit alias
|
|
197
|
+
fkAlias = fks[0].as || fks[0].ref[fks[0].ref.length - 1];
|
|
198
|
+
const managedAssocStepName = ref[i];
|
|
199
|
+
const newFkName = `${managedAssocStepName}${pathDelimiter}${fkAlias}`;
|
|
200
|
+
if (isColumns) {
|
|
201
|
+
refOwner.ref = [ ...ref.slice(0, i), newFkName ];
|
|
202
|
+
if (!refOwner.as)
|
|
203
|
+
refOwner.as = implicitAs(ref);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
refOwner.ref = [ ...ref.slice(0, i), newFkName ];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
fkAlias = '';
|
|
212
|
+
// Ignore last path step and unmanaged associations.
|
|
213
|
+
// Structures should have been already flattened.
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
};
|
|
212
220
|
}
|
|
213
221
|
}
|
|
214
222
|
}
|
|
@@ -203,28 +203,28 @@ function createReferentialConstraints( csn, options ) {
|
|
|
203
203
|
// in contrast to foreign keys which stem from managed associations,
|
|
204
204
|
// a tenant foreign key column may have multiple parent keys as partners
|
|
205
205
|
const tenantForeignKey = isTenant && dependentKeyValuePair[0] === 'tenant';
|
|
206
|
-
if ($foreignKeyConstraint && (!tenantForeignKey || $foreignKeyConstraint.upLinkFor))
|
|
206
|
+
if ($foreignKeyConstraint && (!tenantForeignKey || $foreignKeyConstraint.upLinkFor))
|
|
207
207
|
return;
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
|
|
209
|
+
const parentKeyValuePair = parentKeys.pop();
|
|
210
|
+
const parentKeyName = parentKeyValuePair[0];
|
|
211
|
+
|
|
212
|
+
const constraint = {
|
|
213
|
+
parentKey: parentKeyName,
|
|
214
|
+
parentTable,
|
|
215
|
+
upLinkFor,
|
|
216
|
+
sourceAssociation,
|
|
217
|
+
onDelete: upLinkFor ? 'CASCADE' : 'RESTRICT',
|
|
218
|
+
validated,
|
|
219
|
+
enforced,
|
|
220
|
+
};
|
|
221
|
+
if (tenantForeignKey) {
|
|
222
|
+
const dontOverwriteUp = dependentKey.$foreignKeyConstraint && dependentKey.$foreignKeyConstraint.some(c => c.sourceAssociation === sourceAssociation && c.parentTable === parentTable);
|
|
223
|
+
const dontOverwriteTexts = dependentKey.$foreignKeyConstraint && dependentKey.$foreignKeyConstraint.some(c => c.sourceAssociation === 'texts' && c.upLinkFor.texts);
|
|
224
|
+
if (!dontOverwriteUp && !dontOverwriteTexts)
|
|
225
|
+
dependentKey.$foreignKeyConstraint = dependentKey.$foreignKeyConstraint ? [ ...dependentKey.$foreignKeyConstraint, constraint ] : [ constraint ];
|
|
212
226
|
}
|
|
213
227
|
else {
|
|
214
|
-
const parentKeyValuePair = parentKeys.pop();
|
|
215
|
-
const parentKeyName = parentKeyValuePair[0];
|
|
216
|
-
|
|
217
|
-
const constraint = {
|
|
218
|
-
parentKey: parentKeyName,
|
|
219
|
-
parentTable,
|
|
220
|
-
upLinkFor,
|
|
221
|
-
sourceAssociation: tenantForeignKey
|
|
222
|
-
? [ sourceAssociation ]
|
|
223
|
-
: sourceAssociation,
|
|
224
|
-
onDelete: upLinkFor ? 'CASCADE' : 'RESTRICT',
|
|
225
|
-
validated,
|
|
226
|
-
enforced,
|
|
227
|
-
};
|
|
228
228
|
dependentKey.$foreignKeyConstraint = constraint;
|
|
229
229
|
}
|
|
230
230
|
}
|
|
@@ -509,13 +509,11 @@ function createReferentialConstraints( csn, options ) {
|
|
|
509
509
|
if (isTenant && artifact.elements?.tenant) {
|
|
510
510
|
const element = artifact.elements.tenant;
|
|
511
511
|
if (element.$foreignKeyConstraint) {
|
|
512
|
-
const
|
|
512
|
+
const tenantConstraints = element.$foreignKeyConstraint;
|
|
513
513
|
delete element.$foreignKeyConstraint;
|
|
514
|
-
// create
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
copy.sourceAssociation = sourceAssociation;
|
|
518
|
-
createReferentialConstraints(copy, 'tenant');
|
|
514
|
+
// create (multiple) foreign key constraint(s) for the tenant column with each association in the dependent entity
|
|
515
|
+
tenantConstraints.forEach((c) => {
|
|
516
|
+
createReferentialConstraints(c, 'tenant');
|
|
519
517
|
});
|
|
520
518
|
}
|
|
521
519
|
}
|
|
@@ -47,7 +47,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
47
47
|
const isComplexQuery = parent.from.join !== undefined;
|
|
48
48
|
if (!options.toOdata)
|
|
49
49
|
parent.columns = replaceStar(root, columns, parent.excluding, isComplexQuery);
|
|
50
|
-
// FIXME(
|
|
50
|
+
// FIXME(v6): Remove argument "isComplexOrNestedQuery"; we use path.length > 4 to check
|
|
51
51
|
// if we're inside the outermost "columns". If so, always prepend a table alias. See #11662
|
|
52
52
|
parent.columns = expand(parent.columns, path.concat('columns'), true, isComplexQuery || path.length > 4);
|
|
53
53
|
}
|
|
@@ -622,11 +622,6 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
622
622
|
return _expandStructCol(art, columnAlias(root), root.ref, ( currentRef, currentAlias) => {
|
|
623
623
|
const obj = { ...root, ref: currentRef };
|
|
624
624
|
if (withAlias) {
|
|
625
|
-
// TODO: Remove this line in case foreign key annotations should
|
|
626
|
-
// be addressed via full path into target instead of using alias
|
|
627
|
-
// names. See flattening.js::flattenAllStructStepsInRefs()
|
|
628
|
-
// apply transformations on `ref` counterpart comment.
|
|
629
|
-
setProp(obj, '$structRef', currentAlias);
|
|
630
625
|
obj.as = currentAlias.join(pathDelimiter);
|
|
631
626
|
// alias was implicit - to later distinguish expanded s -> s.a from explicitly written s.a
|
|
632
627
|
if (root.as === undefined)
|