@sap/cds-compiler 2.11.2 → 2.13.6
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 +175 -2
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +23 -17
- package/bin/cdsse.js +2 -2
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +32 -79
- package/lib/api/options.js +3 -2
- package/lib/api/validate.js +2 -1
- package/lib/backends.js +16 -26
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +10 -19
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +75 -9
- package/lib/base/messages.js +31 -35
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/unknownMagic.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +12 -8
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +42 -21
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +46 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +100 -65
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +215 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +189 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +144 -53
- package/lib/language/language.g4 +424 -200
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +550 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +322 -198
- package/lib/model/csnUtils.js +226 -370
- package/lib/model/enrichCsn.js +124 -69
- package/lib/model/revealInternalProperties.js +29 -7
- package/lib/model/sortViews.js +10 -2
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +8 -3
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +203 -122
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +161 -82
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +212 -0
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +556 -106
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +103 -28
- package/lib/transform/db/views.js +92 -44
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +98 -783
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +13 -30
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +8 -3
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2349
- package/lib/compiler/resolver.js +0 -2922
- package/lib/transform/db/helpers.js +0 -58
- package/lib/transform/universalCsnEnricher.js +0 -67
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { ModelError } = require('../../base/error');
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Replace (formerly) managed association in a GROUP BY/ORDER BY with its foreign keys.
|
|
5
7
|
*
|
|
@@ -89,7 +91,7 @@ function replaceAssociationsInGroupByOrderBy(inputQuery, options, inspectRef, er
|
|
|
89
91
|
function getForeignKeyRefs(assoc) {
|
|
90
92
|
return assoc.keys.map((fk) => {
|
|
91
93
|
if (!fk.$generatedFieldName)
|
|
92
|
-
throw new
|
|
94
|
+
throw new ModelError(`Expecting generated field name for foreign key: ${JSON.stringify(fk)}`);
|
|
93
95
|
|
|
94
96
|
return { ref: [ fk.$generatedFieldName ] };
|
|
95
97
|
});
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
getUtils, getNormalizedQuery, hasAnnotationValue, forEachMember,
|
|
5
|
+
} = require('../../model/csnUtils');
|
|
6
|
+
const { implicitAs } = require('../../model/csnRefs');
|
|
7
|
+
const { setProp } = require('../../base/model');
|
|
8
|
+
const { getTransformers } = require('../transformUtilsNew');
|
|
9
|
+
|
|
10
|
+
const validToString = '@cds.valid.to';
|
|
11
|
+
const validFromString = '@cds.valid.from';
|
|
12
|
+
/**
|
|
13
|
+
* Get the forEachDefinition callback function that adds a where condition to views that
|
|
14
|
+
* - are annotated with @cds.valid.from and @cds.valid.to,
|
|
15
|
+
* - have only one @cds.valid.from and @cds.valid.to,
|
|
16
|
+
* - and both annotations come from the same entity
|
|
17
|
+
*
|
|
18
|
+
* If the view has one of the annotations but the other conditions are not met, an error will be raised.
|
|
19
|
+
*
|
|
20
|
+
* @param {CSN.Model} csn
|
|
21
|
+
* @param {object} messageFunctions
|
|
22
|
+
* @param {Function} messageFunctions.info
|
|
23
|
+
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition applying the where-condition to views.
|
|
24
|
+
*/
|
|
25
|
+
function getViewDecorator(csn, messageFunctions) {
|
|
26
|
+
const { info } = messageFunctions;
|
|
27
|
+
const { get$combined } = getUtils(csn);
|
|
28
|
+
return addTemporalWhereConditionToView;
|
|
29
|
+
/**
|
|
30
|
+
* Add a where condition to views that
|
|
31
|
+
* - are annotated with @cds.valid.from and @cds.valid.to,
|
|
32
|
+
* - have only one @cds.valid.from and @cds.valid.to,
|
|
33
|
+
* - and both annotations come from the same entity
|
|
34
|
+
*
|
|
35
|
+
* If the view has one of the annotations but the other conditions are not met, an error will be raised.
|
|
36
|
+
*
|
|
37
|
+
* @param {CSN.Artifact} artifact
|
|
38
|
+
* @param {string} artifactName
|
|
39
|
+
*/
|
|
40
|
+
function addTemporalWhereConditionToView(artifact, artifactName) {
|
|
41
|
+
const normalizedQuery = getNormalizedQuery(artifact);
|
|
42
|
+
if (normalizedQuery && normalizedQuery.query && normalizedQuery.query.SELECT) {
|
|
43
|
+
// BLOCKER: We need information to handle $combined
|
|
44
|
+
// What we are trying to achieve by this:
|
|
45
|
+
// Forbid joining/selecting from two or more temporal entities
|
|
46
|
+
// Idea: Follow the query-tree and check each from
|
|
47
|
+
// Collect all source-entities and compute our own $combined
|
|
48
|
+
const $combined = get$combined(normalizedQuery.query);
|
|
49
|
+
const [ from, to ] = getFromToElements($combined);
|
|
50
|
+
// exactly one validFrom & validTo
|
|
51
|
+
if (from.length === 1 && to.length === 1) {
|
|
52
|
+
// and both are from the same origin
|
|
53
|
+
if (from[0].source === to[0].source && from[0].parent === to[0].parent) {
|
|
54
|
+
if (!hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0])) {
|
|
55
|
+
const fromPath = {
|
|
56
|
+
ref: [
|
|
57
|
+
from[0].parent,
|
|
58
|
+
from[0].name,
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const toPath = {
|
|
63
|
+
ref: [
|
|
64
|
+
to[0].parent,
|
|
65
|
+
to[0].name,
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
const atFrom = { ref: [ '$at', 'from' ] };
|
|
71
|
+
const atTo = { ref: [ '$at', 'to' ] };
|
|
72
|
+
|
|
73
|
+
const cond = [ '(', fromPath, '<', atTo, 'and', toPath, '>', atFrom, ')' ];
|
|
74
|
+
|
|
75
|
+
if (normalizedQuery.query.SELECT.where) { // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
|
|
76
|
+
normalizedQuery.query.SELECT.where = [ '(', ...normalizedQuery.query.SELECT.where, ')', 'and', ...cond ];
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
normalizedQuery.query.SELECT.where = cond;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
info(null, [ 'definitions', artifactName ], `No temporal WHERE clause added as "${from[0].error_parent}"."${from[0].name}" and "${to[0].error_parent}"."${to[0].name}" are not of same origin`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (from.length > 0 || to.length > 0) {
|
|
88
|
+
const missingAnnotation = from.length > to.length ? validToString : validFromString;
|
|
89
|
+
info(null, [ 'definitions', artifactName ],
|
|
90
|
+
{ anno: missingAnnotation },
|
|
91
|
+
'No temporal WHERE clause added because $(ANNO) is missing');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get all elements tagged with @cds.valid.from/to from the union of all entities of the from-clause.
|
|
98
|
+
*
|
|
99
|
+
* @param {any} combined union of all entities of the from-clause
|
|
100
|
+
* @returns {Array[]} Array where first field is array of elements with @cds.valid.from, second field is array of elements with @cds.valid.to.
|
|
101
|
+
*/
|
|
102
|
+
function getFromToElements(combined) {
|
|
103
|
+
const from = [];
|
|
104
|
+
const to = [];
|
|
105
|
+
for (const name in combined) {
|
|
106
|
+
let elt = combined[name];
|
|
107
|
+
if (!Array.isArray(elt))
|
|
108
|
+
elt = [ elt ];
|
|
109
|
+
elt.forEach((e) => {
|
|
110
|
+
if (hasAnnotationValue(e.element, validFromString))
|
|
111
|
+
from.push(e);
|
|
112
|
+
|
|
113
|
+
if (hasAnnotationValue(e.element, validToString))
|
|
114
|
+
to.push(e);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return [ from, to ];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check if the given SELECT has a falsy @cds.valid.from and a falsy @cds.valid.to
|
|
123
|
+
*
|
|
124
|
+
* @param {CSN.QuerySelect} SELECT
|
|
125
|
+
* @param {CSN.Elements} elements
|
|
126
|
+
* @param {object} from
|
|
127
|
+
* @param {object} to
|
|
128
|
+
* @returns {boolean} True if both are present and false.
|
|
129
|
+
*/
|
|
130
|
+
function hasFalsyTemporalAnnotations(SELECT, elements, from, to) {
|
|
131
|
+
let fromElement = elements[from.name];
|
|
132
|
+
let toElement = elements[to.name];
|
|
133
|
+
|
|
134
|
+
if (SELECT.columns) {
|
|
135
|
+
for (const col of SELECT.columns) {
|
|
136
|
+
if (col.ref) {
|
|
137
|
+
const implicitAlias = implicitAs(col.ref);
|
|
138
|
+
if (implicitAlias === from.name)
|
|
139
|
+
fromElement = elements[col.as || implicitAlias];
|
|
140
|
+
else if (implicitAlias === to.name)
|
|
141
|
+
toElement = elements[col.as || implicitAlias];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return fromElement && toElement &&
|
|
146
|
+
hasAnnotationValue(fromElement, validFromString, false) &&
|
|
147
|
+
hasAnnotationValue(toElement, validToString, false);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get the forEachDefinition callback function that collects all usages of @cds.valid.from/to/key and checks that
|
|
153
|
+
* - the assignment is on a valid element
|
|
154
|
+
* - the annotation is only assigned once
|
|
155
|
+
* - key is only used in conjunction with from and to
|
|
156
|
+
*
|
|
157
|
+
* Furthermore, @cds.valid.from and @cds.valid.key is processed - @cds.valid.from is marked as key or marked as unique if @cds.valid.key is used.
|
|
158
|
+
* If @cds.valid.key is used, the real key-elements have their key-property removed (set non-enumerable as $key) and instead the @cds.valid.key-marked elements have it added.
|
|
159
|
+
*
|
|
160
|
+
* @param {CSN.Model} csn
|
|
161
|
+
* @param {CSN.Options} options
|
|
162
|
+
* @param {string} pathDelimiter
|
|
163
|
+
* @param {object} messageFunctions
|
|
164
|
+
* @param {Function} messageFunctions.error
|
|
165
|
+
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition processing the annotations.
|
|
166
|
+
*/
|
|
167
|
+
function getAnnotationHandler(csn, options, pathDelimiter, messageFunctions) {
|
|
168
|
+
const { error } = messageFunctions;
|
|
169
|
+
const {
|
|
170
|
+
extractValidFromToKeyElement, checkAssignment, checkMultipleAssignments, recurseElements,
|
|
171
|
+
} = getTransformers(csn, options, pathDelimiter);
|
|
172
|
+
|
|
173
|
+
return handleTemporalAnnotations;
|
|
174
|
+
/**
|
|
175
|
+
* @param {CSN.Artifact} artifact
|
|
176
|
+
* @param {string} artifactName
|
|
177
|
+
*/
|
|
178
|
+
function handleTemporalAnnotations(artifact, artifactName) {
|
|
179
|
+
const validFrom = [];
|
|
180
|
+
const validTo = [];
|
|
181
|
+
const validKey = [];
|
|
182
|
+
|
|
183
|
+
recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
|
|
184
|
+
const [ f, t, k ] = extractValidFromToKeyElement(member, path);
|
|
185
|
+
validFrom.push(...f);
|
|
186
|
+
validTo.push(...t);
|
|
187
|
+
validKey.push(...k);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (artifact.kind === 'entity' && !artifact.query) {
|
|
191
|
+
validFrom.forEach(obj => checkAssignment(validFromString, obj.element, obj.path, artifact));
|
|
192
|
+
validTo.forEach(obj => checkAssignment(validToString, obj.element, obj.path, artifact));
|
|
193
|
+
validKey.forEach(obj => checkAssignment('@cds.valid.key', obj.element, obj.path, artifact));
|
|
194
|
+
checkMultipleAssignments(validFrom, validFromString, artifact, artifactName);
|
|
195
|
+
checkMultipleAssignments(validTo, validToString, artifact, artifactName, true);
|
|
196
|
+
checkMultipleAssignments(validKey, '@cds.valid.key', artifact, artifactName);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// if there is an cds.valid.key, make this the only primary key
|
|
200
|
+
// otherwise add all cds.valid.from to primary key tuple
|
|
201
|
+
if (validKey.length) {
|
|
202
|
+
if (!validFrom.length || !validTo.length) {
|
|
203
|
+
error(null, [ 'definitions', artifactName ],
|
|
204
|
+
'Expecting “@cds.valid.from” and “@cds.valid.to” if “@cds.valid.key” is used');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
forEachMember(artifact, (member) => {
|
|
208
|
+
if (member.key) {
|
|
209
|
+
member.unique = true;
|
|
210
|
+
delete member.key;
|
|
211
|
+
// Remember that this element was a key in the original artifact.
|
|
212
|
+
// This is needed for localized convenience view generation.
|
|
213
|
+
setProp(member, '$key', true);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
validKey.forEach((member) => {
|
|
217
|
+
member.element.key = true;
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
validFrom.forEach((member) => {
|
|
221
|
+
member.element.unique = true;
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
validFrom.forEach((member) => {
|
|
226
|
+
member.element.key = true;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
module.exports = {
|
|
234
|
+
getViewDecorator,
|
|
235
|
+
getAnnotationHandler,
|
|
236
|
+
};
|
|
@@ -4,6 +4,7 @@ const { forAllQueries, forEachDefinition, walkCsnPath } = require('../../model/c
|
|
|
4
4
|
const { setProp } = require('../../base/model');
|
|
5
5
|
const { getRealName } = require('../../render/utils/common');
|
|
6
6
|
const { csnRefs } = require('../../model/csnRefs');
|
|
7
|
+
const { ModelError } = require('../../base/error');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* 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)`.
|
|
@@ -48,9 +49,12 @@ const { csnRefs } = require('../../model/csnRefs');
|
|
|
48
49
|
* @param {Function} error
|
|
49
50
|
*/
|
|
50
51
|
function handleExists(csn, options, error) {
|
|
51
|
-
|
|
52
|
+
let { inspectRef } = csnRefs(csn);
|
|
52
53
|
const generatedExists = new WeakMap();
|
|
53
54
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
55
|
+
if (artifact.projection) // do the same hack we do for the other stuff...
|
|
56
|
+
artifact.query = { SELECT: artifact.projection };
|
|
57
|
+
|
|
54
58
|
if (artifact.query) {
|
|
55
59
|
forAllQueries(artifact.query, (query, path) => {
|
|
56
60
|
if (!generatedExists.has(query)) {
|
|
@@ -78,11 +82,19 @@ function handleExists(csn, options, error) {
|
|
|
78
82
|
// to check for further exists
|
|
79
83
|
const { result, leftovers } = processExists(queryPath, exprPath);
|
|
80
84
|
walkCsnPath(csn, exprPath.slice(0, -1))[exprPath[exprPath.length - 1]] = result;
|
|
85
|
+
if (leftovers.length > 0)
|
|
86
|
+
inspectRef = csnRefs(csn).inspectRef; // Refresh caches - we need to resolve stuff in the newly created subquery
|
|
81
87
|
toProcess.push(...leftovers.reverse()); // any leftovers - schedule for further processing
|
|
82
88
|
}
|
|
83
89
|
}
|
|
84
90
|
}, [ 'definitions', artifactName, 'query' ]);
|
|
85
91
|
}
|
|
92
|
+
|
|
93
|
+
if (artifact.projection) { // undo our hack
|
|
94
|
+
artifact.projection = artifact.query.SELECT;
|
|
95
|
+
|
|
96
|
+
delete artifact.query;
|
|
97
|
+
}
|
|
86
98
|
});
|
|
87
99
|
|
|
88
100
|
/**
|
|
@@ -356,16 +368,23 @@ function handleExists(csn, options, error) {
|
|
|
356
368
|
const subselect = getSubselect(root.target, ref, sources);
|
|
357
369
|
|
|
358
370
|
const target = subselect.SELECT.from.as; // use subquery alias as target - prevent shadowing
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
371
|
+
const extension = root.keys ? translateManagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current) : translateUnmanagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current);
|
|
372
|
+
if (extension.length > 3)
|
|
373
|
+
subselect.SELECT.where.push('('); // add braces around the on-condition part to ensure precedence is kept
|
|
374
|
+
|
|
375
|
+
subselect.SELECT.where.push(...extension);
|
|
376
|
+
|
|
377
|
+
if (extension.length > 3)
|
|
378
|
+
subselect.SELECT.where.push(')');
|
|
365
379
|
|
|
366
380
|
newExpr.push('exists');
|
|
367
|
-
if (ref && ref.where)
|
|
368
|
-
|
|
381
|
+
if (ref && ref.where) {
|
|
382
|
+
const remappedWhere = remapExistingWhere(target, ref.where);
|
|
383
|
+
if (remappedWhere.length > 3)
|
|
384
|
+
subselect.SELECT.where.push(...[ 'and', '(', ...remappedWhere, ')' ]);
|
|
385
|
+
else
|
|
386
|
+
subselect.SELECT.where.push(...[ 'and', ...remappedWhere ]);
|
|
387
|
+
}
|
|
369
388
|
|
|
370
389
|
newExpr.push(subselect);
|
|
371
390
|
toContinue.push([ exprPath.concat(newExpr.length - 1), exprPath.concat([ newExpr.length - 1, 'SELECT', 'where' ]) ]);
|
|
@@ -403,21 +422,24 @@ function handleExists(csn, options, error) {
|
|
|
403
422
|
*
|
|
404
423
|
* @param {CSN.Element} root
|
|
405
424
|
* @param {string} target
|
|
406
|
-
* @param {CSN.Query} subselect This subselect will in the end replace <assoc> in EXISTS <assoc>
|
|
407
425
|
* @param {boolean} isPrefixedWithTableAlias
|
|
408
426
|
* @param {string} base
|
|
409
427
|
* @param {Token} current
|
|
428
|
+
* @returns {object[]} The stuff to add to the where
|
|
410
429
|
*/
|
|
411
|
-
function translateManagedAssocToWhere(root, target,
|
|
430
|
+
function translateManagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current) {
|
|
431
|
+
const whereExtension = [];
|
|
412
432
|
for (let j = 0; j < root.keys.length; j++) {
|
|
413
433
|
const lop = { ref: [ target, ...root.keys[j].ref ] }; // target side
|
|
414
434
|
const rop = { ref: (isPrefixedWithTableAlias ? [] : [ base ]).concat([ ...toRawRef(current.ref), ...root.keys[j].ref ]) }; // source side
|
|
415
435
|
|
|
416
436
|
if (j > 0)
|
|
417
|
-
|
|
437
|
+
whereExtension.push('and');
|
|
418
438
|
|
|
419
|
-
|
|
439
|
+
whereExtension.push(...[ lop, '=', rop ]);
|
|
420
440
|
}
|
|
441
|
+
|
|
442
|
+
return whereExtension;
|
|
421
443
|
}
|
|
422
444
|
|
|
423
445
|
/**
|
|
@@ -443,12 +465,13 @@ function handleExists(csn, options, error) {
|
|
|
443
465
|
*
|
|
444
466
|
* @param {CSN.Element} root
|
|
445
467
|
* @param {string} target
|
|
446
|
-
* @param {CSN.Query} subselect This subselect will in the end replace <assoc> in EXISTS <assoc>
|
|
447
468
|
* @param {boolean} isPrefixedWithTableAlias
|
|
448
469
|
* @param {string} base
|
|
449
470
|
* @param {Token} current
|
|
471
|
+
* @returns {object[]} The stuff to add to the where
|
|
450
472
|
*/
|
|
451
|
-
function translateUnmanagedAssocToWhere(root, target,
|
|
473
|
+
function translateUnmanagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current) {
|
|
474
|
+
const whereExtension = [];
|
|
452
475
|
for (let j = 0; j < root.on.length; j++) {
|
|
453
476
|
const part = root.on[j];
|
|
454
477
|
|
|
@@ -456,7 +479,7 @@ function handleExists(csn, options, error) {
|
|
|
456
479
|
// but also keep along stuff like null and undefined, so compiler
|
|
457
480
|
// can have a chance to complain/ we can fail later nicely maybe
|
|
458
481
|
if (!(part && part.ref)) {
|
|
459
|
-
|
|
482
|
+
whereExtension.push(part);
|
|
460
483
|
continue;
|
|
461
484
|
}
|
|
462
485
|
|
|
@@ -466,30 +489,82 @@ function handleExists(csn, options, error) {
|
|
|
466
489
|
// Dollar Self Backlink
|
|
467
490
|
if (isValidDollarSelf(root.on[j], root.$path.concat([ 'on', j ]), root.on[j + 1], root.on[j + 2], root.$path.concat([ 'on', j + 2 ]))) {
|
|
468
491
|
if (root.on[j].ref[0] === '$self' && root.on[j].ref.length === 1)
|
|
469
|
-
|
|
492
|
+
whereExtension.push(...translateDollarSelfToWhere(base, target, root.on[j + 2], root.$path.concat([ 'on', j + 2 ])));
|
|
470
493
|
else
|
|
471
|
-
|
|
494
|
+
whereExtension.push(...translateDollarSelfToWhere(base, target, root.on[j], root.$path.concat([ 'on', j ])));
|
|
472
495
|
|
|
473
496
|
j += 2;
|
|
474
497
|
}
|
|
475
498
|
else if (links && links[0].art === root) { // target side
|
|
476
|
-
|
|
499
|
+
whereExtension.push({ ref: [ target, ...part.ref.slice(1) ] });
|
|
477
500
|
}
|
|
478
501
|
else if (part.$scope === '$self') { // source side - "absolute" scope
|
|
479
|
-
|
|
480
|
-
|
|
502
|
+
const column = part._art._column;
|
|
503
|
+
if (column && column.as) { // Replace with the "original" expression (the .ref, .xpr etc.)
|
|
504
|
+
whereExtension.push(translateToSourceSide(column));
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
whereExtension.push(assignAndDeleteAs({}, part, { ref: [ base, ...part.ref.slice(1) ] }));
|
|
508
|
+
}
|
|
481
509
|
}
|
|
482
510
|
else if (art) { // source side - with local scope
|
|
483
|
-
if (isPrefixedWithTableAlias)
|
|
484
|
-
|
|
511
|
+
if (isPrefixedWithTableAlias || part.$scope === 'alias')
|
|
512
|
+
whereExtension.push({ ref: [ ...current.ref.slice(0, -1), ...part.ref ] });
|
|
485
513
|
else
|
|
486
|
-
|
|
514
|
+
whereExtension.push({ ref: [ base, ...current.ref.slice(0, -1), ...part.ref ] });
|
|
487
515
|
}
|
|
488
516
|
else { // operator - or any other leftover
|
|
489
|
-
|
|
517
|
+
whereExtension.push(part);
|
|
490
518
|
}
|
|
491
519
|
}
|
|
492
520
|
|
|
521
|
+
return whereExtension;
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Run Object.assign on all of the passed in parameters and delete a .as at the end
|
|
526
|
+
*
|
|
527
|
+
* @param {...any} args
|
|
528
|
+
* @returns {object} The merged args without an .as property
|
|
529
|
+
*/
|
|
530
|
+
function assignAndDeleteAs(...args) {
|
|
531
|
+
const obj = Object.assign.apply(null, args);
|
|
532
|
+
delete obj.as;
|
|
533
|
+
return obj;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Translate the given obj (a column-like thing) into an expression that we can use in the WHERE.
|
|
537
|
+
* - Strip off $self/$projection and correctly replace with source expression
|
|
538
|
+
* - Drill further down into .xpr
|
|
539
|
+
* - Correctly set table alias in front of ref
|
|
540
|
+
*
|
|
541
|
+
* @param {object} obj
|
|
542
|
+
* @returns {object}
|
|
543
|
+
*/
|
|
544
|
+
function translateToSourceSide(obj) {
|
|
545
|
+
if (obj.ref) {
|
|
546
|
+
if (obj.$scope === '$self') { // TODO: Check with this way down, do we keep the links?
|
|
547
|
+
const column = obj._art._column;
|
|
548
|
+
if (column && column.as)
|
|
549
|
+
return translateToSourceSide(column);
|
|
550
|
+
return assignAndDeleteAs({}, obj, { ref: [ base, ...obj.ref.slice(1) ] });
|
|
551
|
+
}
|
|
552
|
+
else if (typeof obj.$env === 'string') {
|
|
553
|
+
return assignAndDeleteAs({}, obj, { ref: [ obj.$env, ...obj.ref ] });
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return assignAndDeleteAs({}, obj, { ref: [ ...obj.ref ] });
|
|
557
|
+
}
|
|
558
|
+
else if (obj.xpr) { // we need to drill further down into .xpr
|
|
559
|
+
return assignAndDeleteAs({}, obj, { xpr: obj.xpr.map(translateToSourceSide) });
|
|
560
|
+
}
|
|
561
|
+
else if (obj.args) {
|
|
562
|
+
return assignAndDeleteAs({}, obj, { args: obj.args.map(translateToSourceSide) });
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return obj;
|
|
566
|
+
}
|
|
567
|
+
|
|
493
568
|
/**
|
|
494
569
|
* Check that an expression triple is a valid $self
|
|
495
570
|
*
|
|
@@ -562,7 +637,7 @@ function handleExists(csn, options, error) {
|
|
|
562
637
|
}
|
|
563
638
|
|
|
564
639
|
/**
|
|
565
|
-
* Check (using inspectRef -> links),
|
|
640
|
+
* Check (using inspectRef -> links), whether the first path step is an entity or query source
|
|
566
641
|
*
|
|
567
642
|
* @param {CSN.Path} path
|
|
568
643
|
* @returns {boolean}
|
|
@@ -612,7 +687,7 @@ function handleExists(csn, options, error) {
|
|
|
612
687
|
return error(null, xpr.$path, 'Boolean $env is not handled yet - please report this error!');
|
|
613
688
|
}
|
|
614
689
|
else if (xpr.ref) {
|
|
615
|
-
throw new
|
|
690
|
+
throw new ModelError('Missing $env and missing leading artifact ref - throwing to trigger recompilation!');
|
|
616
691
|
}
|
|
617
692
|
}
|
|
618
693
|
|
|
@@ -697,7 +772,7 @@ function handleExists(csn, options, error) {
|
|
|
697
772
|
*
|
|
698
773
|
* @param {string} base The source entity/query source name
|
|
699
774
|
* @param {string} target The target entity/query source name
|
|
700
|
-
* @param {
|
|
775
|
+
* @param {object} assoc The association element - the "not-$self" side of the comparison
|
|
701
776
|
* @param {CSN.Path} path
|
|
702
777
|
* @returns {TokenStream} The WHERE representing the $self comparison
|
|
703
778
|
*/
|