@sap/cds-compiler 2.5.2 → 2.11.0
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 +235 -9
- package/bin/cdsc.js +44 -27
- package/bin/cdsse.js +1 -0
- package/doc/CHANGELOG_BETA.md +37 -3
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +37 -123
- package/lib/api/options.js +27 -15
- package/lib/api/validate.js +34 -9
- package/lib/backends.js +9 -89
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +73 -11
- package/lib/base/messages.js +86 -30
- package/lib/base/model.js +6 -6
- package/lib/base/optionProcessorHelper.js +56 -22
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +25 -7
- package/lib/checks/selectItems.js +29 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +41 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +60 -7
- package/lib/compiler/assert-consistency.js +23 -7
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +30 -1
- package/lib/compiler/checks.js +8 -5
- package/lib/compiler/definer.js +157 -133
- package/lib/compiler/index.js +89 -31
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +375 -185
- package/lib/compiler/shared.js +49 -202
- package/lib/compiler/utils.js +173 -0
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +104 -108
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +388 -146
- package/lib/edm/edmUtils.js +104 -34
- package/lib/gen/Dictionary.json +22 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +28 -1
- package/lib/gen/language.tokens +79 -69
- package/lib/gen/languageLexer.interp +28 -1
- package/lib/gen/languageLexer.js +879 -805
- package/lib/gen/languageLexer.tokens +71 -62
- package/lib/gen/languageParser.js +5330 -4300
- package/lib/json/from-csn.js +110 -52
- package/lib/json/to-csn.js +434 -120
- package/lib/language/antlrParser.js +15 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +93 -26
- package/lib/language/language.g4 +172 -31
- package/lib/main.d.ts +216 -19
- package/lib/main.js +32 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +413 -149
- package/lib/model/csnUtils.js +286 -75
- package/lib/model/enrichCsn.js +50 -6
- package/lib/model/revealInternalProperties.js +22 -5
- package/lib/modelCompare/compare.js +39 -21
- package/lib/optionProcessor.js +35 -18
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +9 -6
- package/lib/render/toCdl.js +121 -36
- package/lib/render/toHdbcds.js +148 -98
- package/lib/render/toSql.js +114 -43
- package/lib/render/utils/common.js +8 -13
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/assertUnique.js +5 -6
- package/lib/transform/db/constraints.js +281 -106
- package/lib/transform/db/draft.js +11 -8
- package/lib/transform/db/expansion.js +584 -0
- package/lib/transform/db/flattening.js +341 -0
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/transformExists.js +345 -65
- package/lib/transform/db/views.js +438 -0
- package/lib/transform/forHanaNew.js +131 -793
- package/lib/transform/forOdataNew.js +30 -24
- package/lib/transform/localized.js +39 -10
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +60 -39
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +19 -18
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +144 -78
- package/lib/transform/translateAssocsToJoins.js +22 -27
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +5 -14
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
- package/lib/json/walker.js +0 -26
- package/lib/transform/sqlite +0 -0
- package/lib/utils/string.js +0 -17
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
hasAnnotationValue, getUtils,
|
|
5
|
+
applyTransformations,
|
|
6
|
+
setDependencies,
|
|
7
|
+
walkCsnPath,
|
|
8
|
+
} = require('../../model/csnUtils');
|
|
9
|
+
const { csnRefs, implicitAs } = require('../../model/csnRefs');
|
|
10
|
+
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* For keys, columns, groupBy and orderBy, expand structured things.
|
|
14
|
+
* Replace them with their flattened leaves, keeping the overall order intact.
|
|
15
|
+
*
|
|
16
|
+
* @param {CSN.Model} csn
|
|
17
|
+
* @param {CSN.Options} options
|
|
18
|
+
* @param {string} pathDelimiter
|
|
19
|
+
* @param {object} messageFunctions
|
|
20
|
+
* @param {Function} messageFunctions.error
|
|
21
|
+
* @param {Function} messageFunctions.info
|
|
22
|
+
* @param {Function} messageFunctions.throwWithError
|
|
23
|
+
*/
|
|
24
|
+
function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithError }) {
|
|
25
|
+
const {
|
|
26
|
+
isStructured, get$combined, getFinalBaseType, getServiceName,
|
|
27
|
+
} = getUtils(csn);
|
|
28
|
+
let { effectiveType, inspectRef } = csnRefs(csn);
|
|
29
|
+
|
|
30
|
+
if (isBetaEnabled(options, 'nestedProjections'))
|
|
31
|
+
rewriteExpandInline();
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
applyTransformations(csn, {
|
|
35
|
+
keys: (parent, name, keys, path) => {
|
|
36
|
+
parent.keys = expand(keys, path.concat('keys'), true);
|
|
37
|
+
},
|
|
38
|
+
columns: (parent, name, columns, path) => {
|
|
39
|
+
const artifact = csn.definitions[path[1]];
|
|
40
|
+
if (!hasAnnotationValue(artifact, '@cds.persistence.table')) {
|
|
41
|
+
const root = get$combined({ SELECT: parent });
|
|
42
|
+
parent.columns = replaceStar(root, columns, parent.excluding);
|
|
43
|
+
parent.columns = expand(parent.columns, path.concat('columns'), true);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
groupBy: (parent, name, groupBy, path) => {
|
|
47
|
+
parent.groupBy = expand(groupBy, path.concat('groupBy'));
|
|
48
|
+
},
|
|
49
|
+
orderBy: (parent, name, orderBy, path) => {
|
|
50
|
+
parent.orderBy = expand(orderBy, path.concat('orderBy'));
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Turn .expand/.inline into normal refs. @cds.persistence.skip .expand with to-many (and all transitive views).
|
|
56
|
+
* For such skipped things, error for usage of assoc pointing to them and and ignore publishing of assoc pointing to them.
|
|
57
|
+
*/
|
|
58
|
+
function rewriteExpandInline() {
|
|
59
|
+
const { cleanup, _dependents } = setDependencies(csn);
|
|
60
|
+
|
|
61
|
+
const entity = findAnEntity();
|
|
62
|
+
const toDummyfy = [];
|
|
63
|
+
|
|
64
|
+
applyTransformations(csn, {
|
|
65
|
+
columns: (parent, name, columns, path) => {
|
|
66
|
+
const artifact = csn.definitions[path[1]];
|
|
67
|
+
// get$combined expects a SET/SELECT - so we wrap the parent
|
|
68
|
+
// (which is the thing inside SET/SELECT)
|
|
69
|
+
// We can directly use SELECT here, as only projections and SELECT can have .columns
|
|
70
|
+
const root = get$combined({ SELECT: parent });
|
|
71
|
+
if (!hasAnnotationValue(artifact, '@cds.persistence.table')) {
|
|
72
|
+
const rewritten = rewrite(root, parent.columns, parent.excluding);
|
|
73
|
+
parent.columns = rewritten.columns;
|
|
74
|
+
if (rewritten.toMany.length > 0) {
|
|
75
|
+
markAsToDummyfy(artifact, path[1]);
|
|
76
|
+
if (getServiceName(path[1]) === null)
|
|
77
|
+
error( null, [ 'definitions', path[1] ], { name: path[1] }, 'Unexpected .expand with to-many association in entity $(NAME), which is outside any service');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
dummyfy();
|
|
84
|
+
|
|
85
|
+
cleanup.forEach(fn => fn());
|
|
86
|
+
|
|
87
|
+
({ effectiveType, inspectRef } = csnRefs(csn));
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
const publishing = [];
|
|
91
|
+
|
|
92
|
+
applyTransformations(csn, {
|
|
93
|
+
target: (parent, name, target, path) => {
|
|
94
|
+
if (toDummyfy.indexOf(target) !== -1) {
|
|
95
|
+
publishing.push({
|
|
96
|
+
parent, name, target, path: [ ...path ],
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
from: check,
|
|
101
|
+
columns: check,
|
|
102
|
+
where: check,
|
|
103
|
+
groupBy: check,
|
|
104
|
+
orderBy: check,
|
|
105
|
+
having: check,
|
|
106
|
+
limit: check,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check for usage of associations to skipped.
|
|
112
|
+
* While we're at it, kill publishing of such assocs in columns.
|
|
113
|
+
*
|
|
114
|
+
* @param {object} parent
|
|
115
|
+
* @param {string} name
|
|
116
|
+
* @param {Array} parts
|
|
117
|
+
* @param {CSN.Path} path
|
|
118
|
+
*/
|
|
119
|
+
function check(parent, name, parts, path) {
|
|
120
|
+
const inColumns = name === 'columns';
|
|
121
|
+
const kill = [];
|
|
122
|
+
for (let i = 0; i < parts.length; i++) {
|
|
123
|
+
const obj = parts[i];
|
|
124
|
+
if (!(obj && obj.ref) || obj.$scope === 'alias')
|
|
125
|
+
continue;
|
|
126
|
+
|
|
127
|
+
const links = obj._links || inspectRef(path.concat([ name, i ])).links;
|
|
128
|
+
|
|
129
|
+
if (!links)
|
|
130
|
+
continue;
|
|
131
|
+
|
|
132
|
+
// Don't check the last element - to allow association publishing in columns
|
|
133
|
+
for (let j = 0; j < (inColumns ? links.length - 1 : links.length); j++) {
|
|
134
|
+
const link = links[j];
|
|
135
|
+
if (!link)
|
|
136
|
+
continue;
|
|
137
|
+
|
|
138
|
+
const { art } = link;
|
|
139
|
+
if (!art)
|
|
140
|
+
continue;
|
|
141
|
+
|
|
142
|
+
const pathStep = obj.ref[j].id ? obj.ref[j].id : obj.ref[j];
|
|
143
|
+
const target = art.target ? art.target : pathStep;
|
|
144
|
+
if (toDummyfy.indexOf(target) !== -1) {
|
|
145
|
+
error( null, obj.$path, {
|
|
146
|
+
id: pathStep, elemref: obj, name,
|
|
147
|
+
}, 'Unexpected “@cds.persistence.skip” annotation on Association target $(NAME) of $(ID) in path $(ELEMREF) was skipped because of .expand in conjunction with to-many');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (inColumns) {
|
|
152
|
+
const { art } = links[links.length - 1];
|
|
153
|
+
|
|
154
|
+
if (art) {
|
|
155
|
+
const pathStep = obj.ref[obj.ref.length - 1].id ? obj.ref[obj.ref.length - 1].id : obj.ref[obj.ref.length - 1];
|
|
156
|
+
const target = art.target ? art.target : pathStep;
|
|
157
|
+
if (toDummyfy.indexOf(target) !== -1)
|
|
158
|
+
kill.push(i);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for (let i = kill.length - 1; i >= 0; i--)
|
|
164
|
+
parent[name].splice(kill[i]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// We would be broken if we continue with assoc usage to now skipped
|
|
168
|
+
throwWithError();
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
for (const {
|
|
172
|
+
parent, target, path,
|
|
173
|
+
} of publishing) {
|
|
174
|
+
const last = parent.$path[parent.$path.length - 1];
|
|
175
|
+
const grandparent = walkCsnPath(csn, parent.$path.slice(0, -1));
|
|
176
|
+
|
|
177
|
+
if (typeof last === 'number')
|
|
178
|
+
grandparent.splice(last);
|
|
179
|
+
else
|
|
180
|
+
delete grandparent[last];
|
|
181
|
+
|
|
182
|
+
info(null, path, { name: last, target }, 'Ignoring association $(NAME) with target $(TARGET), because it was skipped because of .expand in conjunction with to-many');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Mark the given artifact and all (transitively) dependent artifacts as `toDummify`.
|
|
187
|
+
* This means that they will be replaced with simple dummy views in @dummify
|
|
188
|
+
*
|
|
189
|
+
* @param {CSN.Artifact} artifact
|
|
190
|
+
* @param {string} name
|
|
191
|
+
*/
|
|
192
|
+
function markAsToDummyfy(artifact, name) {
|
|
193
|
+
const stack = [ [ artifact, name ] ];
|
|
194
|
+
while (stack.length > 0) {
|
|
195
|
+
const [ a, n ] = stack.pop();
|
|
196
|
+
if (a[_dependents]) {
|
|
197
|
+
Object.entries(a[_dependents]).forEach(([ dependentName, dependent ]) => {
|
|
198
|
+
stack.push([ dependent, dependentName ]);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
toDummyfy.push(n);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Replace the artifacts in `toDummify` with simple dummy views as produced by createDummyView.
|
|
207
|
+
*/
|
|
208
|
+
function dummyfy() {
|
|
209
|
+
for (const artifactName of [ ...new Set(toDummyfy) ])
|
|
210
|
+
csn.definitions[artifactName] = createDummyView(entity);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get the next base for resolving a *.
|
|
216
|
+
* Keep the current base unless we are now navigating into a structure or association.
|
|
217
|
+
*
|
|
218
|
+
* @param {CSN.Column} parent
|
|
219
|
+
* @param {CSN.Artifact} base The current base
|
|
220
|
+
* @returns {CSN.Artifact}
|
|
221
|
+
*/
|
|
222
|
+
function nextBase(parent, base) {
|
|
223
|
+
if (parent.ref) {
|
|
224
|
+
const finalBaseType = getFinalBaseType(parent._art.type);
|
|
225
|
+
const art = parent._art;
|
|
226
|
+
|
|
227
|
+
if (finalBaseType === 'cds.Association' || finalBaseType === 'cds.Composition')
|
|
228
|
+
return csn.definitions[art.target].elements;
|
|
229
|
+
|
|
230
|
+
return art.elements || finalBaseType.elements;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return base;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Rewrite expand and inline to "normal" refs
|
|
238
|
+
*
|
|
239
|
+
* @param {CSN.Artifact} root All elements visible fromt he query source ($combined)
|
|
240
|
+
* @param {CSN.Column[]} columns
|
|
241
|
+
* @param {string[]} excluding
|
|
242
|
+
* @returns {{columns: Array, toManys: Array}} Object with rewritten columns (.expand/.inline) and with any .expand + to-many
|
|
243
|
+
*/
|
|
244
|
+
function rewrite(root, columns, excluding) {
|
|
245
|
+
const allToMany = [];
|
|
246
|
+
const newThing = [];
|
|
247
|
+
// Replace stars - needs to happen here since the .expand/.inline first path step affects the root *
|
|
248
|
+
columns = replaceStar(root, columns, excluding);
|
|
249
|
+
for (let i = 0; i < columns.length; i++) {
|
|
250
|
+
const col = columns[i];
|
|
251
|
+
if (col.expand) {
|
|
252
|
+
// TODO: Can col.ref be empty without an as? Assumption is it cannot - if it has, it's an error, we throw, compiler checks.
|
|
253
|
+
const { expanded, toManys } = expandInline(root, col, col.ref || [], [ dbName(col) ]);
|
|
254
|
+
|
|
255
|
+
allToMany.push(...toManys);
|
|
256
|
+
newThing.push(...expanded);
|
|
257
|
+
}
|
|
258
|
+
else if (col.inline) {
|
|
259
|
+
const { expanded, toManys } = expandInline(root, col, col.ref || [], []);
|
|
260
|
+
|
|
261
|
+
allToMany.push(...toManys);
|
|
262
|
+
newThing.push(...expanded);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
newThing.push(col);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return { columns: newThing, toMany: allToMany };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Check wether the given object is a to-many association
|
|
274
|
+
*
|
|
275
|
+
* @param {CSN.Element} obj
|
|
276
|
+
* @returns {boolean}
|
|
277
|
+
*/
|
|
278
|
+
function isToMany(obj) {
|
|
279
|
+
if (!obj._art)
|
|
280
|
+
return false;
|
|
281
|
+
const eType = effectiveType(obj._art);
|
|
282
|
+
return (eType.type === 'cds.Association' || eType.type === 'cds.Composition') && eType.cardinality && eType.cardinality.max !== 1;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Rewrite the expand/inline. For expand, keep along the alias - for inline, only leaf-alias has effect.
|
|
287
|
+
* Expand * into the corresponding leaves - correctly handling .exlcluding and shadowing.
|
|
288
|
+
*
|
|
289
|
+
* Iterative, to not run into stack overflow.
|
|
290
|
+
*
|
|
291
|
+
* @param {CSN.Artifact} root All elements visible fromt he query source ($combined)
|
|
292
|
+
* @param {CSN.Column} col Column to expand
|
|
293
|
+
* @param {Array} ref Ref so far
|
|
294
|
+
* @param {Array} alias Any start-alias
|
|
295
|
+
* @returns {{expanded: Array, toManys: Array}} Object with expanded .expand/.inline and with any .expand + to-many
|
|
296
|
+
*/
|
|
297
|
+
function expandInline(root, col, ref, alias) {
|
|
298
|
+
const toManys = [];
|
|
299
|
+
const expanded = [];
|
|
300
|
+
const stack = [ [ root, col, ref, alias ] ];
|
|
301
|
+
|
|
302
|
+
while (stack.length > 0) {
|
|
303
|
+
const [ base, current, currentRef, currentAlias ] = stack.pop();
|
|
304
|
+
if (isToMany(current) && current.expand) {
|
|
305
|
+
toManys.push({ art: current, ref: currentRef, as: currentAlias.join(pathDelimiter) });
|
|
306
|
+
}
|
|
307
|
+
else if (current.expand) {
|
|
308
|
+
current.expand = replaceStar(nextBase(current, base), current.expand, current.excluding);
|
|
309
|
+
for (let i = current.expand.length - 1; i >= 0; i--) {
|
|
310
|
+
const sub = current.expand[i];
|
|
311
|
+
stack.push([ nextBase(current, base), sub, sub.ref ? currentRef.concat(sub.ref) : currentRef, !sub.inline ? currentAlias.concat(dbName(sub)) : currentAlias ]);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else if (current.inline) {
|
|
315
|
+
current.inline = replaceStar(nextBase(current, base), current.inline, current.excluding);
|
|
316
|
+
for (let i = current.inline.length - 1; i >= 0; i--) {
|
|
317
|
+
const sub = current.inline[i];
|
|
318
|
+
stack.push([ nextBase(current, base), sub, sub.ref ? currentRef.concat(sub.ref) : currentRef, !sub.inline ? currentAlias.concat(dbName(sub)) : currentAlias ]);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else if (current.xpr || current.args) {
|
|
322
|
+
// We need to re-write refs in the .xpr/.args so they stay resolvable - we need to prepend the currentRef
|
|
323
|
+
rewriteXprArgs(current, currentRef);
|
|
324
|
+
expanded.push(Object.assign({}, current, { as: currentAlias.join(pathDelimiter) } ));
|
|
325
|
+
}
|
|
326
|
+
else if (current.val !== undefined || current.func !== undefined) {
|
|
327
|
+
expanded.push(Object.assign(current, { as: currentAlias.join(pathDelimiter) }));
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
expanded.push({ ref: currentRef, as: currentAlias.join(pathDelimiter) });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return { expanded, toManys };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Rewrite refs in the .xpr/.args to stay resolvable
|
|
339
|
+
*
|
|
340
|
+
* @param {object} parent Thing that has an .xpr/.args
|
|
341
|
+
* @param {string[]} ref Ref so far
|
|
342
|
+
*/
|
|
343
|
+
function rewriteXprArgs(parent, ref) {
|
|
344
|
+
const stack = [ [ parent, ref ] ];
|
|
345
|
+
while (stack.length > 0) {
|
|
346
|
+
const [ current, currentRef ] = stack.pop();
|
|
347
|
+
if (current.xpr) {
|
|
348
|
+
for (let i = 0; i < current.xpr.length; i++) {
|
|
349
|
+
const part = current.xpr[i];
|
|
350
|
+
if (part.ref) {
|
|
351
|
+
part.ref = currentRef.concat(part.ref);
|
|
352
|
+
// part.as = currentAlias.concat(part.as || part.ref[ref.length - 1]).join(pathDelimiter);
|
|
353
|
+
current.xpr[i] = part;
|
|
354
|
+
stack.push([ part, part.ref ]);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
stack.push([ part, currentRef ]);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (current.args) {
|
|
362
|
+
for (let i = 0; i < current.args.length; i++) {
|
|
363
|
+
const part = current.args[i];
|
|
364
|
+
if (part.ref) {
|
|
365
|
+
part.ref = currentRef.concat(part.ref);
|
|
366
|
+
// part.as = currentAlias.concat(part.as || part.ref[ref.length - 1]).join(pathDelimiter);
|
|
367
|
+
current.args[i] = part;
|
|
368
|
+
stack.push([ part, part.ref ]);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
stack.push([ part, currentRef ]);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Find any entity from the model so we can use it as the query source for our dummies.
|
|
380
|
+
*
|
|
381
|
+
* @returns {string|null} Name of any entity
|
|
382
|
+
*/
|
|
383
|
+
function findAnEntity() {
|
|
384
|
+
for (const [ name, artifact ] of Object.entries(csn.definitions)) {
|
|
385
|
+
if (artifact.kind === 'entity' && !artifact.query)
|
|
386
|
+
return name;
|
|
387
|
+
}
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Create a simple dummy view marked with @cds.persistence.skip
|
|
393
|
+
*
|
|
394
|
+
* @param {string} source
|
|
395
|
+
* @returns {CSN.Artifact}
|
|
396
|
+
*/
|
|
397
|
+
function createDummyView(source) {
|
|
398
|
+
const elements = Object.create(null);
|
|
399
|
+
elements.one = {
|
|
400
|
+
'@Core.Computed': true,
|
|
401
|
+
type: 'cds.Integer',
|
|
402
|
+
};
|
|
403
|
+
const artifact = {
|
|
404
|
+
'@cds.persistence.skip': true,
|
|
405
|
+
kind: 'entity',
|
|
406
|
+
query: {
|
|
407
|
+
SELECT: {
|
|
408
|
+
from: {
|
|
409
|
+
ref: [
|
|
410
|
+
source,
|
|
411
|
+
],
|
|
412
|
+
},
|
|
413
|
+
columns: [
|
|
414
|
+
{
|
|
415
|
+
val: 1,
|
|
416
|
+
as: 'one',
|
|
417
|
+
cast: {
|
|
418
|
+
type: 'cds.Integer',
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
elements,
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
setProp(artifact, '$wasToMany', true);
|
|
428
|
+
|
|
429
|
+
return artifact;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Process thing and expand all structured refs inside
|
|
436
|
+
*
|
|
437
|
+
* @param {Array} thing
|
|
438
|
+
* @param {CSN.Path} path
|
|
439
|
+
* @param {boolean} [withAlias=false] Wether to "expand" the (implicit) alias aswell.
|
|
440
|
+
* @returns {Array} New array - with all structured things expanded
|
|
441
|
+
*/
|
|
442
|
+
function expand(thing, path, withAlias = false) {
|
|
443
|
+
const newThing = [];
|
|
444
|
+
for (let i = 0; i < thing.length; i++) {
|
|
445
|
+
const col = thing[i];
|
|
446
|
+
if (col.ref && col.$scope !== '$magic') {
|
|
447
|
+
const _art = col._art || inspectRef(path.concat(i)).art;
|
|
448
|
+
if (_art && isStructured(_art))
|
|
449
|
+
newThing.push(...expandRef(_art, col.ref, col.as, col.key || false, withAlias));
|
|
450
|
+
|
|
451
|
+
else
|
|
452
|
+
newThing.push(col);
|
|
453
|
+
}
|
|
454
|
+
else if (col.ref && col.$scope === '$magic' && ( col.ref[0] === '$user' || col.ref[0] === '$session' ) && !col.as) {
|
|
455
|
+
col.as = implicitAs(col.ref);
|
|
456
|
+
newThing.push(col);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
newThing.push(col);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return newThing;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Expand the ref and - if requested - expand the alias with it.
|
|
468
|
+
*
|
|
469
|
+
* Iterative, to not run into stack overflow.
|
|
470
|
+
*
|
|
471
|
+
* @param {CSN.Element} art
|
|
472
|
+
* @param {Array} ref
|
|
473
|
+
* @param {Array} alias
|
|
474
|
+
* @param {boolean} isKey True if the ref obj has property key: true
|
|
475
|
+
* @param {boolean} withAlias
|
|
476
|
+
* @returns {Array}
|
|
477
|
+
*/
|
|
478
|
+
function expandRef(art, ref, alias, isKey, withAlias) {
|
|
479
|
+
const expanded = [];
|
|
480
|
+
const stack = [ [ art, ref, [ alias || ref[ref.length - 1] ] ] ];
|
|
481
|
+
while (stack.length > 0) {
|
|
482
|
+
const [ current, currentRef, currentAlias ] = stack.pop();
|
|
483
|
+
if (isStructured(current)) {
|
|
484
|
+
for (const [ n, e ] of Object.entries(current.elements || effectiveType(current).elements).reverse())
|
|
485
|
+
stack.push([ e, currentRef.concat(n), currentAlias.concat(n) ]);
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
const obj = { ref: currentRef };
|
|
489
|
+
if (withAlias) {
|
|
490
|
+
const newAlias = currentAlias.join(pathDelimiter);
|
|
491
|
+
// if (alias !== undefined) // explicit alias
|
|
492
|
+
obj.as = newAlias;
|
|
493
|
+
// alias was implicit - to later distinguish expanded s -> s.a from explicitly written s.a
|
|
494
|
+
if (alias === undefined)
|
|
495
|
+
setProp(obj, '$implicitAlias', true);
|
|
496
|
+
}
|
|
497
|
+
if (isKey)
|
|
498
|
+
obj.key = true;
|
|
499
|
+
expanded.push(obj);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return expanded;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Get the effective name produced by the object
|
|
508
|
+
*
|
|
509
|
+
* @param {object} part A thing with a ref/as/func
|
|
510
|
+
* @returns {string}
|
|
511
|
+
*/
|
|
512
|
+
function dbName(part) {
|
|
513
|
+
if (part.as)
|
|
514
|
+
return part.as;
|
|
515
|
+
else if (part.ref)
|
|
516
|
+
return implicitAs(part.ref);
|
|
517
|
+
else if (part.func)
|
|
518
|
+
return part.func;
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Replace the star and correctly put shadowed things in the right place.
|
|
524
|
+
*
|
|
525
|
+
* @param {Object} base The raw set of things a * can expand to
|
|
526
|
+
* @param {Array} subs Things - the .expand/.inline or .columns
|
|
527
|
+
* @param {string[]} [excluding=[]]
|
|
528
|
+
* @returns {Array} If there was a star, expand it and handle shadowing/excluding, else just return subs
|
|
529
|
+
*/
|
|
530
|
+
function replaceStar(base, subs, excluding = []) {
|
|
531
|
+
const stars = [];
|
|
532
|
+
const names = Object.create(null);
|
|
533
|
+
for (let i = 0; i < subs.length; i++) {
|
|
534
|
+
const sub = subs[i];
|
|
535
|
+
if (sub !== '*') {
|
|
536
|
+
const name = dbName(sub);
|
|
537
|
+
names[name] = i;
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
// There should only be one * - but be prepared for more than one
|
|
541
|
+
stars.push(i);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
// We have stars - replace/expand them
|
|
547
|
+
if (stars.length > 0) {
|
|
548
|
+
const replaced = Object.create(null);
|
|
549
|
+
const final = [];
|
|
550
|
+
const star = [];
|
|
551
|
+
// Build the result of a * - for later use
|
|
552
|
+
for (const part of Object.keys(base)) {
|
|
553
|
+
if (excluding.indexOf(part) === -1) {
|
|
554
|
+
// The thing is shadowed - ignore names present because of .inline, as those "disappear"
|
|
555
|
+
if (names[part] !== undefined && !subs[names[part]].inline) { // Only works for a single * - but a second is forbidden anyway
|
|
556
|
+
if (names[part] > stars[0]) { // explicit definitions BEFORE the star should stay "infront" of the star
|
|
557
|
+
replaced[part] = true;
|
|
558
|
+
star.push(subs[names[part]]);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
else { // the thing is not shadowed - use the name from the base
|
|
562
|
+
star.push({ ref: [ part ] });
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
// Finally: Replace the stars and leave out the shadowed things
|
|
567
|
+
for (let i = 0; i < subs.length; i++) {
|
|
568
|
+
const sub = subs[i];
|
|
569
|
+
if (sub !== '*' && !replaced[dbName(sub)])
|
|
570
|
+
final.push(sub);
|
|
571
|
+
else if (sub === '*')
|
|
572
|
+
final.push(...star);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return final;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return subs;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
module.exports = {
|
|
583
|
+
expandStructureReferences,
|
|
584
|
+
};
|