Skip to content

Commit 6910ac8

Browse files
committed
refactor: simplify js/aql bindVariable methods
The previous names suggested a real difference between the regular and the "alias" variants, but the only difference was that for one, the AQLVariable was created by the method, and the other accepted a custom fragment to bind the variable to. Also harmonized the methods between aql and js.
1 parent c48f5a9 commit 6910ac8

File tree

2 files changed

+64
-77
lines changed

2 files changed

+64
-77
lines changed

src/database/arangodb/aql-generator.ts

Lines changed: 41 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ class QueryContext {
160160
* @param variableNode the variable token as it is referenced in the query tree
161161
* @param aqlVariable the variable token as it will be available within the AQL fragment
162162
*/
163-
private newNestedContextWithVariableMapping(
163+
private newNestedContextWithVariableBinding(
164164
variableNode: VariableQueryNode,
165165
aqlVariable: AQLFragment,
166166
): QueryContext {
@@ -177,31 +177,24 @@ class QueryContext {
177177
/**
178178
* Creates a new QueryContext that is identical to this one but has one additional variable binding
179179
*
180-
* The AQLFragment for the variable will be available via getVariable().
180+
* If aqlFrag is omitted, a new AQLVariable will be created using the name of the variableNode.
181181
*
182-
* @param {VariableQueryNode} variableNode the variable as referenced in the query tree
183-
* @returns {QueryContext} the nested context
184-
*/
185-
introduceVariable(variableNode: VariableQueryNode): QueryContext {
186-
if (this.variableMap.has(variableNode)) {
187-
throw new Error(`Variable ${variableNode} is introduced twice`);
188-
}
189-
const variable = new AQLVariable(variableNode.label);
190-
return this.newNestedContextWithVariableMapping(variableNode, variable);
191-
}
192-
193-
/**
194-
* Creates a new QueryContext that is identical to this one but has one additional variable binding
182+
* For aqlFrag, you can specify an AQLVariable or a different AQLFragment (e.g. a field access).
183+
* Do not specify complex AQL expressions because they would be repeated for every use of the
184+
* variable.
195185
*
196186
* @param variableNode the variable as referenced in the query tree
197-
* @param existingVariable a variable that has been previously introduced with introduceVariable() and fetched by getVariable
198-
* @returns {QueryContext} the nested context
187+
* @param aqlFrag the fragment representing the variable in AQL
188+
* @returns the new context
199189
*/
200-
introduceVariableAlias(
190+
bindVariable(
201191
variableNode: VariableQueryNode,
202-
existingVariable: AQLFragment,
192+
aqlFrag: AQLFragment = new AQLVariable(variableNode.label),
203193
): QueryContext {
204-
return this.newNestedContextWithVariableMapping(variableNode, existingVariable);
194+
if (this.variableMap.has(variableNode)) {
195+
throw new Error(`Variable ${variableNode} is introduced twice`);
196+
}
197+
return this.newNestedContextWithVariableBinding(variableNode, aqlFrag);
205198
}
206199

207200
/**
@@ -224,7 +217,7 @@ class QueryContext {
224217
let newContext: QueryContext;
225218
if (resultVariable) {
226219
resultVar = new AQLQueryResultVariable(resultVariable.label);
227-
newContext = this.newNestedContextWithVariableMapping(resultVariable, resultVar);
220+
newContext = this.newNestedContextWithVariableBinding(resultVariable, resultVar);
228221
} else {
229222
resultVar = undefined;
230223
newContext = this;
@@ -287,7 +280,7 @@ class QueryContext {
287280
throw new Error(`Variable ${variableNode.toString()} is used but not introduced`);
288281
}
289282
// we're returning an AQLFragment as AQLVariable
290-
// it can be a non-variable fragment (e.g. a simple field access) if we used introduceVariableAlias
283+
// it can be a non-variable fragment (e.g. a simple field access) if we used bindVariable
291284
// typescript only allows it because there are no private fields in AQLVariable
292285
// TODO introduce a better way to introduce a VariableQueryNode as AQLVariable, then make this here return AQLFragment
293286
return variable;
@@ -337,7 +330,7 @@ function createAQLCompoundQuery(
337330
const variableAssignmentNodes: VariableAssignmentQueryNode[] = [];
338331
node = extractVariableAssignments(node, variableAssignmentNodes);
339332
for (const assignmentNode of variableAssignmentNodes) {
340-
context = context.introduceVariable(assignmentNode.variableNode);
333+
context = context.bindVariable(assignmentNode.variableNode);
341334
const tmpVar = context.getVariable(assignmentNode.variableNode);
342335
variableAssignments.push(
343336
aql`LET ${tmpVar} = ${processNode(assignmentNode.variableValueNode, context)}`,
@@ -472,7 +465,7 @@ register(VariableQueryNode, (node, context) => {
472465
});
473466

474467
register(VariableAssignmentQueryNode, (node, context) => {
475-
const newContext = context.introduceVariable(node.variableNode);
468+
const newContext = context.bindVariable(node.variableNode);
476469
const tmpVar = newContext.getVariable(node.variableNode);
477470

478471
// note that we have to know statically if the context var is a list or an object
@@ -551,7 +544,7 @@ register(RevisionQueryNode, (node, context) => {
551544

552545
register(FlexSearchQueryNode, (node, context) => {
553546
let itemContext = context
554-
.introduceVariable(node.itemVariable)
547+
.bindVariable(node.itemVariable)
555548
.withExtension(inFlexSearchFilterSymbol, true);
556549
const viewName = getFlexSearchViewNameForRootEntity(node.rootEntityType!);
557550
context.addCollectionAccess(viewName, AccessType.EXPLICIT_READ);
@@ -565,7 +558,7 @@ register(FlexSearchQueryNode, (node, context) => {
565558
});
566559

567560
register(TransformListQueryNode, (node, context) => {
568-
let itemContext = context.introduceVariable(node.itemVariable);
561+
let itemContext = context.bindVariable(node.itemVariable);
569562
const itemVar = itemContext.getVariable(node.itemVariable);
570563
let itemProjectionContext = itemContext;
571564

@@ -577,9 +570,7 @@ register(TransformListQueryNode, (node, context) => {
577570
const variableAssignmentNodes: VariableAssignmentQueryNode[] = [];
578571
innerNode = extractVariableAssignments(innerNode, variableAssignmentNodes);
579572
for (const assignmentNode of variableAssignmentNodes) {
580-
itemProjectionContext = itemProjectionContext.introduceVariable(
581-
assignmentNode.variableNode,
582-
);
573+
itemProjectionContext = itemProjectionContext.bindVariable(assignmentNode.variableNode);
583574
const tmpVar = itemProjectionContext.getVariable(assignmentNode.variableNode);
584575
variableAssignments.push(
585576
aql`LET ${tmpVar} = ${processNode(
@@ -665,7 +656,7 @@ function generateLimitClause({ skip = 0, maxCount }: LimitClauseArgs): AQLFragme
665656
*/
666657
function generateInClause(node: QueryNode, context: QueryContext, entityVar: AQLFragment) {
667658
if (node instanceof TransformListQueryNode && node.innerNode === node.itemVariable) {
668-
const itemContext = context.introduceVariableAlias(node.itemVariable, entityVar);
659+
const itemContext = context.bindVariable(node.itemVariable, entityVar);
669660
return generateInClauseWithFilterAndOrderAndLimit({
670661
node,
671662
itemContext,
@@ -846,7 +837,7 @@ register(AggregationQueryNode, (node, context) => {
846837
register(UpdateChildEntitiesQueryNode, (node, context) => {
847838
const itemsVar = aql.variable('items');
848839
const itemsWithIndexVar = aql.variable('itemsWithIndex');
849-
const childContext = context.introduceVariable(node.dictionaryVar);
840+
const childContext = context.bindVariable(node.dictionaryVar);
850841
const dictVar = childContext.getVariable(node.dictionaryVar);
851842
const updatedDictVar = aql.variable('updatedDict');
852843
const itemVar = aql.variable('item');
@@ -1529,7 +1520,7 @@ function processTraversalWithOnlyRelationSegmentsNoList(
15291520
);
15301521
}
15311522

1532-
const innerContext = context.introduceVariable(node.itemVariable);
1523+
const innerContext = context.bindVariable(node.itemVariable);
15331524
const itemVar = innerContext.getVariable(node.itemVariable);
15341525
const forStatementsFrag = getRelationTraversalForStatements({
15351526
node,
@@ -1580,7 +1571,7 @@ function processTraversalWithOnlyRelationSegmentsAsList(
15801571
// we could refactor the single usage so it does not use a TraversalQueryNode in the first place
15811572

15821573
const itemVar = aql.variable(`node`);
1583-
const innerContext = context.introduceVariableAlias(node.itemVariable, itemVar);
1574+
const innerContext = context.bindVariable(node.itemVariable, itemVar);
15841575

15851576
const forStatementsFrag = getRelationTraversalForStatements({
15861577
node,
@@ -1648,7 +1639,7 @@ function processTraversalWithListRelationSegmentsAndNonListFieldSegments(
16481639
segments: node.fieldSegments,
16491640
sourceFrag: rootVar,
16501641
});
1651-
const innerContext = context.introduceVariableAlias(node.itemVariable, fieldTraversalFrag);
1642+
const innerContext = context.bindVariable(node.itemVariable, fieldTraversalFrag);
16521643

16531644
// note: we don't filter out NULL values even if preserveNullValues is false because that's currently
16541645
// only a flag for performance - actually filtering out NULLs is done by a surrounding AggregationQueryNode
@@ -1715,7 +1706,7 @@ function processTraversalWithNonListRelationSegmentsAndNonListFieldSegments(
17151706
segments: node.fieldSegments,
17161707
sourceFrag: rootVar,
17171708
});
1718-
const innerContext = context.introduceVariableAlias(node.itemVariable, fieldTraversalFrag);
1709+
const innerContext = context.bindVariable(node.itemVariable, fieldTraversalFrag);
17191710

17201711
return aqlExt.firstOfSubquery(
17211712
forStatementsFrag,
@@ -1773,7 +1764,7 @@ function processTraversalWithRelationAndListFieldSegmentsUsingSubquery(
17731764
? (itemFrag: AQLFragment) => {
17741765
// don't provide rootEntityVariable
17751766
// (if we want to filter on root, it should happen outside already)
1776-
const innerContext = context.introduceVariableAlias(node.itemVariable, itemFrag);
1767+
const innerContext = context.bindVariable(node.itemVariable, itemFrag);
17771768
return processNode(filterNode, innerContext);
17781769
}
17791770
: undefined;
@@ -1786,8 +1777,8 @@ function processTraversalWithRelationAndListFieldSegmentsUsingSubquery(
17861777

17871778
const itemVar = aql.variable(`item`);
17881779
const innerContext = context
1789-
.introduceVariableAlias(node.itemVariable, itemVar)
1790-
.introduceVariableAlias(node.rootEntityVariable, rootVar);
1780+
.bindVariable(node.itemVariable, itemVar)
1781+
.bindVariable(node.rootEntityVariable, rootVar);
17911782

17921783
// The outerMapFrag will produce a list, so a simple RETURN mapFrag would result in nested lists
17931784
// -> we iterate over the items again to flatten the lists (the FOR ${itemVar} ...)
@@ -1851,8 +1842,8 @@ function processTraversalWithRelationAndListFieldSegmentsWithoutSort(
18511842
const innerMapFrag = innerNode
18521843
? (itemFrag: AQLFragment) => {
18531844
const innerContext = context
1854-
.introduceVariableAlias(node.itemVariable, itemFrag)
1855-
.introduceVariableAlias(node.rootEntityVariable, rootVar);
1845+
.bindVariable(node.itemVariable, itemFrag)
1846+
.bindVariable(node.rootEntityVariable, rootVar);
18561847
return processNode(innerNode, innerContext);
18571848
}
18581849
: undefined;
@@ -1882,7 +1873,7 @@ function processTraversalWithRelationAndListFieldSegmentsWithoutSort(
18821873
innerFilterFrag = (itemFrag: AQLFragment) => {
18831874
// don't map rootEntityVariable
18841875
// (if we want to filter on root, it should happen outside already)
1885-
const innerContext = context.introduceVariableAlias(node.itemVariable, itemFrag);
1876+
const innerContext = context.bindVariable(node.itemVariable, itemFrag);
18861877
return processNode(filterNode, innerContext);
18871878
};
18881879
}
@@ -2014,8 +2005,8 @@ function processTraversalWithRelationAndListFieldSegmentsWithSort(
20142005
// -> we produce items like this: { value: ..., sortValues: [...] }
20152006
const innerMapFrag = (itemFrag: AQLFragment) => {
20162007
const innerContext = context
2017-
.introduceVariableAlias(node.itemVariable, itemFrag)
2018-
.introduceVariableAlias(node.rootEntityVariable, rootVar);
2008+
.bindVariable(node.itemVariable, itemFrag)
2009+
.bindVariable(node.rootEntityVariable, rootVar);
20192010
const valueFrag = node.innerNode ? processNode(node.innerNode, innerContext) : itemFrag;
20202011

20212012
return aql.lines(
@@ -2042,7 +2033,7 @@ function processTraversalWithRelationAndListFieldSegmentsWithSort(
20422033
? (itemFrag: AQLFragment) => {
20432034
// don't provide rootEntityVariable
20442035
// (if we want to filter on root, it should happen outside already)
2045-
const innerContext = context.introduceVariableAlias(node.itemVariable, itemFrag);
2036+
const innerContext = context.bindVariable(node.itemVariable, itemFrag);
20462037
return processNode(filterNode, innerContext);
20472038
}
20482039
: undefined;
@@ -2107,13 +2098,13 @@ function processTraversalWithOnlyFieldSegmentsUsingArrayExpansion(
21072098
const innerNode = node.innerNode;
21082099
const mapFrag = innerNode
21092100
? (itemFrag: AQLFragment) =>
2110-
processNode(innerNode, context.introduceVariableAlias(node.itemVariable, itemFrag))
2101+
processNode(innerNode, context.bindVariable(node.itemVariable, itemFrag))
21112102
: undefined;
21122103

21132104
const filterNode = node.filterNode;
21142105
const filterFrag = filterNode
21152106
? (itemFrag: AQLFragment) =>
2116-
processNode(filterNode, context.introduceVariableAlias(node.itemVariable, itemFrag))
2107+
processNode(filterNode, context.bindVariable(node.itemVariable, itemFrag))
21172108
: undefined;
21182109

21192110
// if there are no list segments, this will just be a simple path access
@@ -2175,7 +2166,7 @@ function processTraversalWithOnlyFieldSegmentsUsingSubquery(
21752166

21762167
const itemVar = aql.variable('item');
21772168

2178-
const innerContext = context.introduceVariableAlias(node.itemVariable, itemVar);
2169+
const innerContext = context.bindVariable(node.itemVariable, itemVar);
21792170
const returnValueFrag = node.innerNode ? processNode(node.innerNode, innerContext) : itemVar;
21802171

21812172
// we could also do the filter in getFieldTraversalFragment(), but if we have a subquery anyway, this is simpler
@@ -2275,10 +2266,7 @@ function getRelationTraversalForStatements({
22752266
if (!segment.vertexFilterVariable) {
22762267
throw new Error(`vertexFilter is set, but vertexFilterVariable is not`);
22772268
}
2278-
const filterContext = context.introduceVariableAlias(
2279-
segment.vertexFilterVariable,
2280-
nodeVar,
2281-
);
2269+
const filterContext = context.bindVariable(segment.vertexFilterVariable, nodeVar);
22822270
// PRUNE to stop on a node that has to be filtered out (only necessary for traversals > 1 path length)
22832271
// however, PRUNE only seems to be a performance feature and is not reliably evaluated
22842272
// (e.g. it's not when using COLLECT with distinct for some reason), so we need to add a path filter
@@ -2292,7 +2280,7 @@ function getRelationTraversalForStatements({
22922280
}
22932281

22942282
const vertexInPathFrag = aql`${pathVar}.vertices[*]`;
2295-
const pathFilterContext = context.introduceVariableAlias(
2283+
const pathFilterContext = context.bindVariable(
22962284
segment.vertexFilterVariable,
22972285
vertexInPathFrag,
22982286
);
@@ -2511,7 +2499,7 @@ register(CreateEntitiesQueryNode, (node, context) => {
25112499
});
25122500

25132501
register(UpdateEntitiesQueryNode, (node, context) => {
2514-
const newContext = context.introduceVariable(node.currentEntityVariable);
2502+
const newContext = context.bindVariable(node.currentEntityVariable);
25152503
const entityVar = newContext.getVariable(node.currentEntityVariable);
25162504
let entityFrag: AQLFragment;
25172505
let options: AQLFragment;

0 commit comments

Comments
 (0)