Skip to content

Commit 271a335

Browse files
committed
[optimisation] Allow FilteredExpr and Union expression context sequence to be optimised for ft:query
1 parent 1f036cf commit 271a335

File tree

3 files changed

+101
-37
lines changed

3 files changed

+101
-37
lines changed

exist-core/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,7 @@
912912
<include>src/main/java/org/exist/xmlrpc/RpcConnection.java</include>
913913
<include>src/main/java/org/exist/xqj/Marshaller.java</include>
914914
<include>src/test/java/org/exist/xqj/MarshallerTest.java</include>
915+
<include>src/main/java/org/exist/xquery/CombiningExpression.java</include>
915916
<include>src/test/java/org/exist/xquery/ConstructedNodesRecoveryTest.java</include>
916917
<include>src/main/java/org/exist/xquery/DeferredFunctionCall.java</include>
917918
<include>src/main/java/org/exist/xquery/DynamicCardinalityCheck.java</include>
@@ -1255,6 +1256,7 @@
12551256
<exclude>src/main/java/org/exist/xqj/Marshaller.java</exclude>
12561257
<exclude>src/test/java/org/exist/xqj/MarshallerTest.java</exclude>
12571258
<exclude>src/main/java/org/exist/xquery/Cardinality.java</exclude>
1259+
<exclude>src/main/java/org/exist/xquery/CombiningExpression.java</exclude>
12581260
<exclude>src/test/java/org/exist/xquery/ConstructedNodesRecoveryTest.java</exclude>
12591261
<include>src/main/java/org/exist/xquery/DeferredFunctionCall.java</include>
12601262
<include>src/main/java/org/exist/xquery/DynamicCardinalityCheck.java</include>

exist-core/src/main/java/org/exist/xquery/CombiningExpression.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,28 @@
11
/*
2+
* Elemental
3+
* Copyright (C) 2024, Evolved Binary Ltd
4+
*
5+
6+
* https://www.evolvedbinary.com | https://www.elemental.xyz
7+
*
8+
* This library is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU Lesser General Public
10+
* License as published by the Free Software Foundation; version 2.1.
11+
*
12+
* This library is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
* Lesser General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public
18+
* License along with this library; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20+
*
21+
* NOTE: Parts of this file contain code from 'The eXist-db Authors'.
22+
* The original license header is included below.
23+
*
24+
* =====================================================================
25+
*
226
* eXist-db Open Source Native XML Database
327
* Copyright (C) 2001 The eXist-db Authors
428
*
@@ -144,4 +168,12 @@ public void accept(final ExpressionVisitor visitor) {
144168
left.accept(visitor);
145169
right.accept(visitor);
146170
}
171+
172+
public PathExpr getLeft() {
173+
return left;
174+
}
175+
176+
public PathExpr getRight() {
177+
return right;
178+
}
147179
}

extensions/indexes/lucene/src/main/java/org/exist/xquery/modules/lucene/Query.java

Lines changed: 67 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,7 @@
5959

6060
import javax.annotation.Nullable;
6161
import java.io.IOException;
62-
import java.util.ArrayList;
63-
import java.util.Collections;
62+
import java.util.Arrays;
6463
import java.util.List;
6564

6665
import static org.exist.xquery.FunctionDSL.*;
@@ -107,7 +106,7 @@ public class Query extends Function implements Optimizable {
107106
);
108107

109108
private LocationStep contextStep = null;
110-
protected QName contextQName = null;
109+
@Nullable private QName contextQNames[] = null;
111110
protected int axis = Constants.UNKNOWN_AXIS;
112111
private NodeSet preselectResult = null;
113112
protected boolean optimizeSelf = false;
@@ -148,36 +147,29 @@ public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException
148147
if (firstStep != null && steps.size() == 1 && firstStep.getAxis() == Constants.SELF_AXIS) {
149148
final Expression outerExpr = contextInfo.getContextStep();
150149
if (outerExpr instanceof LocationStep) {
151-
final LocationStep outerStep = (LocationStep) outerExpr;
152-
final NodeTest test = outerStep.getTest();
153-
154-
final byte contextQNameType;
155-
if (outerStep.getAxis() == Constants.ATTRIBUTE_AXIS || outerStep.getAxis() == Constants.DESCENDANT_ATTRIBUTE_AXIS) {
156-
contextQNameType = ElementValue.ATTRIBUTE;
157-
} else {
158-
contextQNameType = ElementValue.ELEMENT;
150+
final LocationStep outerLocationStep = (LocationStep) outerExpr;
151+
analyzeLocationStep(firstStep, outerLocationStep);
152+
} else if (outerExpr instanceof FilteredExpression) {
153+
final FilteredExpression outerStep = (FilteredExpression) outerExpr;
154+
// NOTE(AR) fix for https://github.com/eXist-db/exist/issues/3207
155+
if (outerStep.getExpression() instanceof LocationStep) {
156+
final LocationStep outerLocationStep = (LocationStep) outerStep.getExpression();
157+
analyzeLocationStep(firstStep, outerLocationStep);
158+
} else if (outerStep.getExpression() instanceof Union) {
159+
final Union union = (Union) outerStep.getExpression();
160+
analyzeUnion(firstStep, union);
159161
}
160-
161-
if (test.getName() == null) {
162-
contextQName = new QName(null, null, contextQNameType);
163-
} else {
164-
contextQName = new QName(test.getName(), contextQNameType);
165-
}
166-
167-
contextStep = firstStep;
168-
axis = outerStep.getAxis();
169-
optimizeSelf = true;
170162
}
171163
} else if (lastStep != null && firstStep != null) {
172164
final NodeTest test = lastStep.getTest();
173165
if (test.getName() == null) {
174-
contextQName = new QName(null, null, null);
166+
contextQNames = new QName[]{ new QName(null, null, null) };
175167
} else if (test.isWildcardTest()) {
176-
contextQName = test.getName();
168+
contextQNames = new QName[]{ test.getName() };
177169
} else if (lastStep.getAxis() == Constants.ATTRIBUTE_AXIS || lastStep.getAxis() == Constants.DESCENDANT_ATTRIBUTE_AXIS) {
178-
contextQName = new QName(test.getName(), ElementValue.ATTRIBUTE);
170+
contextQNames = new QName[]{ new QName(test.getName(), ElementValue.ATTRIBUTE) };
179171
} else {
180-
contextQName = new QName(test.getName());
172+
contextQNames = new QName[]{ new QName(test.getName()) };
181173
}
182174
axis = firstStep.getAxis();
183175
optimizeChild = steps.size() == 1 &&
@@ -187,8 +179,54 @@ public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException
187179
}
188180
}
189181

182+
private void analyzeLocationStep(final LocationStep firstStep, final LocationStep locationStep) {
183+
contextQNames = new QName[]{ getContextQName(locationStep) };
184+
contextStep = firstStep;
185+
axis = locationStep.getAxis();
186+
optimizeSelf = true;
187+
}
188+
189+
private void analyzeUnion(final LocationStep firstStep, final Union union) {
190+
final PathExpr left = union.getLeft();
191+
final PathExpr right = union.getRight();
192+
193+
if (left.getPrimaryAxis() != right.getPrimaryAxis()) {
194+
return;
195+
}
196+
197+
if (left.getSubExpressionCount() == 1 && left.getSubExpression(0) instanceof LocationStep
198+
&& right.getSubExpressionCount() == 1 && right.getSubExpression(0) instanceof LocationStep) {
199+
final LocationStep leftLocationStep = (LocationStep) left.getSubExpression(0);
200+
final LocationStep rightLocationStep = (LocationStep) right.getSubExpression(0);
201+
contextQNames = new QName[] { getContextQName(leftLocationStep), getContextQName(rightLocationStep) };
202+
contextStep = firstStep;
203+
axis = left.getPrimaryAxis();
204+
optimizeSelf = true;
205+
}
206+
}
207+
208+
private QName getContextQName(final LocationStep locationStep) {
209+
final QName contextQName;
210+
211+
final byte contextQNameType;
212+
if (locationStep.getAxis() == Constants.ATTRIBUTE_AXIS || locationStep.getAxis() == Constants.DESCENDANT_ATTRIBUTE_AXIS) {
213+
contextQNameType = ElementValue.ATTRIBUTE;
214+
} else {
215+
contextQNameType = ElementValue.ELEMENT;
216+
}
217+
218+
final NodeTest test = locationStep.getTest();
219+
if (test.getName() == null) {
220+
contextQName = new QName(null, null, contextQNameType);
221+
} else {
222+
contextQName = new QName(test.getName(), contextQNameType);
223+
}
224+
225+
return contextQName;
226+
}
227+
190228
public boolean canOptimize(final Sequence contextSequence) {
191-
return contextQName != null;
229+
return contextQNames != null;
192230
}
193231

194232
@Override
@@ -221,8 +259,7 @@ public NodeSet preSelect(final Sequence contextSequence, final boolean useContex
221259

222260
final DocumentSet docs = contextSequence.getDocumentSet();
223261
final Item key = getKey(contextSequence, null);
224-
final List<QName> qnames = new ArrayList<>(1);
225-
qnames.add(contextQName);
262+
@Nullable final List<QName> qnames = contextQNames != null ? Arrays.asList(contextQNames) : null;
226263
final QueryOptions options = parseOptions(this, contextSequence, null, 3);
227264
try {
228265
if (key != null && Type.subTypeOf(key.getType(), Type.ELEMENT)) {
@@ -270,14 +307,7 @@ public Sequence eval(Sequence contextSequence, @Nullable final Item contextItem)
270307
final DocumentSet docs = inNodes.getDocumentSet();
271308
final LuceneIndexWorker index = (LuceneIndexWorker) context.getBroker().getIndexController().getWorkerByIndexId(LuceneIndex.ID);
272309
final Item key = getKey(contextSequence, contextItem);
273-
274-
@Nullable final List<QName> qnames;
275-
if (contextQName != null) {
276-
qnames = Collections.singletonList(contextQName);
277-
} else {
278-
qnames = null;
279-
}
280-
310+
@Nullable final List<QName> qnames = contextQNames != null ? Arrays.asList(contextQNames) : null;
281311
final QueryOptions options = parseOptions(this, contextSequence, contextItem, 3);
282312
try {
283313
if (key != null && Type.subTypeOf(key.getType(), Type.ELEMENT)) {
@@ -298,7 +328,7 @@ public Sequence eval(Sequence contextSequence, @Nullable final Item contextItem)
298328

299329
} else {
300330
// DW: contextSequence can be null
301-
contextStep.setPreloadedData(contextSequence.getDocumentSet(), preselectResult);
331+
contextStep.setPreloadedData(preselectResult.getDocumentSet(), preselectResult);
302332
result = getArgument(0).eval(contextSequence, null).toNodeSet();
303333
}
304334

0 commit comments

Comments
 (0)