@sap/cds-compiler 2.11.4 → 2.13.8
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 +159 -1
- package/bin/cds_update_identifiers.js +7 -7
- package/bin/cdsc.js +22 -23
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +30 -63
- package/lib/api/options.js +5 -5
- package/lib/api/validate.js +0 -5
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +52 -2
- package/lib/base/messages.js +16 -26
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +10 -6
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +8 -6
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +33 -14
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +76 -38
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +204 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +143 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/genericAntlrParser.js +144 -54
- package/lib/language/language.g4 +424 -203
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +472 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +321 -204
- package/lib/model/csnUtils.js +224 -263
- package/lib/model/enrichCsn.js +97 -40
- package/lib/model/revealInternalProperties.js +27 -6
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +7 -6
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +201 -115
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +149 -75
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +552 -105
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +94 -28
- package/lib/transform/db/views.js +5 -4
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +94 -801
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +10 -27
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +2 -1
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2340
- package/lib/compiler/resolver.js +0 -2988
- package/lib/transform/universalCsnEnricher.js +0 -67
package/lib/render/utils/sql.js
CHANGED
|
@@ -4,20 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
const { getResultingName } = require('../../model/csnUtils');
|
|
6
6
|
const { smartId, delimitedId } = require('../../sql-identifier');
|
|
7
|
+
const { ModelError } = require('../../base/error');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Render a given referential constraint as part of a SQL CREATE TABLE statement, or as .hdbconstraint artefact.
|
|
10
11
|
*
|
|
11
12
|
* @param {CSN.ReferentialConstraint} constraint Content of the constraint
|
|
12
13
|
* @param {string} indent Indent to render the SQL with
|
|
13
|
-
* @param {boolean} toUpperCase
|
|
14
|
+
* @param {boolean} toUpperCase Whether to uppercase the identifier
|
|
14
15
|
* @param {CSN.Model} csn CSN
|
|
15
16
|
* @param {CSN.Options} options is needed for the naming mode and the sql dialect
|
|
16
|
-
* @param {boolean} alterConstraint whether the constraint should be rendered as part of an ALTER TABLE statement
|
|
17
|
+
* @param {boolean} [alterConstraint=false] whether the constraint should be rendered as part of an ALTER TABLE statement
|
|
17
18
|
*
|
|
18
19
|
* @returns {string} SQL statement which can be used to create the referential constraint on the db.
|
|
19
20
|
*/
|
|
20
|
-
function renderReferentialConstraint(constraint, indent, toUpperCase, csn, options, alterConstraint) {
|
|
21
|
+
function renderReferentialConstraint(constraint, indent, toUpperCase, csn, options, alterConstraint = false) {
|
|
21
22
|
let quoteId;
|
|
22
23
|
// for to.hana we can't utilize the sql identifier utils
|
|
23
24
|
if (options.transformation === 'hdbcds') {
|
|
@@ -51,14 +52,16 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
51
52
|
if (!alterConstraint) {
|
|
52
53
|
result += `${indent}FOREIGN KEY(${constraint.foreignKey.map(quoteId).join(', ')})\n`;
|
|
53
54
|
result += `${indent}REFERENCES ${quoteId(getResultingName(csn, names, constraint.parentTable))}(${constraint.parentKey.map(quoteId).join(', ')})\n`;
|
|
55
|
+
const onDeleteRemark = constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : '';
|
|
56
|
+
|
|
54
57
|
// omit 'RESTRICT' action for ON UPDATE / ON DELETE, because it interferes with deferred constraint check
|
|
55
58
|
if (forSqlite) {
|
|
56
59
|
if (constraint.onDelete === 'CASCADE' )
|
|
57
|
-
result += `${indent}ON DELETE ${constraint.onDelete}${
|
|
60
|
+
result += `${indent}ON DELETE ${constraint.onDelete}${onDeleteRemark}\n`;
|
|
58
61
|
}
|
|
59
62
|
else {
|
|
60
63
|
result += `${indent}ON UPDATE RESTRICT\n`;
|
|
61
|
-
result += `${indent}ON DELETE ${constraint.onDelete}${
|
|
64
|
+
result += `${indent}ON DELETE ${constraint.onDelete}${onDeleteRemark}\n`;
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
// constraint enforcement / validation must be switched off using sqlite pragma statement
|
|
@@ -119,7 +122,7 @@ function getIdentifierUtils(options) {
|
|
|
119
122
|
function prepareIdentifier(name) {
|
|
120
123
|
// Sanity check
|
|
121
124
|
if (options.toSql.dialect === 'sqlite' && options.toSql.names !== 'plain')
|
|
122
|
-
throw new
|
|
125
|
+
throw new ModelError(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);
|
|
123
126
|
|
|
124
127
|
|
|
125
128
|
switch (options.toSql.names) {
|
|
@@ -130,7 +133,7 @@ function getIdentifierUtils(options) {
|
|
|
130
133
|
case 'hdbcds':
|
|
131
134
|
return name;
|
|
132
135
|
default:
|
|
133
|
-
throw new
|
|
136
|
+
throw new ModelError(`No matching rendering found for naming mode ${options.toSql.names}`);
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
139
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const controlCharacters = /[\u{0000}-\u{001F}]/u;
|
|
4
|
+
const highSurrogate = /[\u{D800}-\u{DBFF}]/u;
|
|
5
|
+
const lowSurrogate = /[\u{DC00}-\u{DFFF}]/u;
|
|
6
|
+
// Either a high surrogate that is NOT followed by a low one or
|
|
7
|
+
// a low surrogate not preceded by a high one.
|
|
8
|
+
const unpairedSurrogate = /[^\u{D800}-\u{DBFF}][\u{DC00}-\u{DFFF}]|[\u{D800}-\u{DBFF}][^\u{DC00}-\u{DFFF}]/u;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns true if the string contains an unpaired unicode surrogate.
|
|
12
|
+
* See <https://en.wikipedia.org/wiki/UTF-16#U+D800_to_U+DFFF>.
|
|
13
|
+
* As a surrogate pair MUST consist of a high one followed by a low surrogate,
|
|
14
|
+
* an unpaired surrogate MUST be escaped.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} str
|
|
17
|
+
* @return {boolean}
|
|
18
|
+
*/
|
|
19
|
+
function hasUnpairedUnicodeSurrogate(str) {
|
|
20
|
+
return unpairedSurrogate.test(str);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns true if the string contains control characters such as LF or NUL.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} str
|
|
27
|
+
* @return {boolean}
|
|
28
|
+
*/
|
|
29
|
+
function hasControlCharacters(str) {
|
|
30
|
+
return controlCharacters.test(str);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Escape the given string according to the given specification in `escapes`.
|
|
35
|
+
*
|
|
36
|
+
* `escapes` is an object where the entries are either:
|
|
37
|
+
* - a mapping from character to string, e.g. `{ '"': '"' }`
|
|
38
|
+
* - `control: (codePoint) => str`
|
|
39
|
+
* A function that returns an escape sequence for the given control character.
|
|
40
|
+
* - `unpairedSurrogate: (codePoint) => str`
|
|
41
|
+
* A function that returns an escape sequence for the given unpaired unicode surrogate.
|
|
42
|
+
*
|
|
43
|
+
* Multi-character keys are not allowed.
|
|
44
|
+
*
|
|
45
|
+
* Character escapes take precedence over `control` and `unpairedSurrogate` escapes,
|
|
46
|
+
* i.e. if you do not want to encode LF (`\n`), add an explicit mapping for it, e.g.
|
|
47
|
+
* `{ '\n': '\n' }`.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* You can use `escapeString()` like this:
|
|
51
|
+
* ```js
|
|
52
|
+
* let escaped = escapeString(str, {
|
|
53
|
+
* '"': '\\"',
|
|
54
|
+
* control: (c) => `\\u{${c.toString(16)}}`;
|
|
55
|
+
* unpairedSurrogate: (c) => `\\u{${c.toString(16)}}`;
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @param {string} str
|
|
60
|
+
* @param {object} escapes
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
function escapeString(str, escapes) {
|
|
64
|
+
const output = [];
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < str.length; ++i) {
|
|
67
|
+
const char = str[i];
|
|
68
|
+
|
|
69
|
+
if (char in escapes) {
|
|
70
|
+
output.push(escapes[char]);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Control Characters: C0
|
|
75
|
+
// See <https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Basic_ASCII_control_codes>
|
|
76
|
+
if (controlCharacters.test(char)) {
|
|
77
|
+
output.push(escapes.control ? escapes.control(char.codePointAt(0)) : char);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Unicode Surrogates
|
|
82
|
+
// These characters appear in _pairs_. A high surrogate must be followed by a low surrogate.
|
|
83
|
+
// If this is not the case, either needs to be encoded. This is also done by JSON.
|
|
84
|
+
// See also <https://docs.microsoft.com/en-us/globalization/encoding/surrogate-pairs>
|
|
85
|
+
if (highSurrogate.test(char)) {
|
|
86
|
+
if (i + 1 >= str.length || !lowSurrogate.test(str[i + 1])) {
|
|
87
|
+
output.push(escapes.unpairedSurrogate ? escapes.unpairedSurrogate(char.codePointAt(0)) : char);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
output.push(char);
|
|
91
|
+
++i;
|
|
92
|
+
output.push(str[i]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (lowSurrogate.test(char)) {
|
|
96
|
+
output.push(escapes.unpairedSurrogate ? escapes.unpairedSurrogate(char.codePointAt(0)) : char);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// unhandled / non-special character
|
|
100
|
+
output.push(char);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return output.join('');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
escapeString,
|
|
109
|
+
hasUnpairedUnicodeSurrogate,
|
|
110
|
+
hasControlCharacters,
|
|
111
|
+
};
|
package/lib/sql-identifier.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// an identifier such that the effective name (the one in the DB schema) is
|
|
10
10
|
// the same as the name used in CDS.
|
|
11
11
|
// - 'quoted' and 'hdbcds' for HANA only: similar to the non-provided previous
|
|
12
|
-
// mode, with different
|
|
12
|
+
// mode, with different adaptations to HANA CDS and XS (classic) restrictions.
|
|
13
13
|
|
|
14
14
|
// The main objective of this file is to support the 'plain' mode in a “smart”
|
|
15
15
|
// manner. If we would use the CDS name (after `.` to `_` replacements)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
function isAlreadyBraced(expression, start, end){
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
return start - 1 > -1 &&
|
|
5
|
+
end + 1 < expression.length &&
|
|
6
|
+
expression[start-1] === '(' &&
|
|
7
|
+
expression[end+1] === ')';
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
function binarycomparison(expression, token, index){
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
// Don't enforce stupid descriptions
|
|
14
14
|
"jsdoc/require-param-description": "off",
|
|
15
15
|
"jsdoc/require-returns-description": "off",
|
|
16
|
+
// Sometimes if-else's are more specific
|
|
17
|
+
"sonarjs/prefer-single-boolean-return": "off",
|
|
16
18
|
// Very whiny and nitpicky
|
|
17
19
|
"sonarjs/cognitive-complexity": "off",
|
|
18
20
|
// Does not recognize TS types
|
|
@@ -18,12 +18,11 @@ const { setProp } = require('../../base/model');
|
|
|
18
18
|
* @param {string} prop The property of parent to start at
|
|
19
19
|
* @param {object} customTransformers Map of prop to transform and function to apply
|
|
20
20
|
* @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
|
|
21
|
+
* @param {applyTransformationsOptions} [options={}]
|
|
23
22
|
* @param {CSN.Path} path Path to parent
|
|
24
23
|
* @returns {object} parent with transformations applied
|
|
25
24
|
*/
|
|
26
|
-
function applyTransformationsInternal(parent, prop, customTransformers, artifactTransformers,
|
|
25
|
+
function applyTransformationsInternal(parent, prop, customTransformers, artifactTransformers, options, path = []) {
|
|
27
26
|
const transformers = {
|
|
28
27
|
elements: dictionary,
|
|
29
28
|
definitions: dictionary,
|
|
@@ -32,6 +31,7 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
|
|
|
32
31
|
enum: dictionary,
|
|
33
32
|
mixin: dictionary,
|
|
34
33
|
ref: pathRef,
|
|
34
|
+
$origin: () => {}, // no-op
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
const csnPath = [ ...path ];
|
|
@@ -50,7 +50,12 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
|
|
|
50
50
|
* @param {object} node The value of node[_prop]
|
|
51
51
|
*/
|
|
52
52
|
function standard( _parent, _prop, node ) {
|
|
53
|
-
if (!node || typeof node !== 'object' ||
|
|
53
|
+
if (!node || typeof node !== 'object' ||
|
|
54
|
+
!{}.propertyIsEnumerable.call( _parent, _prop ) ||
|
|
55
|
+
(typeof _prop === 'string' && _prop.startsWith('@')) ||
|
|
56
|
+
(options.skipIgnore && node._ignore) ||
|
|
57
|
+
(options.skipStandard && options.skipStandard[_prop])
|
|
58
|
+
)
|
|
54
59
|
return;
|
|
55
60
|
|
|
56
61
|
csnPath.push( _prop );
|
|
@@ -80,7 +85,7 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
|
|
|
80
85
|
*/
|
|
81
86
|
function dictionary( node, _prop, dict ) {
|
|
82
87
|
// Allow skipping dicts like actions in forHanaNew
|
|
83
|
-
if (options.skipDict && options.skipDict[_prop])
|
|
88
|
+
if (options.skipDict && options.skipDict[_prop] || dict === null || dict === undefined) // with universal CSN, dicts might be null
|
|
84
89
|
return;
|
|
85
90
|
csnPath.push( _prop );
|
|
86
91
|
for (const name of Object.getOwnPropertyNames( dict ))
|
|
@@ -101,7 +106,10 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
|
|
|
101
106
|
function definitions( node, _prop, dict ) {
|
|
102
107
|
csnPath.push( _prop );
|
|
103
108
|
for (const name of Object.getOwnPropertyNames( dict )) {
|
|
104
|
-
const skip = options && options.
|
|
109
|
+
const skip = (options && options.allowArtifact && !options.allowArtifact(dict[name], name)) ||
|
|
110
|
+
(options && options.skipArtifact && options.skipArtifact(dict[name], name)) ||
|
|
111
|
+
(options && options.skip && options.skip.includes(dict[name].kind)) ||
|
|
112
|
+
false;
|
|
105
113
|
if (!skip) {
|
|
106
114
|
artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
|
|
107
115
|
standard( dict, name, dict[name] );
|
|
@@ -152,13 +160,15 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
|
|
|
152
160
|
* @param {object} csn CSN to enrich in-place
|
|
153
161
|
* @param {object} customTransformers Map of _prop to transform and function to apply
|
|
154
162
|
* @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
|
|
155
|
-
* @param {
|
|
156
|
-
* @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
|
|
163
|
+
* @param {applyTransformationsOptions} [options={}]
|
|
157
164
|
* @returns {object} CSN with transformations applied
|
|
158
165
|
*/
|
|
159
|
-
function applyTransformations( csn, customTransformers = {}, artifactTransformers = [],
|
|
166
|
+
function applyTransformations( csn, customTransformers = {}, artifactTransformers = [], options = { } ) {
|
|
167
|
+
if (options.skipIgnore === undefined)
|
|
168
|
+
options.skipIgnore = true;
|
|
169
|
+
|
|
160
170
|
if (csn && csn.definitions)
|
|
161
|
-
return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers,
|
|
171
|
+
return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, options);
|
|
162
172
|
return csn;
|
|
163
173
|
}
|
|
164
174
|
|
|
@@ -176,14 +186,27 @@ function applyTransformations( csn, customTransformers = {}, artifactTransformer
|
|
|
176
186
|
* @param {object} parent The "parent" of which we transform a property of
|
|
177
187
|
* @param {string} prop The property of parent to start at
|
|
178
188
|
* @param {object} customTransformers Map of prop to transform and function to apply
|
|
189
|
+
* @param {applyTransformationsOptions} [options={}]
|
|
179
190
|
* @param {CSN.Path} path Path pointing to parent
|
|
180
191
|
* @returns {object} parent[prop] with transformations applied
|
|
181
192
|
*/
|
|
182
|
-
function applyTransformationsOnNonDictionary(parent, prop, customTransformers = {}, path = []) {
|
|
183
|
-
return applyTransformationsInternal(parent, prop, customTransformers, [],
|
|
193
|
+
function applyTransformationsOnNonDictionary(parent, prop, customTransformers = {}, options = {}, path = []) {
|
|
194
|
+
return applyTransformationsInternal(parent, prop, customTransformers, [], options, path)[prop];
|
|
184
195
|
}
|
|
185
196
|
|
|
186
197
|
module.exports = {
|
|
187
198
|
applyTransformations,
|
|
188
199
|
applyTransformationsOnNonDictionary,
|
|
189
200
|
};
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @typedef {object} applyTransformationsOptions
|
|
205
|
+
* @property {(artifact, name) => boolean} [allowArtifact] to only allow certain artifacts
|
|
206
|
+
* @property {(artifact, name) => boolean} [skipArtifact] to skip certain artifacts
|
|
207
|
+
* @property {boolean} [drillRef] whether to drill into infix/args
|
|
208
|
+
* @property {string[]} [skip] skip definitions from certain kind
|
|
209
|
+
* @property {object} [skipStandard] stop drill-down on certain "standard" props
|
|
210
|
+
* @property {object} [skipDict] stop drill-down on certain "dictionary" props
|
|
211
|
+
* @property {boolean} [skipIgnore=true] Whether to skip _ignore elements or not
|
|
212
|
+
*/
|
|
@@ -158,7 +158,7 @@ function processAssertUnique(csn, options, error, info) {
|
|
|
158
158
|
function toRef(val) {
|
|
159
159
|
let ref = val.split('.');
|
|
160
160
|
const [ head, ...tail ] = ref;
|
|
161
|
-
if (
|
|
161
|
+
if (head === '$self' || head === '$projection')
|
|
162
162
|
ref = tail;
|
|
163
163
|
return {
|
|
164
164
|
ref: ref.map((ps) => {
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
applyTransformationsOnNonDictionary,
|
|
5
|
+
applyTransformations,
|
|
6
|
+
getUtils,
|
|
7
|
+
} = require('../../model/csnUtils');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* In all .elements of entities and views (and their bound actions/functions), create the on-condition for
|
|
12
|
+
* a managed associations. This needs to happen after the .keys are expanded and the corresponding elements are created.
|
|
13
|
+
*
|
|
14
|
+
* @param {CSN.Model} csn
|
|
15
|
+
* @param {string} pathDelimiter
|
|
16
|
+
* @returns {CSN.Model} Return the input csn, with the transformations applied
|
|
17
|
+
*/
|
|
18
|
+
function attachOnConditions(csn, pathDelimiter) {
|
|
19
|
+
const {
|
|
20
|
+
isManagedAssociation,
|
|
21
|
+
} = getUtils(csn);
|
|
22
|
+
|
|
23
|
+
const alreadyHandled = new WeakMap();
|
|
24
|
+
applyTransformations(csn, {
|
|
25
|
+
elements: (parent, prop, elements) => {
|
|
26
|
+
for (const elemName in elements) {
|
|
27
|
+
const elem = elements[elemName];
|
|
28
|
+
// (140) Generate the ON-condition for managed associations
|
|
29
|
+
if (isManagedAssociation(elem))
|
|
30
|
+
transformManagedAssociation(elem, elemName);
|
|
31
|
+
}
|
|
32
|
+
}, /* only for views and entities */
|
|
33
|
+
}, [], { skipIgnore: false, allowArtifact: artifact => (artifact.kind === 'entity') });
|
|
34
|
+
|
|
35
|
+
return csn;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create the foreign key elements for a managed association and build the on-condition
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} elem The association to process
|
|
41
|
+
* @param {string} elemName
|
|
42
|
+
* @returns {void}
|
|
43
|
+
*/
|
|
44
|
+
function transformManagedAssociation(elem, elemName) {
|
|
45
|
+
// No need to run over this - we already did, possibly because it was referenced in the ON-Condition
|
|
46
|
+
// of another association - see a few lines lower
|
|
47
|
+
if (alreadyHandled.has(elem))
|
|
48
|
+
return;
|
|
49
|
+
// Assemble an ON-condition with the foreign keys created in earlier steps
|
|
50
|
+
const onCondParts = [];
|
|
51
|
+
let joinWithAnd = false;
|
|
52
|
+
if (elem.keys.length === 0) { // TODO: really kill instead of _ignore?
|
|
53
|
+
elem._ignore = true;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
for (const foreignKey of elem.keys) {
|
|
57
|
+
// Assemble left hand side of 'assoc.key = fkey'
|
|
58
|
+
const assocKeyArg = {
|
|
59
|
+
ref: [
|
|
60
|
+
elemName,
|
|
61
|
+
].concat(foreignKey.ref),
|
|
62
|
+
};
|
|
63
|
+
const fkName = `${elemName}${pathDelimiter}${foreignKey.as}`;
|
|
64
|
+
const fKeyArg = {
|
|
65
|
+
ref: [
|
|
66
|
+
fkName,
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (joinWithAnd) { // more than one FK
|
|
71
|
+
onCondParts.push('and');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
onCondParts.push(
|
|
75
|
+
assocKeyArg
|
|
76
|
+
);
|
|
77
|
+
onCondParts.push('=');
|
|
78
|
+
onCondParts.push(fKeyArg);
|
|
79
|
+
|
|
80
|
+
if (!joinWithAnd)
|
|
81
|
+
joinWithAnd = true;
|
|
82
|
+
}
|
|
83
|
+
elem.on = onCondParts;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// If the managed association has a 'key' property => remove it as unmanaged assocs cannot be keys
|
|
87
|
+
// TODO: Are there other modifiers (like 'key') that are valid for managed, but not valid for unmanaged assocs?
|
|
88
|
+
if (elem.key)
|
|
89
|
+
delete elem.key;
|
|
90
|
+
|
|
91
|
+
// If the managed association has a 'not null' property => remove it
|
|
92
|
+
if (elem.notNull)
|
|
93
|
+
delete elem.notNull;
|
|
94
|
+
|
|
95
|
+
// The association is now unmanaged, i.e. actually it should no longer have foreign keys
|
|
96
|
+
// at all. But the processing of backlink associations below expects to have them, so
|
|
97
|
+
// we don't delete them
|
|
98
|
+
// TODO: maybe make non-enumerable, so we become recompilable in the future?
|
|
99
|
+
|
|
100
|
+
// Remember that we already processed this
|
|
101
|
+
alreadyHandled.set(elem, true);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {CSN.Model} csn
|
|
107
|
+
* @param {string} pathDelimiter
|
|
108
|
+
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
|
|
109
|
+
*/
|
|
110
|
+
function getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter) {
|
|
111
|
+
const {
|
|
112
|
+
inspectRef,
|
|
113
|
+
} = getUtils(csn);
|
|
114
|
+
|
|
115
|
+
return handleManagedAssocStepsInOnCondition;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Loop over all elements and for all unmanaged associations translate
|
|
119
|
+
* <assoc base>.<managed assoc>.<fk> to <assoc base>.<managed assoc>_<fk>
|
|
120
|
+
*
|
|
121
|
+
* Or in other words: Allow using the foreign keys of managed associations in on-conditions
|
|
122
|
+
*
|
|
123
|
+
* @param {CSN.Artifact} artifact Artifact to check
|
|
124
|
+
* @param {string} artifactName Name of the artifact
|
|
125
|
+
*/
|
|
126
|
+
function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
|
|
127
|
+
for (const elemName in artifact.elements) {
|
|
128
|
+
const elem = artifact.elements[elemName];
|
|
129
|
+
// The association is an unmanaged on
|
|
130
|
+
if (!elem.keys && elem.target && elem.on) {
|
|
131
|
+
applyTransformationsOnNonDictionary(elem, 'on', {
|
|
132
|
+
ref: (refOwner, prop, ref, path) => {
|
|
133
|
+
// [<assoc base>.]<managed assoc>.<field>
|
|
134
|
+
if (ref.length > 1) {
|
|
135
|
+
const { links } = inspectRef(path);
|
|
136
|
+
if (links) {
|
|
137
|
+
// eslint-disable-next-line for-direction
|
|
138
|
+
for (let i = links.length - 1; i >= 0; i--) {
|
|
139
|
+
const link = links[i];
|
|
140
|
+
// We found the latest managed assoc path step
|
|
141
|
+
if (link.art && link.art.target && link.art.keys &&
|
|
142
|
+
// Doesn't work when ref-target (filter condition) or similar is used
|
|
143
|
+
!ref.slice(i).some(refElement => typeof refElement !== 'string')) {
|
|
144
|
+
// We join the managed assoc with everything following it
|
|
145
|
+
const sourceElementName = ref.slice(i).join(pathDelimiter);
|
|
146
|
+
const source = findSource(links, i - 1) || artifact;
|
|
147
|
+
// allow specifying managed assoc on the source side
|
|
148
|
+
const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
|
|
149
|
+
if (fks && fks.length >= 1) {
|
|
150
|
+
const fk = fks[0];
|
|
151
|
+
const managedAssocStepName = refOwner.ref[i];
|
|
152
|
+
const fkName = `${managedAssocStepName}${pathDelimiter}${fk.as}`;
|
|
153
|
+
if (source && source.elements[fkName])
|
|
154
|
+
refOwner.ref = [ ...ref.slice(0, i), fkName ];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
}, {}, [ 'definitions', artifactName, 'elements', elemName ]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Find out where the managed association is
|
|
167
|
+
*
|
|
168
|
+
* @param {Array} links
|
|
169
|
+
* @param {number} startIndex
|
|
170
|
+
* @returns {Object| undefined} CSN definition of the source of the managed association
|
|
171
|
+
*/
|
|
172
|
+
function findSource(links, startIndex) {
|
|
173
|
+
for (let i = startIndex; i >= 0; i--) {
|
|
174
|
+
const link = links[i];
|
|
175
|
+
// We found the latest assoc step - now check where that points to
|
|
176
|
+
if (link.art && link.art.target)
|
|
177
|
+
return csn.definitions[link.art.target];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
module.exports = {
|
|
185
|
+
attachOnConditions,
|
|
186
|
+
getManagedAssocStepsInOnConditionFinalizer,
|
|
187
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
forEachGeneric, forEachMemberRecursively, hasAnnotationValue, isPersistedOnDatabase,
|
|
5
|
+
getUtils,
|
|
6
|
+
} = require('../../model/csnUtils');
|
|
7
|
+
const transformUtils = require('../transformUtilsNew');
|
|
8
|
+
|
|
9
|
+
const exists = '@cds.persistence.exists';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Return a callback function for forEachDefinition that marks artifacts that are abstract or @cds.persistence.exists/skip
|
|
13
|
+
* with _ignore.
|
|
14
|
+
*
|
|
15
|
+
* @returns {(artifact: CSN.Artifact, artifactName) => void} Callback function for forEachDefinition
|
|
16
|
+
*/
|
|
17
|
+
function getAnnoProcessor() {
|
|
18
|
+
return handleCdsPersistence;
|
|
19
|
+
/**
|
|
20
|
+
* @param {CSN.Artifact} artifact
|
|
21
|
+
*/
|
|
22
|
+
function handleCdsPersistence(artifact) {
|
|
23
|
+
const ignoreArtifact = (artifact.kind === 'entity') &&
|
|
24
|
+
(artifact.abstract ||
|
|
25
|
+
hasAnnotationValue(artifact, '@cds.persistence.skip') ||
|
|
26
|
+
hasAnnotationValue(artifact, exists));
|
|
27
|
+
if (ignoreArtifact)
|
|
28
|
+
artifact._ignore = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Return a callback function for forEachDefinition that marks associations with _ignore
|
|
34
|
+
* if their target does not reach the database, i.e. marked with @cds.persistence.skip or is abstract
|
|
35
|
+
*
|
|
36
|
+
* @param {CSN.Model} csn
|
|
37
|
+
* @param {CSN.Options} options
|
|
38
|
+
* @param {object} messageFunctions
|
|
39
|
+
* @param {Function} messageFunctions.info
|
|
40
|
+
* @returns {(artifact: CSN.Artifact, artifactName: string, prop: string, path: CSN.Path) => void} Callback function for forEachDefinition
|
|
41
|
+
*/
|
|
42
|
+
function getAssocToSkippedIgnorer(csn, options, messageFunctions) {
|
|
43
|
+
const { info } = messageFunctions;
|
|
44
|
+
const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
|
|
45
|
+
|
|
46
|
+
const { isAssocOrComposition } = getUtils(csn);
|
|
47
|
+
|
|
48
|
+
return ignoreAssociationToSkippedTarget;
|
|
49
|
+
/**
|
|
50
|
+
* Associations that target a @cds.persistence.skip artifact must be removed
|
|
51
|
+
* from the persistence model
|
|
52
|
+
*
|
|
53
|
+
* @param {CSN.Artifact} artifact
|
|
54
|
+
* @param {string} artifactName
|
|
55
|
+
* @param {string} prop
|
|
56
|
+
* @param {CSN.Path} path
|
|
57
|
+
*/
|
|
58
|
+
function ignoreAssociationToSkippedTarget(artifact, artifactName, prop, path) {
|
|
59
|
+
if (isPersistedOnDatabase(artifact)) {
|
|
60
|
+
// TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
|
|
61
|
+
if (artifact.query) {
|
|
62
|
+
// If we do A2J, we don't need to check the mixin. Either it is used -> a join
|
|
63
|
+
// or published -> handled via elements/members. Unused mixins are removed anyway.
|
|
64
|
+
if (!doA2J && artifact.query.SELECT && artifact.query.SELECT.mixin)
|
|
65
|
+
forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
|
|
66
|
+
|
|
67
|
+
else if (!doA2J && artifact.query.SET && artifact.query.SET.mixin)
|
|
68
|
+
forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
|
|
69
|
+
}
|
|
70
|
+
forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Mark the given member with _ignore if it is an association/composition and it's target is unreachable.
|
|
76
|
+
*
|
|
77
|
+
* @param {CSN.Element} member
|
|
78
|
+
* @param {string} memberName
|
|
79
|
+
* @param {string} prop
|
|
80
|
+
* @param {CSN.Path} path
|
|
81
|
+
* @todo Why do we check for @cds.persistence.exists here if the parent-function only calls this for skip/abstract?
|
|
82
|
+
*/
|
|
83
|
+
function ignore(member, memberName, prop, path) {
|
|
84
|
+
if (options.sqlDialect === 'hana' && !member._ignore && member.target && isAssocOrComposition(member.type) && isUnreachableAssociationTarget(csn.definitions[member.target])) {
|
|
85
|
+
const targetAnnotation = hasAnnotationValue(csn.definitions[member.target], exists) ? exists : '@cds.persistence.skip';
|
|
86
|
+
info(null, path,
|
|
87
|
+
{ target: member.target, anno: targetAnnotation },
|
|
88
|
+
'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)');
|
|
89
|
+
member._ignore = true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check whether the given artifact is an unreachable association target because it will not "realy" hit the database:
|
|
95
|
+
* - @cds.persistence.skip/exists
|
|
96
|
+
* - abstract
|
|
97
|
+
*
|
|
98
|
+
* @param {CSN.Artifact} art
|
|
99
|
+
* @returns {boolean}
|
|
100
|
+
*/
|
|
101
|
+
function isUnreachableAssociationTarget(art) {
|
|
102
|
+
return !isPersistedOnDatabase(art) || hasAnnotationValue(art, exists);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Return a callback function for forEachDefinition that handles artifacts marked with @cds.persistence.table.
|
|
108
|
+
* If a .query artifact has this annotation, the .query will be deleted and it will be treated like a table.
|
|
109
|
+
*
|
|
110
|
+
* @param {CSN.Model} csn
|
|
111
|
+
* @param {CSN.Options} options
|
|
112
|
+
* @param {object} messageFunctions
|
|
113
|
+
* @param {Function} messageFunctions.error
|
|
114
|
+
* @returns {(artifact: CSN.Artifact, artifactName) => void} Callback function for forEachDefinition
|
|
115
|
+
*/
|
|
116
|
+
function getPersistenceTableProcessor(csn, options, messageFunctions ) {
|
|
117
|
+
const { error } = messageFunctions;
|
|
118
|
+
const {
|
|
119
|
+
recurseElements,
|
|
120
|
+
} = transformUtils.getTransformers(csn, options, '_');
|
|
121
|
+
|
|
122
|
+
return handleQueryish;
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {CSN.Artifact} artifact
|
|
127
|
+
* @param {string} artifactName
|
|
128
|
+
*/
|
|
129
|
+
function handleQueryish(artifact, artifactName) {
|
|
130
|
+
const stripQueryish = artifact.query && hasAnnotationValue(artifact, '@cds.persistence.table');
|
|
131
|
+
|
|
132
|
+
if (stripQueryish) {
|
|
133
|
+
artifact.kind = 'entity';
|
|
134
|
+
delete artifact.query;
|
|
135
|
+
|
|
136
|
+
recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
|
|
137
|
+
// All elements must have a type for this to work
|
|
138
|
+
if (!member._ignore && !member.kind && !member.type)
|
|
139
|
+
error(null, path, 'Expecting element to have a type if view is annotated with “@cds.persistence.table“');
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
getAnnoProcessor,
|
|
148
|
+
getAssocToSkippedIgnorer,
|
|
149
|
+
getPersistenceTableProcessor,
|
|
150
|
+
};
|