Skip to content

Commit c023b92

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 e0de109 commit c023b92

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');
@@ -1535,7 +1526,7 @@ function processTraversalWithOnlyRelationSegmentsNoList(
15351526
);
15361527
}
15371528

1538-
const innerContext = context.introduceVariable(node.itemVariable);
1529+
const innerContext = context.bindVariable(node.itemVariable);
15391530
const itemVar = innerContext.getVariable(node.itemVariable);
15401531
const forStatementsFrag = getRelationTraversalForStatements({
15411532
node,
@@ -1586,7 +1577,7 @@ function processTraversalWithOnlyRelationSegmentsAsList(
15861577
// we could refactor the single usage so it does not use a TraversalQueryNode in the first place
15871578

15881579
const itemVar = aql.variable(`node`);
1589-
const innerContext = context.introduceVariableAlias(node.itemVariable, itemVar);
1580+
const innerContext = context.bindVariable(node.itemVariable, itemVar);
15901581

15911582
const forStatementsFrag = getRelationTraversalForStatements({
15921583
node,
@@ -1654,7 +1645,7 @@ function processTraversalWithListRelationSegmentsAndNonListFieldSegments(
16541645
segments: node.fieldSegments,
16551646
sourceFrag: rootVar,
16561647
});
1657-
const innerContext = context.introduceVariableAlias(node.itemVariable, fieldTraversalFrag);
1648+
const innerContext = context.bindVariable(node.itemVariable, fieldTraversalFrag);
16581649

16591650
// note: we don't filter out NULL values even if preserveNullValues is false because that's currently
16601651
// only a flag for performance - actually filtering out NULLs is done by a surrounding AggregationQueryNode
@@ -1721,7 +1712,7 @@ function processTraversalWithNonListRelationSegmentsAndNonListFieldSegments(
17211712
segments: node.fieldSegments,
17221713
sourceFrag: rootVar,
17231714
});
1724-
const innerContext = context.introduceVariableAlias(node.itemVariable, fieldTraversalFrag);
1715+
const innerContext = context.bindVariable(node.itemVariable, fieldTraversalFrag);
17251716

17261717
return aqlExt.firstOfSubquery(
17271718
forStatementsFrag,
@@ -1779,7 +1770,7 @@ function processTraversalWithRelationAndListFieldSegmentsUsingSubquery(
17791770
? (itemFrag: AQLFragment) => {
17801771
// don't provide rootEntityVariable
17811772
// (if we want to filter on root, it should happen outside already)
1782-
const innerContext = context.introduceVariableAlias(node.itemVariable, itemFrag);
1773+
const innerContext = context.bindVariable(node.itemVariable, itemFrag);
17831774
return processNode(filterNode, innerContext);
17841775
}
17851776
: undefined;
@@ -1792,8 +1783,8 @@ function processTraversalWithRelationAndListFieldSegmentsUsingSubquery(
17921783

17931784
const itemVar = aql.variable(`item`);
17941785
const innerContext = context
1795-
.introduceVariableAlias(node.itemVariable, itemVar)
1796-
.introduceVariableAlias(node.rootEntityVariable, rootVar);
1786+
.bindVariable(node.itemVariable, itemVar)
1787+
.bindVariable(node.rootEntityVariable, rootVar);
17971788

17981789
// The outerMapFrag will produce a list, so a simple RETURN mapFrag would result in nested lists
17991790
// -> we iterate over the items again to flatten the lists (the FOR ${itemVar} ...)
@@ -1885,8 +1876,8 @@ function processTraversalWithRelationAndListFieldSegmentsUsingArrayExpansionWith
18851876
const innerMapFrag = innerNode
18861877
? (itemFrag: AQLFragment) => {
18871878
const innerContext = context
1888-
.introduceVariableAlias(node.itemVariable, itemFrag)
1889-
.introduceVariableAlias(node.rootEntityVariable, rootVar);
1879+
.bindVariable(node.itemVariable, itemFrag)
1880+
.bindVariable(node.rootEntityVariable, rootVar);
18901881
return processNode(innerNode, innerContext);
18911882
}
18921883
: undefined;
@@ -1916,7 +1907,7 @@ function processTraversalWithRelationAndListFieldSegmentsUsingArrayExpansionWith
19161907
innerFilterFrag = (itemFrag: AQLFragment) => {
19171908
// don't map rootEntityVariable
19181909
// (if we want to filter on root, it should happen outside already)
1919-
const innerContext = context.introduceVariableAlias(node.itemVariable, itemFrag);
1910+
const innerContext = context.bindVariable(node.itemVariable, itemFrag);
19201911
return processNode(filterNode, innerContext);
19211912
};
19221913
}
@@ -2051,8 +2042,8 @@ function processTraversalWithRelationAndListFieldSegmentsUsingArrayExpansionWith
20512042
// -> we produce items like this: { value: ..., sortValues: [...] }
20522043
const innerMapFrag = (itemFrag: AQLFragment) => {
20532044
const innerContext = context
2054-
.introduceVariableAlias(node.itemVariable, itemFrag)
2055-
.introduceVariableAlias(node.rootEntityVariable, rootVar);
2045+
.bindVariable(node.itemVariable, itemFrag)
2046+
.bindVariable(node.rootEntityVariable, rootVar);
20562047
const valueFrag = node.innerNode ? processNode(node.innerNode, innerContext) : itemFrag;
20572048

20582049
return aql.lines(
@@ -2079,7 +2070,7 @@ function processTraversalWithRelationAndListFieldSegmentsUsingArrayExpansionWith
20792070
? (itemFrag: AQLFragment) => {
20802071
// don't provide rootEntityVariable
20812072
// (if we want to filter on root, it should happen outside already)
2082-
const innerContext = context.introduceVariableAlias(node.itemVariable, itemFrag);
2073+
const innerContext = context.bindVariable(node.itemVariable, itemFrag);
20832074
return processNode(filterNode, innerContext);
20842075
}
20852076
: undefined;
@@ -2144,13 +2135,13 @@ function processTraversalWithOnlyFieldSegmentsUsingArrayExpansion(
21442135
const innerNode = node.innerNode;
21452136
const mapFrag = innerNode
21462137
? (itemFrag: AQLFragment) =>
2147-
processNode(innerNode, context.introduceVariableAlias(node.itemVariable, itemFrag))
2138+
processNode(innerNode, context.bindVariable(node.itemVariable, itemFrag))
21482139
: undefined;
21492140

21502141
const filterNode = node.filterNode;
21512142
const filterFrag = filterNode
21522143
? (itemFrag: AQLFragment) =>
2153-
processNode(filterNode, context.introduceVariableAlias(node.itemVariable, itemFrag))
2144+
processNode(filterNode, context.bindVariable(node.itemVariable, itemFrag))
21542145
: undefined;
21552146

21562147
// if there are no list segments, this will just be a simple path access
@@ -2220,7 +2211,7 @@ function processTraversalWithOnlyFieldSegmentsUsingSubquery(
22202211

22212212
const itemVar = aql.variable('item');
22222213

2223-
const innerContext = context.introduceVariableAlias(node.itemVariable, itemVar);
2214+
const innerContext = context.bindVariable(node.itemVariable, itemVar);
22242215
const returnValueFrag = node.innerNode ? processNode(node.innerNode, innerContext) : itemVar;
22252216

22262217
return aqlExt.subquery(
@@ -2314,10 +2305,7 @@ function getRelationTraversalForStatements({
23142305
if (!segment.vertexFilterVariable) {
23152306
throw new Error(`vertexFilter is set, but vertexFilterVariable is not`);
23162307
}
2317-
const filterContext = context.introduceVariableAlias(
2318-
segment.vertexFilterVariable,
2319-
nodeVar,
2320-
);
2308+
const filterContext = context.bindVariable(segment.vertexFilterVariable, nodeVar);
23212309
// PRUNE to stop on a node that has to be filtered out (only necessary for traversals > 1 path length)
23222310
// however, PRUNE only seems to be a performance feature and is not reliably evaluated
23232311
// (e.g. it's not when using COLLECT with distinct for some reason), so we need to add a path filter
@@ -2331,7 +2319,7 @@ function getRelationTraversalForStatements({
23312319
}
23322320

23332321
const vertexInPathFrag = aql`${pathVar}.vertices[*]`;
2334-
const pathFilterContext = context.introduceVariableAlias(
2322+
const pathFilterContext = context.bindVariable(
23352323
segment.vertexFilterVariable,
23362324
vertexInPathFrag,
23372325
);
@@ -2551,7 +2539,7 @@ register(CreateEntitiesQueryNode, (node, context) => {
25512539
});
25522540

25532541
register(UpdateEntitiesQueryNode, (node, context) => {
2554-
const newContext = context.introduceVariable(node.currentEntityVariable);
2542+
const newContext = context.bindVariable(node.currentEntityVariable);
25552543
const entityVar = newContext.getVariable(node.currentEntityVariable);
25562544
let entityFrag: AQLFragment;
25572545
let options: AQLFragment;

0 commit comments

Comments
 (0)