@sap/cds-compiler 2.4.4 → 2.10.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 +241 -1
- package/bin/.eslintrc.json +17 -0
- package/bin/cds_update_identifiers.js +8 -7
- package/bin/cdsc.js +180 -132
- package/bin/cdshi.js +18 -11
- package/bin/cdsse.js +38 -32
- package/bin/cdsv2m.js +8 -7
- package/doc/CHANGELOG_BETA.md +36 -1
- package/lib/api/main.js +81 -100
- package/lib/api/options.js +17 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/location.js +2 -2
- package/lib/base/message-registry.js +66 -4
- package/lib/base/messages.js +84 -27
- package/lib/base/model.js +2 -61
- package/lib/checks/arrayOfs.js +0 -1
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/enricher.js +8 -2
- 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 +27 -9
- 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 +66 -13
- package/lib/compiler/assert-consistency.js +24 -12
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +101 -39
- package/lib/compiler/index.js +88 -59
- package/lib/compiler/resolver.js +455 -209
- package/lib/compiler/shared.js +57 -33
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +128 -99
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -127
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +74 -28
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +18 -4
- package/lib/gen/language.tokens +124 -118
- package/lib/gen/languageLexer.interp +13 -1
- package/lib/gen/languageLexer.js +870 -839
- package/lib/gen/languageLexer.tokens +116 -111
- package/lib/gen/languageParser.js +5894 -5614
- package/lib/json/from-csn.js +152 -67
- package/lib/json/to-csn.js +334 -135
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +24 -14
- package/lib/language/language.g4 +188 -128
- package/lib/main.d.ts +435 -0
- package/lib/main.js +31 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +463 -187
- package/lib/model/csnUtils.js +280 -136
- package/lib/model/enrichCsn.js +75 -4
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +70 -25
- package/lib/optionProcessor.js +13 -10
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +123 -40
- package/lib/render/toHdbcds.js +156 -65
- package/lib/render/toSql.js +87 -11
- package/lib/render/utils/common.js +55 -9
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/{sql → db}/.eslintrc.json +0 -0
- package/lib/transform/{sql → db}/assertUnique.js +7 -8
- package/lib/transform/{sql → db}/constraints.js +35 -20
- package/lib/transform/db/draft.js +353 -0
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
- package/lib/transform/{sql → db}/helpers.js +0 -0
- package/lib/transform/{sql → db}/transformExists.js +256 -60
- package/lib/transform/forHanaNew.js +216 -765
- package/lib/transform/forOdataNew.js +60 -56
- package/lib/transform/localized.js +48 -26
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
- package/lib/transform/odata/generateForeignKeyElements.js +13 -12
- package/lib/transform/odata/referenceFlattener.js +60 -36
- package/lib/transform/odata/sortByAssociationDependency.js +4 -4
- package/lib/transform/odata/structuralPath.js +76 -0
- package/lib/transform/odata/structureFlattener.js +21 -22
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +27 -17
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +141 -77
- 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/lib/utils/timetrace.js +6 -1
- package/package.json +2 -1
- package/lib/base/deepCopy.js +0 -66
- package/lib/json/walker.js +0 -26
- package/lib/utils/string.js +0 -17
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { forAllQueries, forEachDefinition } = require('../../model/csnUtils');
|
|
3
|
+
const { forAllQueries, forEachDefinition, walkCsnPath } = require('../../model/csnUtils');
|
|
4
4
|
const { setProp } = require('../../base/model');
|
|
5
5
|
const { getRealName } = require('../../render/utils/common');
|
|
6
|
+
const { csnRefs } = require('../../model/csnRefs');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Turn a `exists assoc[filter = 100]` into a `exists (select 1 as dummy from assoc.target where <assoc on condition> and assoc.target.filter = 100)`.
|
|
@@ -43,23 +44,40 @@ const { getRealName } = require('../../render/utils/common');
|
|
|
43
44
|
* The final subselect looks like (select 1 as dummy from E where F.backToE.id = E.id and filter = 100).
|
|
44
45
|
*
|
|
45
46
|
* @param {CSN.Model} csn
|
|
47
|
+
* @param {CSN.Options} options
|
|
46
48
|
* @param {Function} error
|
|
47
49
|
*/
|
|
48
|
-
function handleExists(csn, error) {
|
|
50
|
+
function handleExists(csn, options, error) {
|
|
51
|
+
const { inspectRef } = csnRefs(csn);
|
|
49
52
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
50
53
|
if (artifact.query) {
|
|
51
|
-
forAllQueries(artifact.query, (query) => {
|
|
54
|
+
forAllQueries(artifact.query, (query, path) => {
|
|
52
55
|
if (!query.$generatedExists) {
|
|
56
|
+
const toProcess = []; // Collect all expressions we need to process here
|
|
53
57
|
if (query.SELECT && query.SELECT.where && query.SELECT.where.length > 1)
|
|
54
|
-
|
|
58
|
+
toProcess.push([ path.slice(0, -1), path.concat('where') ]);
|
|
55
59
|
|
|
56
60
|
|
|
57
61
|
if (query.SELECT && query.SELECT.columns)
|
|
58
|
-
|
|
62
|
+
toProcess.push([ path.slice(0, -1), path.concat('columns') ]);
|
|
59
63
|
|
|
60
64
|
|
|
61
65
|
if (query.SELECT && query.SELECT.from.on )
|
|
62
|
-
|
|
66
|
+
toProcess.push([ path.slice(0, -1), path.concat([ 'from', 'on' ]) ]);
|
|
67
|
+
|
|
68
|
+
for (const [ , exprPath ] of toProcess) {
|
|
69
|
+
const expr = nestExists(exprPath);
|
|
70
|
+
walkCsnPath(csn, exprPath.slice(0, -1))[exprPath[exprPath.length - 1]] = expr;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
while (toProcess.length > 0) {
|
|
74
|
+
const [ queryPath, exprPath ] = toProcess.pop();
|
|
75
|
+
// leftovers can happen with nested exists - we then need to drill down into the created SELECT
|
|
76
|
+
// to check for further exists
|
|
77
|
+
const { result, leftovers } = processExists(queryPath, exprPath);
|
|
78
|
+
walkCsnPath(csn, exprPath.slice(0, -1))[exprPath[exprPath.length - 1]] = result;
|
|
79
|
+
toProcess.push(...leftovers.reverse()); // any leftovers - schedule for further processing
|
|
80
|
+
}
|
|
63
81
|
}
|
|
64
82
|
}, [ 'definitions', artifactName, 'query' ]);
|
|
65
83
|
}
|
|
@@ -108,14 +126,149 @@ function handleExists(csn, error) {
|
|
|
108
126
|
}
|
|
109
127
|
|
|
110
128
|
/**
|
|
111
|
-
*
|
|
129
|
+
* Get the index of the first association that is found - starting the
|
|
130
|
+
* search at the given startIndex.
|
|
112
131
|
*
|
|
113
|
-
* @param {
|
|
114
|
-
* @param {
|
|
115
|
-
* @returns {
|
|
132
|
+
* @param {number} startIndex Where to start searching
|
|
133
|
+
* @param {object[]} links links for a ref, produced by inspectRef
|
|
134
|
+
* @returns {number|null} Null if no association was found
|
|
135
|
+
*/
|
|
136
|
+
function getFirstAssocIndex(startIndex, links) {
|
|
137
|
+
for (let i = startIndex; i < links.length; i++) {
|
|
138
|
+
if (links[i] && links[i].art && links[i].art.target)
|
|
139
|
+
return i;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* For a given ref-array, this function is called for the first assoc-ref in the array.
|
|
147
|
+
*
|
|
148
|
+
* It then runs over the rest of the array and puts all other steps in the first assocs filter.
|
|
149
|
+
* If the rest contains another assoc, we put all following things into that assocs filter and
|
|
150
|
+
* add the sub-assoc to the previous assoc filter.
|
|
151
|
+
*
|
|
152
|
+
* Or in other words:
|
|
153
|
+
* - exists toF[1=1].toG[1=1].toH[1=1] is found
|
|
154
|
+
* - we get called with toF[1=1].toG[1=1].toH[1=1]
|
|
155
|
+
* - we return toF[1=1 and exists toG[1=1 and exists toH[1=1]]]
|
|
156
|
+
*
|
|
157
|
+
* @param {number} startIndex The index of the thing AFTER _main in the ref-array
|
|
158
|
+
* @param {string|object} startAssoc The path step that is the first assoc
|
|
159
|
+
* @param {Array} startRest Any path steps after startAssoc
|
|
160
|
+
* @param {CSN.Path} path to the overall ref where _main is contained
|
|
161
|
+
* @returns {Array} Return the now-nested ref-array
|
|
116
162
|
*/
|
|
117
|
-
function
|
|
163
|
+
function nestFilters(startIndex, startAssoc, startRest, path) {
|
|
164
|
+
let revert;
|
|
165
|
+
if (!startAssoc.where) { // initialize first filter if not present
|
|
166
|
+
if (typeof startAssoc === 'string') {
|
|
167
|
+
startAssoc = {
|
|
168
|
+
id: startAssoc,
|
|
169
|
+
where: [],
|
|
170
|
+
};
|
|
171
|
+
revert = () => {
|
|
172
|
+
startAssoc = startAssoc.id;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
startAssoc.where = [];
|
|
177
|
+
revert = () => {
|
|
178
|
+
delete startAssoc.where;
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const stack = [ [ null, startAssoc, startRest, startIndex ] ];
|
|
183
|
+
const { links } = inspectRef(path);
|
|
184
|
+
while (stack.length > 0) {
|
|
185
|
+
// previous: to nest "up" if the previous assoc did not originaly have a filter
|
|
186
|
+
// assoc: the assoc path step
|
|
187
|
+
// rest: path steps after assoc
|
|
188
|
+
// index: index of after-assoc in the overall ref-array - so we know where to start looking for the next assoc
|
|
189
|
+
const workPackage = stack.pop();
|
|
190
|
+
const [ previous, , rest, index ] = workPackage;
|
|
191
|
+
let [ , assoc, , ] = workPackage;
|
|
192
|
+
|
|
193
|
+
const firstAssocIndex = getFirstAssocIndex(index, links);
|
|
194
|
+
|
|
195
|
+
const head = rest.slice(0, firstAssocIndex - index);
|
|
196
|
+
const nextAssoc = rest[firstAssocIndex - index];
|
|
197
|
+
const tail = rest.slice(firstAssocIndex - index + 1);
|
|
198
|
+
|
|
199
|
+
const hasAssoc = nextAssoc !== undefined;
|
|
200
|
+
|
|
201
|
+
if (!assoc.where && hasAssoc) { // no existing filter - and there is stuff we need to nest afterwards
|
|
202
|
+
if (typeof assoc === 'string') {
|
|
203
|
+
assoc = {
|
|
204
|
+
id: assoc,
|
|
205
|
+
where: [],
|
|
206
|
+
};
|
|
207
|
+
// We need to "hook" this into the previous filter.
|
|
208
|
+
// Since we create a new object, we don't have a handy reference we can just manipulate
|
|
209
|
+
if (previous)
|
|
210
|
+
previous.where[previous.where.length - 1] = { ref: [ assoc ] };
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
assoc.where = [];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else if (assoc.where && assoc.where.length > 0 && (hasAssoc || rest.length > 0)) {
|
|
217
|
+
assoc.where.push('and');
|
|
218
|
+
} // merge with existing filter
|
|
219
|
+
|
|
220
|
+
if (hasAssoc)
|
|
221
|
+
assoc.where.push('exists', { ref: [ ...head, nextAssoc ] });
|
|
222
|
+
else if (rest.length > 0)
|
|
223
|
+
assoc.where.push({ ref: rest });
|
|
224
|
+
|
|
225
|
+
if (hasAssoc)
|
|
226
|
+
stack.push([ assoc, nextAssoc, tail, firstAssocIndex ]);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Seems like we did not have anything to nest into the filter - then kill it
|
|
230
|
+
if (startAssoc.where.length === 0 && revert !== undefined)
|
|
231
|
+
revert();
|
|
232
|
+
|
|
233
|
+
return startAssoc;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Walk to the expr using the given path and scan it for the "exists" + "ref" pattern.
|
|
238
|
+
* If such a pattern is found, nest association steps therein into filters.
|
|
239
|
+
*
|
|
240
|
+
* @param {CSN.Path} exprPath
|
|
241
|
+
* @returns {Array}
|
|
242
|
+
*/
|
|
243
|
+
function nestExists(exprPath) {
|
|
244
|
+
const expr = walkCsnPath(csn, exprPath);
|
|
245
|
+
for (let i = 0; i < expr.length; i++) {
|
|
246
|
+
if (i < expr.length - 1 && expr[i] === 'exists' && expr[i + 1].ref) {
|
|
247
|
+
i++;
|
|
248
|
+
const current = expr[i];
|
|
249
|
+
const {
|
|
250
|
+
ref, head, tail,
|
|
251
|
+
} = getFirstAssoc(current, exprPath.concat(i));
|
|
252
|
+
const newThing = [ ...head, nestFilters(head.length + 1, ref, tail, exprPath.concat([ i ])) ];
|
|
253
|
+
expr[i].ref = newThing;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return expr;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Process the given expr of the given query and translate a `EXISTS assoc` into a `EXISTS (subquery)`. Also, return paths to things we need to process in a second step.
|
|
262
|
+
*
|
|
263
|
+
* @param {CSN.Path} queryPath Path to the query-object
|
|
264
|
+
* @param {CSN.Path} exprPath Path to the expression-array to process
|
|
265
|
+
* @returns {{result: TokenStream, leftovers: Array[]}} result: A new token stream expression - the same as expr, but with the expanded EXISTS, leftovers: path-tuples to further subqueries to process.
|
|
266
|
+
*/
|
|
267
|
+
function processExists(queryPath, exprPath) {
|
|
268
|
+
const toContinue = [];
|
|
118
269
|
const newExpr = [];
|
|
270
|
+
const query = walkCsnPath(csn, queryPath);
|
|
271
|
+
const expr = walkCsnPath(csn, exprPath);
|
|
119
272
|
const queryBase = query.SELECT.from.ref ? (query.SELECT.from.as || query.SELECT.from.ref[0]) : null;
|
|
120
273
|
const sources = getQuerySources(query.SELECT);
|
|
121
274
|
|
|
@@ -123,17 +276,19 @@ function handleExists(csn, error) {
|
|
|
123
276
|
if (i < expr.length - 1 && expr[i] === 'exists' && expr[i + 1].ref) {
|
|
124
277
|
i++;
|
|
125
278
|
const current = expr[i];
|
|
126
|
-
const isPrefixedWithTableAlias = firstLinkIsEntityOrQuerySource(
|
|
127
|
-
const base = getBase(queryBase, isPrefixedWithTableAlias, current);
|
|
128
|
-
const { root, ref, tail } = getFirstAssoc(current);
|
|
279
|
+
const isPrefixedWithTableAlias = firstLinkIsEntityOrQuerySource(exprPath.concat(i));
|
|
280
|
+
const base = getBase(queryBase, isPrefixedWithTableAlias, current, exprPath.concat(i));
|
|
281
|
+
const { root, ref, tail } = getFirstAssoc(current, exprPath.concat(i));
|
|
129
282
|
|
|
130
283
|
if (tail.length > 0) {
|
|
131
|
-
error(null, current.$path, { id: tail[0], name: ref.id ? ref.id : ref }, 'Unexpected path step $(ID) after association $(NAME) in "EXISTS"');
|
|
284
|
+
error(null, current.$path, { id: tail[0].id ? tail[0].id : tail[0], name: ref.id ? ref.id : ref }, 'Unexpected path step $(ID) after association $(NAME) in "EXISTS"');
|
|
132
285
|
continue;
|
|
133
286
|
}
|
|
134
287
|
|
|
135
|
-
if (!root.target)
|
|
136
|
-
|
|
288
|
+
if (!root.target) {
|
|
289
|
+
error(null, current.$path, { type: root.type }, '“EXISTS” can only be used with associations/compositions, found $(TYPE)');
|
|
290
|
+
return { result: [], leftovers: [] };
|
|
291
|
+
}
|
|
137
292
|
|
|
138
293
|
const subselect = getSubselect(root.target, ref, sources);
|
|
139
294
|
|
|
@@ -150,18 +305,24 @@ function handleExists(csn, error) {
|
|
|
150
305
|
subselect.SELECT.where.push(...[ 'and', ...remapExistingWhere(target, ref.where) ]);
|
|
151
306
|
|
|
152
307
|
newExpr.push(subselect);
|
|
308
|
+
toContinue.push([ exprPath.concat(newExpr.length - 1), exprPath.concat([ newExpr.length - 1, 'SELECT', 'where' ]) ]);
|
|
153
309
|
}
|
|
154
310
|
else { // Drill down into other places that might contain a `EXISTS <assoc>`
|
|
155
|
-
if (expr[i].xpr)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
311
|
+
if (expr[i].xpr) {
|
|
312
|
+
const { result, leftovers } = processExists(queryPath, exprPath.concat([ i, 'xpr' ]));
|
|
313
|
+
expr[i].xpr = result;
|
|
314
|
+
toContinue.push(...leftovers);
|
|
315
|
+
}
|
|
316
|
+
if (expr[i].args && Array.isArray(expr[i].args)) {
|
|
317
|
+
const { result, leftovers } = processExists(queryPath, exprPath.concat([ i, 'args' ]));
|
|
318
|
+
expr[i].args = result;
|
|
319
|
+
toContinue.push(...leftovers);
|
|
320
|
+
}
|
|
160
321
|
newExpr.push(expr[i]);
|
|
161
322
|
}
|
|
162
323
|
}
|
|
163
324
|
|
|
164
|
-
return newExpr;
|
|
325
|
+
return { result: newExpr, leftovers: toContinue };
|
|
165
326
|
}
|
|
166
327
|
|
|
167
328
|
/**
|
|
@@ -227,23 +388,35 @@ function handleExists(csn, error) {
|
|
|
227
388
|
function translateUnmanagedAssocToWhere(root, target, subselect, isPrefixedWithTableAlias, base, current) {
|
|
228
389
|
for (let j = 0; j < root.on.length; j++) {
|
|
229
390
|
const part = root.on[j];
|
|
391
|
+
|
|
392
|
+
// we can only resolve stuff on refs - skip literals like =
|
|
393
|
+
// but also keep along stuff like null and undefined, so compiler
|
|
394
|
+
// can have a chance to complain/ we can fail later nicely maybe
|
|
395
|
+
if (!(part && part.ref)) {
|
|
396
|
+
subselect.SELECT.where.push(part);
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// root.$path should be safe - we can only reference things in exists that exist when we enrich
|
|
401
|
+
// so all of them should have a $path.
|
|
402
|
+
const { art, links } = inspectRef(root.$path.concat([ 'on', j ]));
|
|
230
403
|
// Dollar Self Backlink
|
|
231
|
-
if (isValidDollarSelf(root.on[j], root.on[j + 1], root.on[j + 2])) {
|
|
404
|
+
if (isValidDollarSelf(root.on[j], root.$path.concat([ 'on', j ]), root.on[j + 1], root.on[j + 2], root.$path.concat([ 'on', j + 2 ]))) {
|
|
232
405
|
if (root.on[j].ref[0] === '$self' && root.on[j].ref.length === 1)
|
|
233
|
-
subselect.SELECT.where.push(...translateDollarSelfToWhere(base, target, root.on[j + 2]));
|
|
406
|
+
subselect.SELECT.where.push(...translateDollarSelfToWhere(base, target, root.on[j + 2], root.$path.concat([ 'on', j + 2 ])));
|
|
234
407
|
else
|
|
235
|
-
subselect.SELECT.where.push(...translateDollarSelfToWhere(base, target, root.on[j]));
|
|
408
|
+
subselect.SELECT.where.push(...translateDollarSelfToWhere(base, target, root.on[j], root.$path.concat([ 'on', j ])));
|
|
236
409
|
|
|
237
410
|
j += 2;
|
|
238
411
|
}
|
|
239
|
-
else if (
|
|
412
|
+
else if (links && links[0].art === root) { // target side
|
|
240
413
|
subselect.SELECT.where.push({ ref: [ target, ...part.ref.slice(1) ] });
|
|
241
414
|
}
|
|
242
415
|
else if (part.$scope === '$self') { // source side - "absolute" scope
|
|
243
416
|
// cut off the $self, as we prefix the entity name now
|
|
244
417
|
subselect.SELECT.where.push({ ref: [ base, ...part.ref.slice(1) ] });
|
|
245
418
|
}
|
|
246
|
-
else if (
|
|
419
|
+
else if (art) { // source side - with local scope
|
|
247
420
|
if (isPrefixedWithTableAlias)
|
|
248
421
|
subselect.SELECT.where.push({ ref: [ ...current.ref.slice(0, -1), ...part.ref ] });
|
|
249
422
|
else
|
|
@@ -258,43 +431,61 @@ function handleExists(csn, error) {
|
|
|
258
431
|
* Check that an expression triple is a valid $self
|
|
259
432
|
*
|
|
260
433
|
* @param {Token} leftSide
|
|
434
|
+
* @param {CSN.Path} pathLeft
|
|
261
435
|
* @param {Token} middle
|
|
262
436
|
* @param {Token} rightSide
|
|
437
|
+
* @param {CSN.Path} pathRight
|
|
263
438
|
* @returns {boolean}
|
|
264
439
|
*/
|
|
265
|
-
function isValidDollarSelf(leftSide, middle, rightSide) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
440
|
+
function isValidDollarSelf(leftSide, pathLeft, middle, rightSide, pathRight) {
|
|
441
|
+
if (leftSide && leftSide.ref && rightSide && rightSide.ref && middle === '=') {
|
|
442
|
+
const right = inspectRef(pathRight);
|
|
443
|
+
const left = inspectRef(pathLeft);
|
|
444
|
+
|
|
445
|
+
if (!right || !left)
|
|
446
|
+
return false;
|
|
447
|
+
|
|
448
|
+
const rightSideArt = right.art;
|
|
449
|
+
const leftSideArt = left.art;
|
|
450
|
+
|
|
451
|
+
return leftSide.ref[0] === '$self' && leftSide.ref.length === 1 && rightSideArt && rightSideArt.target ||
|
|
452
|
+
rightSide.ref[0] === '$self' && rightSide.ref.length === 1 && leftSideArt && leftSideArt.target;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return false;
|
|
273
456
|
}
|
|
274
457
|
}
|
|
275
458
|
|
|
276
459
|
/**
|
|
277
|
-
* From the given expression (having
|
|
460
|
+
* From the given expression (having inspectRef -> links), find the first association.
|
|
278
461
|
*
|
|
279
462
|
* @param {object} xprPart
|
|
280
|
-
* @
|
|
463
|
+
* @param {CSN.Path} path
|
|
464
|
+
* @returns {{head: Array, root: CSN.Element, ref: string|object, tail: Array}} The first assoc (root), the corresponding ref (ref), anything before the ref (head) and the rest of the ref (tail).
|
|
281
465
|
*/
|
|
282
|
-
function getFirstAssoc(xprPart) {
|
|
466
|
+
function getFirstAssoc(xprPart, path) {
|
|
467
|
+
const { links, art } = inspectRef(path);
|
|
283
468
|
for (let i = 0; i < xprPart.ref.length - 1; i++) {
|
|
284
|
-
if (
|
|
285
|
-
return {
|
|
469
|
+
if (links[i].art && links[i].art.target) {
|
|
470
|
+
return {
|
|
471
|
+
head: (i === 0 ? [] : xprPart.ref.slice(0, i)), root: links[i].art, ref: xprPart.ref[i], tail: xprPart.ref.slice(i + 1),
|
|
472
|
+
};
|
|
473
|
+
}
|
|
286
474
|
}
|
|
287
|
-
return {
|
|
475
|
+
return {
|
|
476
|
+
head: (xprPart.ref.length === 1 ? [] : xprPart.ref.slice(0, xprPart.ref.length - 1)), root: art, ref: xprPart.ref[xprPart.ref.length - 1], tail: [],
|
|
477
|
+
};
|
|
288
478
|
}
|
|
289
479
|
|
|
290
480
|
/**
|
|
291
|
-
* Check (using
|
|
481
|
+
* Check (using inspectRef -> links), wether the first path step is an entity or query source
|
|
292
482
|
*
|
|
293
|
-
* @param {
|
|
483
|
+
* @param {CSN.Path} path
|
|
294
484
|
* @returns {boolean}
|
|
295
485
|
*/
|
|
296
|
-
function firstLinkIsEntityOrQuerySource(
|
|
297
|
-
|
|
486
|
+
function firstLinkIsEntityOrQuerySource(path) {
|
|
487
|
+
const { links } = inspectRef(path);
|
|
488
|
+
return links && (links[0].art.kind === 'entity' || links[0].art.query || links[0].art.from);
|
|
298
489
|
}
|
|
299
490
|
|
|
300
491
|
/**
|
|
@@ -314,13 +505,14 @@ function handleExists(csn, error) {
|
|
|
314
505
|
* we can be sure that resolving the ref requires $env information.
|
|
315
506
|
*
|
|
316
507
|
* @param {object} xpr
|
|
508
|
+
* @param {CSN.Path} path
|
|
317
509
|
* @returns {string|undefined} undefined in case of errors
|
|
318
510
|
* @throws {Error} Throws if xpr.ref but no xpr.$env
|
|
319
511
|
* @todo $env is going to be removed from CSN, but csnRefs will provide it
|
|
320
512
|
*/
|
|
321
513
|
// eslint-disable-next-line consistent-return
|
|
322
|
-
function getParent(xpr) {
|
|
323
|
-
if (firstLinkIsEntityOrQuerySource(
|
|
514
|
+
function getParent(xpr, path) {
|
|
515
|
+
if (firstLinkIsEntityOrQuerySource(path)) {
|
|
324
516
|
return xpr.ref[0];
|
|
325
517
|
}
|
|
326
518
|
else if (xpr.$env) {
|
|
@@ -382,14 +574,15 @@ function handleExists(csn, error) {
|
|
|
382
574
|
* @param {string|null} queryBase
|
|
383
575
|
* @param {boolean} isPrefixedWithTableAlias
|
|
384
576
|
* @param {CSN.Column} current
|
|
577
|
+
* @param {CSN.Path} path
|
|
385
578
|
* @returns {string}
|
|
386
579
|
*/
|
|
387
|
-
function getBase(queryBase, isPrefixedWithTableAlias, current) {
|
|
580
|
+
function getBase(queryBase, isPrefixedWithTableAlias, current, path) {
|
|
388
581
|
if (queryBase)
|
|
389
582
|
return getRealName(csn, queryBase);
|
|
390
583
|
else if (isPrefixedWithTableAlias)
|
|
391
584
|
return current.ref[0];
|
|
392
|
-
return getParent(current);
|
|
585
|
+
return getParent(current, path);
|
|
393
586
|
}
|
|
394
587
|
|
|
395
588
|
|
|
@@ -421,31 +614,34 @@ function handleExists(csn, error) {
|
|
|
421
614
|
* @param {string} base The source entity/query source name
|
|
422
615
|
* @param {string} target The target entity/query source name
|
|
423
616
|
* @param {CSN.Element} assoc The association element - the "not-$self" side of the comparison
|
|
617
|
+
* @param {CSN.Path} path
|
|
424
618
|
* @returns {TokenStream} The WHERE representing the $self comparison
|
|
425
619
|
*/
|
|
426
|
-
function translateDollarSelfToWhere(base, target, assoc) {
|
|
620
|
+
function translateDollarSelfToWhere(base, target, assoc, path) {
|
|
427
621
|
const where = [];
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const
|
|
622
|
+
const { art } = inspectRef(path);
|
|
623
|
+
if (art.keys) {
|
|
624
|
+
for (let i = 0; i < art.keys.length; i++) {
|
|
625
|
+
const lop = { ref: [ target, ...assoc.ref.slice(1), ...art.keys[i].ref ] }; // target side
|
|
626
|
+
const rop = { ref: [ base, ...art.keys[i].ref ] }; // source side
|
|
432
627
|
if (i > 0)
|
|
433
628
|
where.push('and');
|
|
434
629
|
|
|
435
630
|
where.push(...[ lop, '=', rop ]);
|
|
436
631
|
}
|
|
437
632
|
}
|
|
438
|
-
else if (
|
|
439
|
-
for (let i = 0; i <
|
|
440
|
-
const part =
|
|
441
|
-
|
|
633
|
+
else if (art.on) {
|
|
634
|
+
for (let i = 0; i < art.on.length; i++) {
|
|
635
|
+
const part = art.on[i];
|
|
636
|
+
const partInspect = inspectRef(art.$path.concat([ 'on', i ]));
|
|
637
|
+
if (partInspect.links && partInspect.links[0].art === art) { // target side
|
|
442
638
|
where.push({ ref: [ base, ...part.ref.slice(1) ] });
|
|
443
639
|
}
|
|
444
640
|
else if (part.$scope === '$self') { // source side - "absolute" scope
|
|
445
641
|
// Same message as in forHanaNew/transformDollarSelfComparisonWithUnmanagedAssoc
|
|
446
642
|
error(null, part.$path, 'An association that uses "$self" in its ON-condition can\'t be compared to "$self"');
|
|
447
643
|
}
|
|
448
|
-
else if (
|
|
644
|
+
else if (partInspect.art) { // source side - with local scope
|
|
449
645
|
where.push({ ref: [ target, ...assoc.ref.slice(1, -1), ...part.ref ] });
|
|
450
646
|
}
|
|
451
647
|
else { // operator - or any other leftover
|