@sap/cds-compiler 4.0.0 → 4.1.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 +115 -5
- package/bin/cdsc.js +12 -12
- package/doc/CHANGELOG_BETA.md +11 -0
- package/lib/api/main.js +60 -12
- package/lib/api/validate.js +1 -1
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +84 -38
- package/lib/base/messages.js +11 -10
- package/lib/base/model.js +6 -2
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/onConditions.js +17 -12
- package/lib/checks/queryNoDbArtifacts.js +132 -72
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +1 -1
- package/lib/compiler/assert-consistency.js +44 -16
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +7 -8
- package/lib/compiler/checks.js +274 -197
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +3 -3
- package/lib/compiler/define.js +63 -50
- package/lib/compiler/extend.js +38 -20
- package/lib/compiler/finalize-parse-cdl.js +2 -1
- package/lib/compiler/generate.js +0 -8
- package/lib/compiler/index.js +9 -7
- package/lib/compiler/kick-start.js +2 -0
- package/lib/compiler/populate.js +139 -110
- package/lib/compiler/propagator.js +4 -3
- package/lib/compiler/resolve.js +157 -126
- package/lib/compiler/shared.js +706 -404
- package/lib/compiler/tweak-assocs.js +21 -10
- package/lib/compiler/utils.js +228 -36
- package/lib/edm/annotations/genericTranslation.js +30 -2
- package/lib/edm/edm.js +4 -1
- package/lib/edm/edmPreprocessor.js +12 -5
- package/lib/edm/edmUtils.js +2 -4
- package/lib/gen/Dictionary.json +34 -10
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +3987 -3963
- package/lib/json/from-csn.js +43 -47
- package/lib/json/to-csn.js +11 -11
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +59 -59
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +5 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +20 -16
- package/lib/model/revealInternalProperties.js +29 -21
- package/lib/model/sortViews.js +4 -2
- package/lib/modelCompare/compare.js +112 -39
- package/lib/modelCompare/utils/filter.js +54 -24
- package/lib/optionProcessor.js +6 -6
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +34 -20
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +77 -26
- package/lib/render/utils/common.js +3 -3
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +61 -20
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +8 -8
- package/lib/transform/db/expansion.js +17 -21
- package/lib/transform/db/flattening.js +23 -23
- package/lib/transform/db/rewriteCalculatedElements.js +20 -14
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +8 -7
- package/lib/transform/db/views.js +73 -33
- package/lib/transform/draft/db.js +11 -9
- package/lib/transform/draft/odata.js +1 -1
- package/lib/transform/{forOdataNew.js → forOdata.js} +56 -42
- package/lib/transform/forRelationalDB.js +69 -75
- package/lib/transform/localized.js +6 -5
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
- package/lib/transform/translateAssocsToJoins.js +14 -28
- package/package.json +1 -1
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
- package/share/messages/message-explanations.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/*
|
|
4
4
|
* Module for general (partial) CSN looper functions, respecting dictionaries and allowing
|
|
5
5
|
* to pass custom callbacks for certain properties like "ref".
|
|
6
6
|
*
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module lib/transform/db/applyTransformations
|
|
12
12
|
*/
|
|
13
|
+
|
|
14
|
+
|
|
13
15
|
const { setProp } = require('../../base/model');
|
|
14
16
|
|
|
15
17
|
|
|
@@ -18,11 +20,17 @@ const { setProp } = require('../../base/model');
|
|
|
18
20
|
* @param {string} prop The property of parent to start at
|
|
19
21
|
* @param {object} customTransformers Map of prop to transform and function to apply
|
|
20
22
|
* @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
|
|
21
|
-
* @param {applyTransformationsOptions} [
|
|
23
|
+
* @param {applyTransformationsOptions} [_options={}]
|
|
22
24
|
* @param {CSN.Path} path Path to parent
|
|
23
25
|
* @returns {object} parent with transformations applied
|
|
24
26
|
*/
|
|
25
|
-
function applyTransformationsInternal( parent, prop, customTransformers, artifactTransformers,
|
|
27
|
+
function applyTransformationsInternal( parent, prop, customTransformers, artifactTransformers, _options, path = [] ) {
|
|
28
|
+
const options = { ..._options };
|
|
29
|
+
if (!options.skipStandard)
|
|
30
|
+
options.skipStandard = { $tableConstraints: true };
|
|
31
|
+
else if (options.skipStandard.$tableConstraints === undefined)
|
|
32
|
+
options.skipStandard = { ...options.skipStandard, ...{ $tableConstraints: true } };
|
|
33
|
+
|
|
26
34
|
const transformers = {
|
|
27
35
|
elements: dictionary,
|
|
28
36
|
definitions: dictionary,
|
|
@@ -43,12 +51,13 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
43
51
|
standard( parent, name, parent[name] );
|
|
44
52
|
}
|
|
45
53
|
else {
|
|
46
|
-
standard(parent, prop, parent[prop]);
|
|
54
|
+
standard( parent, prop, parent[prop] );
|
|
47
55
|
}
|
|
48
56
|
return parent;
|
|
49
57
|
|
|
50
58
|
/**
|
|
51
|
-
* Default transformer for things that are not dictionaries
|
|
59
|
+
* Default transformer for things that are not dictionaries or dictionary entries,
|
|
60
|
+
* such as "type" or "keys".
|
|
52
61
|
* The customTransformers are applied here (and only here).
|
|
53
62
|
*
|
|
54
63
|
* @param {object | Array} _parent the thing that has _prop
|
|
@@ -59,7 +68,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
59
68
|
if (!node || typeof node !== 'object' ||
|
|
60
69
|
!{}.propertyIsEnumerable.call( _parent, _prop ) ||
|
|
61
70
|
(typeof _prop === 'string' && _prop.startsWith('@')) ||
|
|
62
|
-
(options.skipIgnore && node
|
|
71
|
+
(options.skipIgnore && node.$ignore) ||
|
|
63
72
|
options.skipStandard?.[_prop]
|
|
64
73
|
)
|
|
65
74
|
return;
|
|
@@ -75,13 +84,35 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
75
84
|
const trans = transformers[name] || standard;
|
|
76
85
|
if (customTransformers[name])
|
|
77
86
|
customTransformers[name](node, name, node[name], csnPath, _parent, _prop);
|
|
78
|
-
|
|
79
87
|
trans( node, name, node[name], csnPath );
|
|
80
88
|
}
|
|
81
89
|
}
|
|
82
90
|
csnPath.pop();
|
|
83
91
|
}
|
|
84
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Transformer for dictionary entries. Similar to standard(), but does not filter
|
|
95
|
+
* based on the given name. Otherwise, `options.skipStandards` could accidentally skip
|
|
96
|
+
* dictionary entries (e.g. entity called `@Name`).
|
|
97
|
+
*
|
|
98
|
+
* @param {object | Array} dict the thing that has _prop
|
|
99
|
+
* @param {string|number} entryName the name of the current property
|
|
100
|
+
* @param {object} node The value of node[_prop]
|
|
101
|
+
*/
|
|
102
|
+
function dictEntry( dict, entryName, node ) {
|
|
103
|
+
if (!node || typeof node !== 'object' || (options.skipIgnore && node.$ignore))
|
|
104
|
+
return;
|
|
105
|
+
|
|
106
|
+
csnPath.push( entryName );
|
|
107
|
+
for (const name of Object.getOwnPropertyNames( node )) {
|
|
108
|
+
const trans = transformers[name] || standard;
|
|
109
|
+
if (customTransformers[name])
|
|
110
|
+
customTransformers[name](node, name, node[name], csnPath, dict);
|
|
111
|
+
trans( node, name, node[name], csnPath );
|
|
112
|
+
}
|
|
113
|
+
csnPath.pop();
|
|
114
|
+
}
|
|
115
|
+
|
|
85
116
|
/**
|
|
86
117
|
* Transformer for things that are dictionaries - like "elements".
|
|
87
118
|
*
|
|
@@ -90,12 +121,12 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
90
121
|
* @param {object} dict The value of node[_prop]
|
|
91
122
|
*/
|
|
92
123
|
function dictionary( node, _prop, dict ) {
|
|
93
|
-
// Allow skipping
|
|
94
|
-
if (options.skipDict
|
|
124
|
+
// Allow skipping dictionaries like actions in forRelationalDB
|
|
125
|
+
if (options.skipDict?.[_prop] || dict == null) // with universal CSN, dictionaries might be null
|
|
95
126
|
return;
|
|
96
127
|
csnPath.push( _prop );
|
|
97
128
|
for (const name of Object.getOwnPropertyNames( dict ))
|
|
98
|
-
|
|
129
|
+
dictEntry( dict, name, dict[name] );
|
|
99
130
|
|
|
100
131
|
if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
|
|
101
132
|
setProp(node, `$${_prop}`, dict);
|
|
@@ -112,13 +143,9 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
112
143
|
function definitions( node, _prop, dict ) {
|
|
113
144
|
csnPath.push( _prop );
|
|
114
145
|
for (const name of Object.getOwnPropertyNames( dict )) {
|
|
115
|
-
|
|
116
|
-
(options && options.skipArtifact && options.skipArtifact(dict[name], name)) ||
|
|
117
|
-
(options && options.skip && options.skip.includes(dict[name].kind)) ||
|
|
118
|
-
false;
|
|
119
|
-
if (!skip) {
|
|
146
|
+
if (!isArtifactSkipped( dict, name )) {
|
|
120
147
|
artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
|
|
121
|
-
|
|
148
|
+
dictEntry( dict, name, dict[name] );
|
|
122
149
|
}
|
|
123
150
|
}
|
|
124
151
|
if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
|
|
@@ -126,6 +153,20 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
126
153
|
csnPath.pop();
|
|
127
154
|
}
|
|
128
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Whether the given artifact `dict[name]` is skipped via options.
|
|
158
|
+
*
|
|
159
|
+
* @param {object} dict
|
|
160
|
+
* @param {string} name
|
|
161
|
+
* @returns {boolean}
|
|
162
|
+
*/
|
|
163
|
+
function isArtifactSkipped( dict, name ) {
|
|
164
|
+
return options && ((options.allowArtifact && !options.allowArtifact(dict[name], name)) ||
|
|
165
|
+
(options.skipArtifact && options.skipArtifact(dict[name], name)) ||
|
|
166
|
+
(options.skip?.includes(dict[name].kind))) ||
|
|
167
|
+
false;
|
|
168
|
+
}
|
|
169
|
+
|
|
129
170
|
/**
|
|
130
171
|
* Keep looping through the pathRef - because in a .ref we can have .args and .where
|
|
131
172
|
*
|
|
@@ -173,7 +214,7 @@ function applyTransformations( csn, customTransformers = {}, artifactTransformer
|
|
|
173
214
|
if (options.skipIgnore === undefined)
|
|
174
215
|
options.skipIgnore = true;
|
|
175
216
|
|
|
176
|
-
if (csn
|
|
217
|
+
if (csn?.definitions)
|
|
177
218
|
return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, options);
|
|
178
219
|
return csn;
|
|
179
220
|
}
|
|
@@ -230,12 +271,12 @@ module.exports = {
|
|
|
230
271
|
|
|
231
272
|
/**
|
|
232
273
|
* @typedef {object} applyTransformationsOptions
|
|
233
|
-
* @property {(artifact, name) => boolean} [allowArtifact] to only allow certain artifacts
|
|
234
|
-
* @property {(artifact, name) => boolean} [skipArtifact] to skip certain artifacts
|
|
274
|
+
* @property {(artifact: CSN.Artifact, name: string) => boolean} [allowArtifact] to only allow certain artifacts
|
|
275
|
+
* @property {(artifact: CSN.Artifact, name: string) => boolean} [skipArtifact] to skip certain artifacts
|
|
235
276
|
* @property {boolean} [drillRef] whether to drill into infix/args
|
|
236
277
|
* @property {string[]} [skip] skip definitions from certain kind
|
|
237
278
|
* @property {object} [skipStandard] stop drill-down on certain "standard" props
|
|
238
279
|
* @property {object} [skipDict] stop drill-down on certain "dictionary" props
|
|
239
|
-
* @property {boolean} [skipIgnore=true] Whether to skip
|
|
280
|
+
* @property {boolean} [skipIgnore=true] Whether to skip $ignore elements or not
|
|
240
281
|
* @property {boolean} [directDict=false] Implicitly set via applyTransformationsOnDictionary
|
|
241
282
|
*/
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { forEachDefinition, hasAnnotationValue } = require('../../model/csnUtils');
|
|
4
|
-
const { getTransformers } = require('../
|
|
5
|
-
const { setProp } = require('../../base/model');
|
|
4
|
+
const { getTransformers } = require('../transformUtils');
|
|
6
5
|
const { pathName } = require('../../compiler/utils');
|
|
7
6
|
|
|
8
7
|
|
|
@@ -97,7 +96,7 @@ function processAssertUnique( csn, options, error, info ) {
|
|
|
97
96
|
}
|
|
98
97
|
// 10) Store remaining paths (if any) in constraint dictionary
|
|
99
98
|
if (flattenedPathObjects.length)
|
|
100
|
-
constraintDict[constraintName.join('.')] = flattenedPathObjects;
|
|
99
|
+
constraintDict[constraintName.join('.')] = { paths: flattenedPathObjects, parentTable: artifactName };
|
|
101
100
|
}
|
|
102
101
|
}
|
|
103
102
|
|
|
@@ -110,9 +109,9 @@ function processAssertUnique( csn, options, error, info ) {
|
|
|
110
109
|
// preserve dictionary in '$tableConstraints' on the artifact for path rewriting and rendering
|
|
111
110
|
if (Object.keys(constraintDict).length) {
|
|
112
111
|
if (!('$tableConstraints' in artifact))
|
|
113
|
-
|
|
112
|
+
artifact.$tableConstraints = Object.create(null);
|
|
114
113
|
|
|
115
|
-
|
|
114
|
+
artifact.$tableConstraints.unique = constraintDict;
|
|
116
115
|
}
|
|
117
116
|
|
|
118
117
|
/**
|
|
@@ -286,7 +285,7 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
|
|
|
286
285
|
}
|
|
287
286
|
for (const uniqueConstraint in artifact.$tableConstraints.unique) {
|
|
288
287
|
// iterate over each constraint
|
|
289
|
-
const c = uniqueConstraints[uniqueConstraint];
|
|
288
|
+
const c = uniqueConstraints[uniqueConstraint].paths;
|
|
290
289
|
const rewrittenPaths = [];
|
|
291
290
|
// and inspect each path of the constraint
|
|
292
291
|
c.forEach((cpath) => {
|
|
@@ -319,7 +318,7 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
|
|
|
319
318
|
}
|
|
320
319
|
});
|
|
321
320
|
// preserve the rewritten and filtered paths for toSql
|
|
322
|
-
uniqueConstraints[uniqueConstraint] = rewrittenPaths;
|
|
321
|
+
uniqueConstraints[uniqueConstraint] = { paths: rewrittenPaths, parentTable: uniqueConstraints[uniqueConstraint].parentTable };
|
|
323
322
|
|
|
324
323
|
// now add the index for HANA CDS
|
|
325
324
|
if (options.transformation === 'hdbcds') {
|
|
@@ -335,7 +334,7 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
|
|
|
335
334
|
artifact.technicalConfig.hana.indexes[uniqueConstraint] = index;
|
|
336
335
|
}
|
|
337
336
|
}
|
|
338
|
-
|
|
337
|
+
artifact.$tableConstraints.unique = uniqueConstraints;
|
|
339
338
|
}
|
|
340
339
|
}
|
|
341
340
|
}
|
|
@@ -48,8 +48,8 @@ function attachOnConditions( csn, csnUtils, pathDelimiter ) {
|
|
|
48
48
|
// Assemble an ON-condition with the foreign keys created in earlier steps
|
|
49
49
|
const onCondParts = [];
|
|
50
50
|
let joinWithAnd = false;
|
|
51
|
-
if (elem.keys.length === 0) { // TODO: really kill instead of
|
|
52
|
-
elem
|
|
51
|
+
if (elem.keys.length === 0) { // TODO: really kill instead of $ignore?
|
|
52
|
+
elem.$ignore = true;
|
|
53
53
|
}
|
|
54
54
|
else {
|
|
55
55
|
for (const foreignKey of elem.keys) {
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
const {
|
|
4
4
|
forEachGeneric, forEachMemberRecursively, hasAnnotationValue, isPersistedOnDatabase,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
|
-
const transformUtils = require('../
|
|
6
|
+
const transformUtils = require('../transformUtils');
|
|
7
7
|
|
|
8
8
|
const exists = '@cds.persistence.exists';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Return a callback function for forEachDefinition that marks artifacts that are abstract or @cds.persistence.exists/skip
|
|
12
|
-
* with
|
|
12
|
+
* with $ignore.
|
|
13
13
|
*
|
|
14
14
|
* @returns {(artifact: CSN.Artifact, artifactName) => void} Callback function for forEachDefinition
|
|
15
15
|
*/
|
|
@@ -24,12 +24,12 @@ function getAnnoProcessor() {
|
|
|
24
24
|
hasAnnotationValue(artifact, '@cds.persistence.skip') ||
|
|
25
25
|
hasAnnotationValue(artifact, exists));
|
|
26
26
|
if (ignoreArtifact)
|
|
27
|
-
artifact
|
|
27
|
+
artifact.$ignore = true;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Return a callback function for forEachDefinition that marks associations with
|
|
32
|
+
* Return a callback function for forEachDefinition that marks associations with $ignore
|
|
33
33
|
* if their target does not reach the database, i.e. marked with @cds.persistence.skip or is abstract
|
|
34
34
|
*
|
|
35
35
|
* @param {CSN.Model} csn
|
|
@@ -72,7 +72,7 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* Mark the given member with
|
|
75
|
+
* Mark the given member with $ignore if it is an association/composition and its target is unreachable.
|
|
76
76
|
*
|
|
77
77
|
* @param {CSN.Element} member
|
|
78
78
|
* @param {string} memberName
|
|
@@ -81,13 +81,13 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
|
|
|
81
81
|
*/
|
|
82
82
|
function ignore( member, memberName, prop, path ) {
|
|
83
83
|
if (options.sqlDialect === 'hana' &&
|
|
84
|
-
!member
|
|
84
|
+
!member.$ignore && member.target &&
|
|
85
85
|
isAssocOrComposition(member) &&
|
|
86
86
|
!isPersistedOnDatabase(csn.definitions[member.target])) {
|
|
87
87
|
info(null, path,
|
|
88
88
|
{ target: member.target, anno: '@cds.persistence.skip' },
|
|
89
89
|
'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)');
|
|
90
|
-
member
|
|
90
|
+
member.$ignore = true;
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
}
|
|
@@ -124,7 +124,7 @@ function getPersistenceTableProcessor( csn, options, messageFunctions ) {
|
|
|
124
124
|
|
|
125
125
|
recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
|
|
126
126
|
// All elements must have a type for this to work
|
|
127
|
-
if (!member
|
|
127
|
+
if (!member.$ignore && !member.kind && !member.type) {
|
|
128
128
|
error(null, path, { anno: '@cds.persistence.table' },
|
|
129
129
|
'Expecting element to have a type if view is annotated with $(ANNO)');
|
|
130
130
|
}
|
|
@@ -274,15 +274,8 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
274
274
|
return { columns, toMany: [] };
|
|
275
275
|
|
|
276
276
|
for (const col of columns) {
|
|
277
|
-
if (col.expand) {
|
|
278
|
-
|
|
279
|
-
const { expanded, toManys } = expandInline(root, col, col.ref || [], [ dbName(col) ]);
|
|
280
|
-
|
|
281
|
-
allToMany.push(...toManys);
|
|
282
|
-
newThing.push(...expanded);
|
|
283
|
-
}
|
|
284
|
-
else if (col.inline) {
|
|
285
|
-
const { expanded, toManys } = expandInline(root, col, col.ref || [], []);
|
|
277
|
+
if (col.expand || col.inline) {
|
|
278
|
+
const { expanded, toManys } = expandInline(root, col, col.ref || [], col.expand ? [ dbName(col) ] : []);
|
|
286
279
|
|
|
287
280
|
allToMany.push(...toManys);
|
|
288
281
|
newThing.push(...expanded);
|
|
@@ -335,18 +328,21 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
|
|
|
335
328
|
});
|
|
336
329
|
toManys.push({ art: current, ref: currentRef, as: currentAlias.join(pathDelimiter) });
|
|
337
330
|
}
|
|
338
|
-
else if (current.expand) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
331
|
+
else if (current.expand || current.inline) {
|
|
332
|
+
const withoutStar = replaceStar(nextBase(current, base), current.expand || current.inline, current.excluding);
|
|
333
|
+
current[current.expand ? 'expand' : 'inline'] = withoutStar;
|
|
334
|
+
for (let i = withoutStar.length - 1; i >= 0; i--) {
|
|
335
|
+
const sub = withoutStar[i];
|
|
336
|
+
let subRef;
|
|
337
|
+
if (sub.ref) {
|
|
338
|
+
// Each expand/inline can introduce another layer of $self/$projection. Since $self is
|
|
339
|
+
// a path-breakout, we can simply use the ref without outer expand/inline-references.
|
|
340
|
+
subRef = (sub.$scope === '$self') ? sub.ref : currentRef.concat(sub.ref);
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
subRef = currentRef;
|
|
344
|
+
}
|
|
345
|
+
stack.push([ nextBase(current, base), sub, subRef, !sub.inline ? currentAlias.concat(dbName(sub)) : currentAlias ]);
|
|
350
346
|
}
|
|
351
347
|
}
|
|
352
348
|
else if (current.xpr || current.args) {
|
|
@@ -5,7 +5,7 @@ const {
|
|
|
5
5
|
isBuiltinType, cloneCsnNonDict,
|
|
6
6
|
copyAnnotations, implicitAs, isDeepEqual,
|
|
7
7
|
} = require('../../model/csnUtils');
|
|
8
|
-
const transformUtils = require('../
|
|
8
|
+
const transformUtils = require('../transformUtils');
|
|
9
9
|
const { csnRefs } = require('../../model/csnRefs');
|
|
10
10
|
const { setProp } = require('../../base/model');
|
|
11
11
|
const { forEach } = require('../../utils/objectUtils');
|
|
@@ -79,24 +79,23 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
|
|
|
79
79
|
const ignoreOdataKinds = { aspect: 1, event: 1, type: 1 };
|
|
80
80
|
const replaceWithDummyKinds = { action: 1, function: 1, event: 1 };
|
|
81
81
|
applyTransformations(csn, {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (cast.type && !isBuiltinType(cast.type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(cast.type, path) && !isODataItems(cast.type)))
|
|
85
|
-
toFinalBaseType(parent.cast, resolved, true);
|
|
86
|
-
},
|
|
87
|
-
// @ts-ignore
|
|
88
|
-
type: (parent, prop, type, path) => {
|
|
89
|
-
if (options.toOdata && parent.kind && parent.kind in ignoreOdataKinds)
|
|
82
|
+
type: (node, prop, type, path, parent, parentProp) => {
|
|
83
|
+
if (options.toOdata && node.kind && node.kind in ignoreOdataKinds)
|
|
90
84
|
return;
|
|
85
|
+
if (parentProp === 'cast') {
|
|
86
|
+
const e = csnUtils.getFinalTypeInfo(type, t => resolved.get(t)?.art || csnUtils.artifactRef(t));
|
|
87
|
+
if (!e || e.items || e.elements)
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
91
90
|
if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type, path) && !isODataItems(type))) {
|
|
92
|
-
toFinalBaseType(
|
|
91
|
+
toFinalBaseType(node, resolved, true);
|
|
93
92
|
|
|
94
|
-
if (
|
|
95
|
-
toFinalBaseType(
|
|
93
|
+
if (node.items) // items could have unresolved types
|
|
94
|
+
toFinalBaseType(node.items, resolved, true);
|
|
96
95
|
|
|
97
96
|
// structured types might not have the child-types replaced.
|
|
98
97
|
// Drill down to ensure this.
|
|
99
|
-
let nextElements =
|
|
98
|
+
let nextElements = node.elements || node.items?.elements;
|
|
100
99
|
if (nextElements) {
|
|
101
100
|
const stack = [ nextElements ];
|
|
102
101
|
while (stack.length > 0) {
|
|
@@ -111,9 +110,9 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
|
|
|
111
110
|
}
|
|
112
111
|
}
|
|
113
112
|
|
|
114
|
-
const directLocalized =
|
|
113
|
+
const directLocalized = node.localized || false;
|
|
115
114
|
if (!directLocalized && !options.toOdata)
|
|
116
|
-
removeLocalized(
|
|
115
|
+
removeLocalized(node);
|
|
117
116
|
}
|
|
118
117
|
},
|
|
119
118
|
}, [ (definitions, artifactName, artifact) => {
|
|
@@ -284,8 +283,7 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
|
|
|
284
283
|
forEach(dict, (elementName, element) => {
|
|
285
284
|
if (element.elements) {
|
|
286
285
|
// Ignore the structured element, replace it by its flattened form
|
|
287
|
-
|
|
288
|
-
element._ignore = true;
|
|
286
|
+
element.$ignore = true;
|
|
289
287
|
|
|
290
288
|
const branches = getBranches(element, elementName, effectiveType, pathDelimiter);
|
|
291
289
|
const flatElems = flattenStructuredElement(element, elementName, [], path.concat([ 'elements', elementName ]));
|
|
@@ -308,6 +306,8 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
|
|
|
308
306
|
|
|
309
307
|
|
|
310
308
|
if (flatElement.type && isAssocOrComposition(flatElement) && flatElement.on) {
|
|
309
|
+
// unmanaged relations can't be primary key
|
|
310
|
+
delete flatElement.key;
|
|
311
311
|
// Make refs resolvable by fixing the first ref step
|
|
312
312
|
for (const onPart of flatElement.on) {
|
|
313
313
|
if (onPart.ref) {
|
|
@@ -441,6 +441,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
|
|
|
441
441
|
* @param {*} path
|
|
442
442
|
*/
|
|
443
443
|
function flattenFKs( assoc, assocName, path ) {
|
|
444
|
+
// TODO Depth first search and not iterate mark and sweep approach
|
|
444
445
|
let finished = false;
|
|
445
446
|
while (!finished) {
|
|
446
447
|
const newKeys = [];
|
|
@@ -465,8 +466,6 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
|
|
|
465
466
|
const { ref } = assoc.keys[i];
|
|
466
467
|
if (isStructured(art)) {
|
|
467
468
|
done = false;
|
|
468
|
-
// Mark this element to filter it later - not needed after expansion
|
|
469
|
-
setProp(assoc.keys[i], '$toDelete', true);
|
|
470
469
|
const flat = flattenStructuredElement(art, ref[ref.length - 1], [], pathToKey);
|
|
471
470
|
Object.keys(flat).forEach((flatElemName) => {
|
|
472
471
|
const key = assoc.keys[i];
|
|
@@ -502,12 +501,10 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
|
|
|
502
501
|
}
|
|
503
502
|
else if (art.target) {
|
|
504
503
|
done = false;
|
|
505
|
-
// Mark this element to filter it later - not needed after expansion
|
|
506
|
-
setProp(assoc.keys[i], '$toDelete', true);
|
|
507
504
|
// Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
|
|
508
505
|
// Add the newly generated foreign keys to the end - they will be picked up later on
|
|
509
506
|
// Recursive solutions run into call stack issues
|
|
510
|
-
art.keys
|
|
507
|
+
art.keys?.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i], ref)));
|
|
511
508
|
}
|
|
512
509
|
else if (assoc.keys[i].ref && !assoc.keys[i].as) {
|
|
513
510
|
setProp(assoc.keys[i], inferredAlias, true);
|
|
@@ -523,7 +520,6 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
|
|
|
523
520
|
}
|
|
524
521
|
return done;
|
|
525
522
|
}
|
|
526
|
-
assoc.keys = assoc.keys.filter(o => !o.$toDelete);
|
|
527
523
|
|
|
528
524
|
/**
|
|
529
525
|
* Clone base and extend the .ref and .as of the clone with the .ref and .as of ref.
|
|
@@ -612,6 +608,10 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
|
|
|
612
608
|
return acc;
|
|
613
609
|
}, Object.create(null));
|
|
614
610
|
|
|
611
|
+
// set default for single foreign key from association (if available)
|
|
612
|
+
if (element.default?.val !== undefined && fks.length === 1)
|
|
613
|
+
fks[0][1].default = element.default;
|
|
614
|
+
|
|
615
615
|
// check for duplicate foreign keys
|
|
616
616
|
Object.entries(refCount).forEach(([ name, occ ]) => {
|
|
617
617
|
if (occ > 1)
|
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
const { setProp } = require('../../base/model');
|
|
4
4
|
const { CompilerAssertion } = require('../../base/error');
|
|
5
5
|
const {
|
|
6
|
-
forEachDefinition,
|
|
6
|
+
forEachDefinition,
|
|
7
|
+
applyTransformationsOnNonDictionary,
|
|
8
|
+
applyTransformationsOnDictionary,
|
|
9
|
+
implicitAs,
|
|
10
|
+
cloneCsnNonDict,
|
|
7
11
|
forEachMemberRecursively,
|
|
8
12
|
} = require('../../model/csnUtils');
|
|
9
13
|
const { getBranches } = require('./flattening');
|
|
10
14
|
const { getColumnMap } = require('./views');
|
|
11
|
-
const {
|
|
15
|
+
const { requireForeignKeyAccess } = require('../../checks/onConditions');
|
|
12
16
|
|
|
13
17
|
const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '$scope' ] };
|
|
14
18
|
|
|
@@ -19,11 +23,13 @@ const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '
|
|
|
19
23
|
*
|
|
20
24
|
* @param {CSN.Model} csn
|
|
21
25
|
* @param {CSN.Options} options
|
|
26
|
+
* @param {object} csnUtils
|
|
22
27
|
* @param {string} pathDelimiter
|
|
23
|
-
* @param {
|
|
28
|
+
* @param {object} messageFunctions
|
|
24
29
|
*/
|
|
25
|
-
function rewriteCalculatedElementsInViews( csn, options, pathDelimiter,
|
|
26
|
-
const { inspectRef, effectiveType } =
|
|
30
|
+
function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter, messageFunctions ) {
|
|
31
|
+
const { inspectRef, effectiveType } = csnUtils;
|
|
32
|
+
const { error } = messageFunctions;
|
|
27
33
|
|
|
28
34
|
const views = [];
|
|
29
35
|
const entities = [];
|
|
@@ -121,7 +127,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
121
127
|
}
|
|
122
128
|
else {
|
|
123
129
|
// It's a managed association - access of the foreign keys is allowed
|
|
124
|
-
|
|
130
|
+
requireForeignKeyAccess(parent, i, (errorIndex) => {
|
|
125
131
|
error('ref-unexpected-navigation', csnPath, {
|
|
126
132
|
'#': 'calc-non-fk', id, elemref: parent, name: value[errorIndex].id || value[errorIndex],
|
|
127
133
|
});
|
|
@@ -412,7 +418,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
412
418
|
* @param {CSN.QuerySelect} SELECT
|
|
413
419
|
* @returns {object}
|
|
414
420
|
*/
|
|
415
|
-
function
|
|
421
|
+
function getDirectlyAddressableElements( SELECT ) {
|
|
416
422
|
const { from } = SELECT;
|
|
417
423
|
if (from.ref) {
|
|
418
424
|
return from._art.elements;
|
|
@@ -422,7 +428,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
422
428
|
}
|
|
423
429
|
else if (from.SET) {
|
|
424
430
|
// args[0] could be SELECT or UNION
|
|
425
|
-
return
|
|
431
|
+
return getDirectlyAddressableElements({ from: from.SET.args[0] });
|
|
426
432
|
}
|
|
427
433
|
else if (from.args) {
|
|
428
434
|
const mergedElements = Object.create(null);
|
|
@@ -432,7 +438,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
432
438
|
mergedElements[elementName] = arg._art.elements[elementName];
|
|
433
439
|
}
|
|
434
440
|
else if (arg.SET) {
|
|
435
|
-
return
|
|
441
|
+
return getDirectlyAddressableElements({ from: arg.SET.args[0] });
|
|
436
442
|
}
|
|
437
443
|
else if (arg.SELECT) { // TODO: UNION
|
|
438
444
|
for (const elementName in arg.SELECT.elements)
|
|
@@ -440,7 +446,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
440
446
|
}
|
|
441
447
|
else if (arg.args) { // TODO: Is it safe to do recursion here?
|
|
442
448
|
for (const subarg of arg.args) {
|
|
443
|
-
const elements =
|
|
449
|
+
const elements = getDirectlyAddressableElements({ from: subarg });
|
|
444
450
|
for (const elementName in elements)
|
|
445
451
|
mergedElements[elementName] = elements[elementName];
|
|
446
452
|
}
|
|
@@ -465,8 +471,8 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
|
|
|
465
471
|
*/
|
|
466
472
|
function makeAllCalculatedElementsExplicitColumns( elements, SELECT, containsExpandInline ) {
|
|
467
473
|
const cleanupCallbacks = [];
|
|
468
|
-
const root =
|
|
469
|
-
const columnMap = getColumnMap( { SELECT });
|
|
474
|
+
const root = getDirectlyAddressableElements(SELECT);
|
|
475
|
+
const columnMap = getColumnMap( { SELECT }, csnUtils );
|
|
470
476
|
const hasStar = SELECT.columns.includes('*');
|
|
471
477
|
const unfoldingMap = {};
|
|
472
478
|
let starContainsCalculated = false;
|
|
@@ -699,9 +705,9 @@ function processCalculatedElementsInEntities( csn ) {
|
|
|
699
705
|
*/
|
|
700
706
|
function removeDummyValueInEntity( artifact, path ) {
|
|
701
707
|
applyTransformationsOnDictionary(artifact.elements, {
|
|
702
|
-
value: (parent, prop, value, p,
|
|
708
|
+
value: (parent, prop, value, p, elements) => {
|
|
703
709
|
if (!value.stored)
|
|
704
|
-
delete
|
|
710
|
+
delete elements[p[p.length - 1]];
|
|
705
711
|
},
|
|
706
712
|
}, {}, path.concat( 'elements' ));
|
|
707
713
|
}
|
|
@@ -5,7 +5,7 @@ const {
|
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
const { implicitAs } = require('../../model/csnRefs');
|
|
7
7
|
const { setProp } = require('../../base/model');
|
|
8
|
-
const { getTransformers } = require('../
|
|
8
|
+
const { getTransformers } = require('../transformUtils');
|
|
9
9
|
|
|
10
10
|
const validToString = '@cds.valid.to';
|
|
11
11
|
const validFromString = '@cds.valid.from';
|
|
@@ -53,22 +53,23 @@ const { ModelError } = require('../../base/error');
|
|
|
53
53
|
function handleExists( csn, options, error, inspectRef, initDefinition, dropDefinitionCache ) {
|
|
54
54
|
const generatedExists = new WeakMap();
|
|
55
55
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
56
|
+
// drop cache: Otherwise, the projection/query hack below won't work, because csnRefs
|
|
57
|
+
// thinks that the artifact was already initialized (including all queries).
|
|
58
|
+
dropDefinitionCache(artifact);
|
|
56
59
|
if (artifact.projection) // do the same hack we do for the other stuff...
|
|
57
60
|
artifact.query = { SELECT: artifact.projection };
|
|
58
61
|
|
|
59
62
|
if (artifact.query) {
|
|
60
|
-
forAllQueries(artifact.query, (query, path)
|
|
63
|
+
forAllQueries(artifact.query, function handleExistsQuery(query, path) {
|
|
61
64
|
if (!generatedExists.has(query)) {
|
|
62
65
|
const toProcess = []; // Collect all expressions we need to process here
|
|
63
|
-
if (query.SELECT
|
|
66
|
+
if (query.SELECT?.where?.length > 1)
|
|
64
67
|
toProcess.push([ path.slice(0, -1), path.concat('where') ]);
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
if (query.SELECT && query.SELECT.columns)
|
|
69
|
+
if (query.SELECT?.columns)
|
|
68
70
|
toProcess.push([ path.slice(0, -1), path.concat('columns') ]);
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
if (query.SELECT && query.SELECT.from.on )
|
|
72
|
+
if (query.SELECT?.from.on)
|
|
72
73
|
toProcess.push([ path.slice(0, -1), path.concat([ 'from', 'on' ]) ]);
|
|
73
74
|
|
|
74
75
|
for (const [ , exprPath ] of toProcess) {
|
|
@@ -268,7 +269,7 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
268
269
|
|
|
269
270
|
const { links } = inspectRef(exprPath.concat(i));
|
|
270
271
|
|
|
271
|
-
const assocs = links.filter(link => link.art
|
|
272
|
+
const assocs = links.filter(link => link.art?.target).map(link => current.ref[link.idx]);
|
|
272
273
|
|
|
273
274
|
checkForInvalidAssoc(assocs);
|
|
274
275
|
}
|