@sap/cds-compiler 2.5.0 → 2.10.4
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 +191 -9
- package/bin/cdsc.js +2 -2
- package/doc/CHANGELOG_BETA.md +33 -3
- package/lib/api/main.js +29 -101
- package/lib/api/options.js +15 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +63 -9
- package/lib/base/messages.js +63 -21
- package/lib/base/model.js +2 -3
- 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 +25 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +60 -7
- package/lib/compiler/assert-consistency.js +16 -7
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +99 -42
- package/lib/compiler/index.js +73 -27
- package/lib/compiler/resolver.js +288 -157
- package/lib/compiler/shared.js +31 -11
- package/lib/edm/annotations/genericTranslation.js +182 -186
- package/lib/edm/csn2edm.js +103 -108
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -114
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +22 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +12 -1
- package/lib/gen/language.tokens +57 -53
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +770 -744
- package/lib/gen/languageLexer.tokens +49 -46
- package/lib/gen/languageParser.js +4713 -4279
- package/lib/json/from-csn.js +103 -45
- package/lib/json/to-csn.js +296 -117
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +21 -12
- package/lib/language/language.g4 +99 -31
- package/lib/main.d.ts +81 -3
- package/lib/main.js +30 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +329 -142
- package/lib/model/csnUtils.js +235 -58
- package/lib/model/enrichCsn.js +18 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +37 -20
- package/lib/optionProcessor.js +9 -3
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +112 -33
- package/lib/render/toHdbcds.js +134 -64
- package/lib/render/toSql.js +91 -38
- 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 +29 -13
- package/lib/transform/db/draft.js +8 -6
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/transformExists.js +284 -63
- package/lib/transform/forHanaNew.js +98 -381
- package/lib/transform/forOdataNew.js +21 -22
- package/lib/transform/localized.js +37 -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 +134 -78
- package/lib/transform/translateAssocsToJoins.js +17 -14
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +0 -11
- package/lib/utils/moduleResolve.js +6 -8
- 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,325 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
forEachDefinition, getUtils,
|
|
5
|
+
applyTransformations, forAllElements, isBuiltinType,
|
|
6
|
+
} = require('../../model/csnUtils');
|
|
7
|
+
const transformUtils = require('../transformUtilsNew');
|
|
8
|
+
const { csnRefs } = require('../../model/csnRefs');
|
|
9
|
+
const { setProp } = require('../../base/model');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Strip off leading $self from refs where applicable
|
|
13
|
+
*
|
|
14
|
+
* @param {CSN.Model} csn
|
|
15
|
+
*/
|
|
16
|
+
function removeLeadingSelf(csn) {
|
|
17
|
+
const magicVars = [ '$now' ];
|
|
18
|
+
forEachDefinition(csn, (artifact, artifactName) => {
|
|
19
|
+
if (artifact.kind === 'entity' || artifact.kind === 'view') {
|
|
20
|
+
forAllElements(artifact, artifactName, (parent, elements) => {
|
|
21
|
+
for (const [ elementName, element ] of Object.entries(elements)) {
|
|
22
|
+
if (element.on) {
|
|
23
|
+
// applyTransformations expects the first thing to have a "definitions"
|
|
24
|
+
const fakeDefinitions = { definitions: {} };
|
|
25
|
+
fakeDefinitions.definitions[elementName] = element;
|
|
26
|
+
applyTransformations( fakeDefinitions, {
|
|
27
|
+
ref: (root, name, ref) => {
|
|
28
|
+
// Renderers seem to expect it to not be there...
|
|
29
|
+
if (ref[0] === '$self' && ref.length > 1 && !magicVars.includes(ref[1]))
|
|
30
|
+
root.ref = ref.slice(1);
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolve type references and turn things with `.items` into elements of type `LargeString`.
|
|
42
|
+
*
|
|
43
|
+
* Also, replace actions, events and functions with simply dummy artifacts.
|
|
44
|
+
*
|
|
45
|
+
* @param {CSN.Model} csn
|
|
46
|
+
* @param {CSN.Options} options
|
|
47
|
+
* @param {WeakMap} resolved Cache for resolved refs
|
|
48
|
+
* @param {string} pathDelimiter
|
|
49
|
+
*/
|
|
50
|
+
function resolveTypeReferences(csn, options, resolved, pathDelimiter) {
|
|
51
|
+
/**
|
|
52
|
+
* Remove .localized from the element and any sub-elements
|
|
53
|
+
*
|
|
54
|
+
* Only direct .localized usage should produce "localized things".
|
|
55
|
+
* If we don't remove it here, the second compile step adds localized stuff again.
|
|
56
|
+
*
|
|
57
|
+
* @param {object} obj
|
|
58
|
+
*/
|
|
59
|
+
function removeLocalized(obj) {
|
|
60
|
+
const stack = [ obj ];
|
|
61
|
+
while (stack.length > 0) {
|
|
62
|
+
const current = stack.pop();
|
|
63
|
+
if (current.localized)
|
|
64
|
+
delete current.localized;
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if (current.elements)
|
|
68
|
+
stack.push(...Object.values(current.elements));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const { toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
72
|
+
applyTransformations(csn, {
|
|
73
|
+
cast: (parent) => {
|
|
74
|
+
// Resolve cast already - we otherwise lose .localized
|
|
75
|
+
if (parent.cast.type && !isBuiltinType(parent.cast.type))
|
|
76
|
+
toFinalBaseType(parent.cast, resolved, true);
|
|
77
|
+
},
|
|
78
|
+
type: (parent, prop, type) => {
|
|
79
|
+
if (!isBuiltinType(type)) {
|
|
80
|
+
const directLocalized = parent.localized || false;
|
|
81
|
+
toFinalBaseType(parent, resolved);
|
|
82
|
+
if (!directLocalized)
|
|
83
|
+
removeLocalized(parent);
|
|
84
|
+
}
|
|
85
|
+
// HANA/SQLite do not support array-of - turn into CLOB/Text
|
|
86
|
+
if (parent.items) {
|
|
87
|
+
parent.type = 'cds.LargeString';
|
|
88
|
+
delete parent.items;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
// HANA/SQLite do not support array-of - turn into CLOB/Text
|
|
92
|
+
items: (parent) => {
|
|
93
|
+
parent.type = 'cds.LargeString';
|
|
94
|
+
delete parent.items;
|
|
95
|
+
},
|
|
96
|
+
}, [ (definitions, artifactName, artifact) => {
|
|
97
|
+
// Replace events, actions and functions with simple dummies - they don't have effect on forHanaNew stuff
|
|
98
|
+
// and that way they contain no references and don't hurt.
|
|
99
|
+
if (artifact.kind === 'action' || artifact.kind === 'function' || artifact.kind === 'event') {
|
|
100
|
+
const dummy = { kind: artifact.kind };
|
|
101
|
+
if (artifact.$location)
|
|
102
|
+
setProp(dummy, '$location', artifact.$location);
|
|
103
|
+
|
|
104
|
+
definitions[artifactName] = dummy;
|
|
105
|
+
}
|
|
106
|
+
} ], true, { skipDict: { actions: true } });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @param {CSN.Model} csn
|
|
111
|
+
* @param {CSN.Options} options
|
|
112
|
+
* @param {WeakMap} resolved Cache for resolved refs
|
|
113
|
+
* @param {string} pathDelimiter
|
|
114
|
+
*/
|
|
115
|
+
function flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter) {
|
|
116
|
+
const { inspectRef, effectiveType } = csnRefs(csn);
|
|
117
|
+
const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
118
|
+
const adaptRefs = [];
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* For each step of the links, check if there is a type reference.
|
|
122
|
+
* If there is, resolve it and store the result in a WeakMap.
|
|
123
|
+
*
|
|
124
|
+
* @param {Array} [links=[]]
|
|
125
|
+
* @todo seems too hacky
|
|
126
|
+
* @returns {WeakMap} A WeakMap where a link is the key and the type is the value
|
|
127
|
+
*/
|
|
128
|
+
function resolveLinkTypes(links = []) {
|
|
129
|
+
const resolvedLinkTypes = new WeakMap();
|
|
130
|
+
links.forEach((link) => {
|
|
131
|
+
const { art } = link;
|
|
132
|
+
if (art && art.type)
|
|
133
|
+
resolvedLinkTypes.set(link, effectiveType(art));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return resolvedLinkTypes;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
applyTransformations(csn, {
|
|
140
|
+
ref: (parent, prop, ref, path) => {
|
|
141
|
+
const { links, art, scope } = inspectRef(path);
|
|
142
|
+
const resolvedLinkTypes = resolveLinkTypes(links);
|
|
143
|
+
setProp(parent, '$path', [ ...path ]);
|
|
144
|
+
const lastRef = ref[ref.length - 1];
|
|
145
|
+
const fn = () => {
|
|
146
|
+
const scopedPath = [ ...parent.$path ];
|
|
147
|
+
|
|
148
|
+
parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes );
|
|
149
|
+
resolved.set(parent, { links, art, scope });
|
|
150
|
+
// Explicitly set implicit alias for things that are now flattened - but only in columns
|
|
151
|
+
// TODO: Can this be done elegantly during expand phase already?
|
|
152
|
+
if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
|
|
153
|
+
if (parent.ref[parent.ref.length - 1] === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
|
|
154
|
+
delete parent.as;
|
|
155
|
+
delete parent.$implicitAlias;
|
|
156
|
+
}
|
|
157
|
+
// To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
|
|
158
|
+
else if (parent.ref[parent.ref.length - 1] !== lastRef && (insideColumns(scopedPath) || insideKeys(scopedPath)) && !parent.as) {
|
|
159
|
+
parent.as = lastRef;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
// adapt queries later
|
|
163
|
+
adaptRefs.push(fn);
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
adaptRefs.forEach(fn => fn());
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Return true if the path points inside columns
|
|
171
|
+
*
|
|
172
|
+
* @param {CSN.Path} path
|
|
173
|
+
* @returns {boolean}
|
|
174
|
+
*/
|
|
175
|
+
function insideColumns(path) {
|
|
176
|
+
return path.length >= 3 && (path[path.length - 3] === 'SELECT' || path[path.length - 3] === 'projection') && path[path.length - 2] === 'columns';
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Return true if the path points inside keys
|
|
180
|
+
*
|
|
181
|
+
* @param {CSN.Path} path
|
|
182
|
+
* @returns {boolean}
|
|
183
|
+
*/
|
|
184
|
+
function insideKeys(path) {
|
|
185
|
+
return path.length >= 3 && path[path.length - 2] === 'keys' && typeof path[path.length - 1] === 'number';
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @param {CSN.Model} csn
|
|
191
|
+
* @param {CSN.Options} options
|
|
192
|
+
* @param {string} pathDelimiter
|
|
193
|
+
* @param {Function} error
|
|
194
|
+
*/
|
|
195
|
+
function flattenElements(csn, options, pathDelimiter, error) {
|
|
196
|
+
const { isAssocOrComposition } = getUtils(csn);
|
|
197
|
+
const { flattenStructuredElement } = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
198
|
+
const { effectiveType } = csnRefs(csn);
|
|
199
|
+
forEachDefinition(csn, flattenStructuredElements);
|
|
200
|
+
/**
|
|
201
|
+
* Flatten structures
|
|
202
|
+
*
|
|
203
|
+
* @param {CSN.Artifact} art Artifact
|
|
204
|
+
* @param {string} artName Artifact Name
|
|
205
|
+
*/
|
|
206
|
+
function flattenStructuredElements(art, artName) {
|
|
207
|
+
forAllElements(art, artName, (parent, elements, pathToElements) => {
|
|
208
|
+
const elementsArray = [];
|
|
209
|
+
for (const elemName in elements) {
|
|
210
|
+
const pathToElement = pathToElements.concat([ elemName ]);
|
|
211
|
+
const elem = parent.elements[elemName];
|
|
212
|
+
elementsArray.push([ elemName, elem ]);
|
|
213
|
+
if (elem.elements) {
|
|
214
|
+
elementsArray.pop();
|
|
215
|
+
// Ignore the structured element, replace it by its flattened form
|
|
216
|
+
// TODO: use $ignore - _ is for links
|
|
217
|
+
elem._ignore = true;
|
|
218
|
+
|
|
219
|
+
const branches = getBranches(elem, elemName);
|
|
220
|
+
const flatElems = flattenStructuredElement(elem, elemName, [], pathToElement);
|
|
221
|
+
|
|
222
|
+
for (const flatElemName in flatElems) {
|
|
223
|
+
if (parent.elements[flatElemName])
|
|
224
|
+
error(null, pathToElement, `"${artName}.${elemName}": Flattened struct element name conflicts with existing element: "${flatElemName}"`);
|
|
225
|
+
|
|
226
|
+
const flatElement = flatElems[flatElemName];
|
|
227
|
+
|
|
228
|
+
// Check if we have a valid notNull chain
|
|
229
|
+
const branch = branches[flatElemName];
|
|
230
|
+
if (flatElement.notNull !== false && !branch.some(s => !s.notNull))
|
|
231
|
+
flatElement.notNull = true;
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
if (flatElement.type && isAssocOrComposition(flatElement.type) && flatElement.on) {
|
|
235
|
+
// Make refs resolvable by fixing the first ref step
|
|
236
|
+
for (let i = 0; i < flatElement.on.length; i++) {
|
|
237
|
+
const onPart = flatElement.on[i];
|
|
238
|
+
if (onPart.ref) {
|
|
239
|
+
const firstRef = flatElement.on[i].ref[0];
|
|
240
|
+
|
|
241
|
+
/*
|
|
242
|
+
when element is defined in the current name resolution scope, like
|
|
243
|
+
entity E {
|
|
244
|
+
key x: Integer;
|
|
245
|
+
s : {
|
|
246
|
+
y : Integer;
|
|
247
|
+
a3 : association to E on a3.x = y;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
|
|
251
|
+
*/
|
|
252
|
+
const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, -1).join(pathDelimiter);
|
|
253
|
+
const possibleFlatName = prefix + pathDelimiter + firstRef;
|
|
254
|
+
|
|
255
|
+
if (flatElems[possibleFlatName])
|
|
256
|
+
flatElement.on[i].ref[0] = possibleFlatName;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
elementsArray.push([ flatElemName, flatElement ]);
|
|
261
|
+
// Still add them - otherwise we might not detect collisions between generated elements.
|
|
262
|
+
parent.elements[flatElemName] = flatElement;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Don't fake consistency of the model by adding empty elements {}
|
|
267
|
+
if (elementsArray.length === 0)
|
|
268
|
+
return;
|
|
269
|
+
|
|
270
|
+
parent.elements = elementsArray.reduce((previous, [ name, element ]) => {
|
|
271
|
+
previous[name] = element;
|
|
272
|
+
return previous;
|
|
273
|
+
}, Object.create(null));
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get not just the leafs, but all the branches of a structured element
|
|
279
|
+
*
|
|
280
|
+
* @param {object} element Structured element
|
|
281
|
+
* @param {string} elementName Name of the structured element
|
|
282
|
+
* @returns {object} Returns a dictionary, where the key is the flat name of the branch and the value is an array of element-steps.
|
|
283
|
+
*/
|
|
284
|
+
function getBranches(element, elementName) {
|
|
285
|
+
const branches = {};
|
|
286
|
+
const subbranchNames = [];
|
|
287
|
+
const subbranchElements = [];
|
|
288
|
+
walkElements(element, elementName);
|
|
289
|
+
/**
|
|
290
|
+
* Walk the element chain
|
|
291
|
+
*
|
|
292
|
+
* @param {CSN.Element} e
|
|
293
|
+
* @param {string} name
|
|
294
|
+
*/
|
|
295
|
+
function walkElements(e, name) {
|
|
296
|
+
if (isBuiltinType(e)) {
|
|
297
|
+
branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
const eType = effectiveType(e);
|
|
301
|
+
const subelements = e.elements || eType.elements;
|
|
302
|
+
if (subelements) {
|
|
303
|
+
subbranchElements.push(e);
|
|
304
|
+
subbranchNames.push(name);
|
|
305
|
+
for (const [ subelementName, subelement ] of Object.entries(subelements))
|
|
306
|
+
walkElements(subelement, subelementName);
|
|
307
|
+
|
|
308
|
+
subbranchNames.pop();
|
|
309
|
+
subbranchElements.pop();
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return branches;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
module.exports = {
|
|
321
|
+
resolveTypeReferences,
|
|
322
|
+
flattenAllStructStepsInRefs,
|
|
323
|
+
flattenElements,
|
|
324
|
+
removeLeadingSelf,
|
|
325
|
+
};
|
|
@@ -24,7 +24,7 @@ function replaceAssociationsInGroupByOrderBy(inputQuery, options, inspectRef, er
|
|
|
24
24
|
if (art.keys) {
|
|
25
25
|
// This is (or used to be before transformation) a managed assoc
|
|
26
26
|
// (230 c) If we keep associations as they are (hdbcds naming convention), we can't have associations in GROUP BY
|
|
27
|
-
if (options.
|
|
27
|
+
if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
|
|
28
28
|
error(null, groupByPath,
|
|
29
29
|
{ $reviewed: true },
|
|
30
30
|
'Unexpected managed association in GROUP BY for naming mode “hdbcds“');
|
|
@@ -57,7 +57,7 @@ function replaceAssociationsInGroupByOrderBy(inputQuery, options, inspectRef, er
|
|
|
57
57
|
if (art.keys) {
|
|
58
58
|
// This is (or used to be before transformation) a managed assoc
|
|
59
59
|
// (230 d) If we keep associations as they are (hdbcds naming convention), we can't have associations in ORDER BY
|
|
60
|
-
if (options.
|
|
60
|
+
if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
|
|
61
61
|
error(null, orderByPath,
|
|
62
62
|
{ $reviewed: true },
|
|
63
63
|
'Unexpected managed association in ORDER BY for naming mode “hdbcds”');
|