Permalink
Browse files

[performance] Redesign query optimizer for operations using range ind…

…ex (comparisons, contains, starts-with...): split query rewriting into two parts: 1) inject pragma (for top-level optimizations) and rewrite filter expressions to use range functions, 2) check at runtime if optimization can be applied (index is available) or not. Fall back to default processing if no index available.

This approach should be more expensive than the compile-time optimization we used before, but has the advantage that queries do not need to be recompiled if the collection configuration changes. The overhead for rewriting the query on the fly was not measurable in my tests though (below the threshold of the profiler).
  • Loading branch information...
1 parent 7d96a73 commit 425e97eba295e51fd29caac74638f4525d7856b9 @wolfgangmm wolfgangmm committed Feb 9, 2014
View
75 extensions/indexes/range/src/org/exist/xquery/modules/range/Lookup.java
@@ -21,19 +21,26 @@
*/
package org.exist.xquery.modules.range;
+import org.exist.collections.Collection;
import org.exist.dom.DocumentSet;
import org.exist.dom.NodeSet;
import org.exist.dom.QName;
import org.exist.dom.VirtualNodeSet;
import org.exist.indexing.range.RangeIndex;
+import org.exist.indexing.range.RangeIndexConfig;
+import org.exist.indexing.range.RangeIndexConfigElement;
import org.exist.indexing.range.RangeIndexWorker;
import org.exist.storage.ElementValue;
+import org.exist.storage.IndexSpec;
+import org.exist.storage.NodePath;
+import org.exist.xmldb.XmldbURI;
import org.exist.xquery.*;
import org.exist.xquery.value.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
public class Lookup extends Function implements Optimizable {
@@ -120,10 +127,10 @@
)
};
- public static Lookup create(XQueryContext context, RangeIndex.Operator operator) {
+ public static Lookup create(XQueryContext context, RangeIndex.Operator operator, NodePath contextPath) {
for (FunctionSignature sig: signatures) {
if (sig.getName().getLocalName().equals(operator.toString())) {
- return new Lookup(context, sig);
+ return new Lookup(context, sig, contextPath);
}
}
return null;
@@ -133,18 +140,32 @@ public static Lookup create(XQueryContext context, RangeIndex.Operator operator)
protected QName contextQName = null;
protected int axis = Constants.UNKNOWN_AXIS;
private NodeSet preselectResult = null;
+ protected boolean canOptimize = false;
protected boolean optimizeSelf = false;
protected boolean optimizeChild = false;
protected Expression fallback = null;
+ protected NodePath contextPath = null;
public Lookup(XQueryContext context, FunctionSignature signature) {
+ this(context, signature, null);
+ }
+
+ public Lookup(XQueryContext context, FunctionSignature signature, NodePath contextPath) {
super(context, signature);
+ this.contextPath = contextPath;
}
public void setFallback(Expression expression) {
+ if (expression instanceof InternalFunctionCall) {
+ expression = ((InternalFunctionCall)expression).getFunction();
+ }
this.fallback = expression;
}
+ public Expression getFallback() {
+ return fallback;
+ }
+
public void setArguments(List<Expression> arguments) throws XPathException {
steps.clear();
Expression path = arguments.get(0);
@@ -205,6 +226,9 @@ else if (test.isWildcardTest())
@Override
public NodeSet preSelect(Sequence contextSequence, boolean useContext) throws XPathException {
+ if (!canOptimize) {
+ return ((Optimizable)fallback).preSelect(contextSequence, useContext);
+ }
if (contextSequence != null && !contextSequence.isPersistentSet())
// in-memory docs won't have an index
return NodeSet.EMPTY_SET;
@@ -262,6 +286,9 @@ public NodeSet preSelect(Sequence contextSequence, boolean useContext) throws XP
@Override
public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
+ if (!canOptimize && fallback != null) {
+ return fallback.eval(contextSequence, contextItem);
+ }
if (contextItem != null)
contextSequence = contextItem.toSequence();
@@ -273,7 +300,7 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
return fallback.eval(contextSequence, contextItem);
}
}
- NodeSet result = NodeSet.EMPTY_SET;
+ NodeSet result;
if (preselectResult == null) {
long start = System.currentTimeMillis();
Sequence input = getArgument(0).eval(contextSequence);
@@ -321,12 +348,52 @@ public void resetState(boolean postOptimization) {
}
if (!postOptimization) {
preselectResult = null;
+ canOptimize = false;
}
}
@Override
public boolean canOptimize(Sequence contextSequence) {
- return contextQName != null;
+ if (contextQName == null) {
+ return false;
+ }
+ RangeIndexConfigElement rice = findConfiguration(contextSequence);
+ if (rice == null) {
+ canOptimize = false;
+ if (fallback instanceof Optimizable) {
+ return ((Optimizable)fallback).canOptimize(contextSequence);
+ }
+ return false;
+ }
+ canOptimize = true;
+ return canOptimize;
+ }
+
+ private RangeIndexConfigElement findConfiguration(Sequence contextSequence) {
+ NodePath path = contextPath;
+ if (path == null) {
+ if (contextQName == null) {
+ return null;
+ }
+ path = new NodePath(contextQName);
+ }
+ for (final Iterator<Collection> i = contextSequence.getCollectionIterator(); i.hasNext(); ) {
+ final Collection collection = i.next();
+ if (collection.getURI().startsWith(XmldbURI.SYSTEM_COLLECTION_URI)) {
+ continue;
+ }
+ IndexSpec idxConf = collection.getIndexConfiguration(context.getBroker());
+ if (idxConf != null) {
+ RangeIndexConfig config = (RangeIndexConfig) idxConf.getCustomIndexSpec(RangeIndex.ID);
+ if (config != null) {
+ RangeIndexConfigElement rice = config.find(path);
+ if (rice != null && !rice.isComplex()) {
+ return rice;
+ }
+ }
+ }
+ }
+ return null;
}
@Override
View
207 extensions/indexes/range/src/org/exist/xquery/modules/range/OptimizeFieldPragma.java
@@ -0,0 +1,207 @@
+/*
+ * eXist Open Source Native XML Database
+ * Copyright (C) 2013 The eXist Project
+ * http://exist-db.org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * $Id$
+ */
+package org.exist.xquery.modules.range;
+
+import org.exist.Namespaces;
+import org.exist.dom.QName;
+import org.exist.indexing.range.*;
+import org.exist.storage.NodePath;
+import org.exist.xquery.*;
+import org.exist.xquery.pragmas.*;
+import org.exist.xquery.value.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A pragma which checks if an XPath expression could be replaced with a range field lookup.
+ *
+ * @author wolf
+ */
+public class OptimizeFieldPragma extends Pragma {
+
+ public final static QName OPTIMIZE_RANGE_PRAGMA = new QName("optimize-field", Namespaces.EXIST_NS, "exist");
+
+ private final XQueryContext context;
+ private Expression rewritten = null;
+ private AnalyzeContextInfo contextInfo;
+
+ public OptimizeFieldPragma(QName qname, String contents, XQueryContext context) throws XPathException {
+ super(qname, contents);
+ this.context = context;
+ }
+
+ @Override
+ public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
+ super.analyze(contextInfo);
+ this.contextInfo = contextInfo;
+ }
+
+ @Override
+ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
+ if (rewritten != null) {
+ rewritten.analyze(contextInfo);
+ return rewritten.eval(contextSequence, contextItem);
+ }
+ return null;
+ }
+
+ @Override
+ public void before(XQueryContext context, Expression expression) throws XPathException {
+ LocationStep locationStep = (LocationStep) expression;
+ if (locationStep.hasPredicates()) {
+ Expression parentExpr = locationStep.getParentExpression();
+ if (!(parentExpr instanceof RewritableExpression)) {
+ return;
+ }
+ final List<Predicate> preds = locationStep.getPredicates();
+
+ // get path of path expression before the predicates
+ NodePath contextPath = RangeQueryRewriter.toNodePath(RangeQueryRewriter.getPrecedingSteps(locationStep));
+
+ rewritten = tryRewriteToFields(locationStep, preds, contextPath);
+ }
+ }
+
+ @Override
+ public void after(XQueryContext context, Expression expression) throws XPathException {
+
+ }
+
+ private Expression tryRewriteToFields(LocationStep locationStep, List<Predicate> preds, NodePath contextPath) throws XPathException {
+ // without context path, we cannot rewrite the entire query
+ if (contextPath != null) {
+ List<Expression> args = null;
+ SequenceConstructor arg0 = null;
+ SequenceConstructor arg1 = null;
+
+ List<Predicate> notOptimizable = new ArrayList<Predicate>(preds.size());
+ List<Object> configs = locationStep.getContext().getBroker().getBrokerPool().getConfigurationManager().getCustomIndexSpecs(RangeIndex.ID);
+ // walk through the predicates attached to the current location step
+ // check if expression can be optimized
+ for (final Predicate pred : preds) {
+ if (pred.getLength() != 1) {
+ // can only optimize predicates with one expression
+ notOptimizable.add(pred);
+ continue;
+ }
+ Expression innerExpr = pred.getExpression(0);
+ List<LocationStep> steps = RangeQueryRewriter.getStepsToOptimize(innerExpr);
+ if (steps == null) {
+ notOptimizable.add(pred);
+ continue;
+ }
+ // compute left hand path
+ NodePath innerPath = RangeQueryRewriter.toNodePath(steps);
+ if (innerPath == null) {
+ notOptimizable.add(pred);
+ continue;
+ }
+ NodePath path = new NodePath(contextPath);
+ path.append(innerPath);
+
+ if (path.length() > 0) {
+ // find a range index configuration matching the full path to the predicate expression
+ RangeIndexConfigElement rice = findConfiguration(path, true, configs);
+ // found index configuration with sub-fields
+ if (rice != null && rice.isComplex() && rice.getNodePath().match(contextPath) && findConfiguration(path, false, configs) == null) {
+ // check for a matching sub-path and retrieve field information
+ RangeIndexConfigField field = ((ComplexRangeIndexConfigElement) rice).getField(path);
+ if (field != null) {
+ if (args == null) {
+ // initialize args
+ args = new ArrayList<Expression>(4);
+ arg0 = new SequenceConstructor(context);
+ args.add(arg0);
+ arg1 = new SequenceConstructor(context);
+ args.add(arg1);
+ }
+ // field is added to the sequence in first parameter
+ arg0.add(new LiteralValue(context, new StringValue(field.getName())));
+ // operator
+ arg1.add(new LiteralValue(context, new StringValue(RangeQueryRewriter.getOperator(innerExpr).toString())));
+ // append right hand expression as additional parameter
+ args.add(getKeyArg(innerExpr));
+ } else {
+ notOptimizable.add(pred);
+ continue;
+ }
+ } else {
+ notOptimizable.add(pred);
+ continue;
+ }
+ } else {
+ notOptimizable.add(pred);
+ continue;
+ }
+ }
+ if (args != null) {
+ // the entire filter expression can be replaced
+ // create range:field-equals function
+ FieldLookup func = new FieldLookup(context, FieldLookup.signatures[0]);
+ func.setFallback(locationStep);
+ func.setLocation(locationStep.getLine(), locationStep.getColumn());
+ func.setArguments(args);
+
+ Expression optimizedExpr = new InternalFunctionCall(func);
+ if (notOptimizable.size() > 0) {
+ final FilteredExpression filtered = new FilteredExpression(context, optimizedExpr);
+ for (Predicate pred : notOptimizable) {
+ filtered.addPredicate(pred);
+ }
+ optimizedExpr = filtered;
+ }
+
+ return optimizedExpr;
+ }
+ }
+ return null;
+ }
+
+ private Expression getKeyArg(Expression expression) {
+ if (expression instanceof GeneralComparison) {
+ return ((GeneralComparison)expression).getRight();
+ } else if (expression instanceof InternalFunctionCall) {
+ InternalFunctionCall fcall = (InternalFunctionCall) expression;
+ Function function = fcall.getFunction();
+ if (function instanceof Lookup) {
+ return function.getArgument(1);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Scan all index configurations to find one matching path.
+ */
+ private RangeIndexConfigElement findConfiguration(NodePath path, boolean complex, List<Object> configs) {
+ for (Object configObj : configs) {
+ final RangeIndexConfig config = (RangeIndexConfig) configObj;
+ final RangeIndexConfigElement rice = config.find(path);
+ if (rice != null && ((complex && rice.isComplex()) ||
+ (!complex && !rice.isComplex()))) {
+ return rice;
+ }
+ }
+ return null;
+ }
+}
View
241 extensions/indexes/range/src/org/exist/xquery/modules/range/RangeQueryRewriter.java
@@ -24,7 +24,6 @@
import org.exist.indexing.range.*;
import org.exist.storage.NodePath;
import org.exist.xquery.*;
-import org.exist.xquery.value.StringValue;
import java.util.ArrayList;
import java.util.List;
@@ -35,175 +34,77 @@
*/
public class RangeQueryRewriter extends QueryRewriter {
- private final RangeIndexWorker worker;
- private final List<Object> configs;
-
- public RangeQueryRewriter(RangeIndexWorker worker, List<Object> configs, XQueryContext context) {
+ public RangeQueryRewriter(XQueryContext context) {
super(context);
- this.worker = worker;
- this.configs = configs;
}
@Override
- public boolean rewriteLocationStep(LocationStep locationStep) throws XPathException {
+ public Pragma rewriteLocationStep(LocationStep locationStep) throws XPathException {
if (locationStep.hasPredicates()) {
Expression parentExpr = locationStep.getParentExpression();
- if (!(parentExpr instanceof RewritableExpression)) {
- return true;
- }
- final List<Predicate> preds = locationStep.getPredicates();
-
- // get path of path expression before the predicates
- NodePath contextPath = toNodePath(getPrecedingSteps(locationStep));
-
- if (tryRewriteToFields(locationStep, (RewritableExpression) parentExpr, preds, contextPath)) {
- return false;
- }
-
- // Step 2: process the remaining predicates
- for (Predicate pred : preds) {
- if (pred.getLength() != 1) {
- // can only optimize predicates with one expression
- break;
- }
+ if ((parentExpr instanceof RewritableExpression)) {
+ // Step 1: replace all optimizable expressions within predicates with
+ // calls to the range functions. If those functions are used or not will
+ // be decided at run time.
+
+ final List<Predicate> preds = locationStep.getPredicates();
+
+ // get path of path expression before the predicates
+ NodePath contextPath = toNodePath(getPrecedingSteps(locationStep));
+ // process the remaining predicates
+ for (Predicate pred : preds) {
+ if (pred.getLength() != 1) {
+ // can only optimize predicates with one expression
+ break;
+ }
- Expression innerExpr = pred.getExpression(0);
- List<LocationStep> steps = getStepsToOptimize(innerExpr);
- if (steps == null) {
- // no optimizable steps found
- continue;
- }
- // compute left hand path
- NodePath innerPath = toNodePath(steps);
- if (innerPath == null) {
- continue;
- }
- NodePath path;
- if (contextPath == null) {
- path = innerPath;
- } else {
- path = new NodePath(contextPath);
- path.append(innerPath);
- }
+ Expression innerExpr = pred.getExpression(0);
+ List<LocationStep> steps = getStepsToOptimize(innerExpr);
+ if (steps == null || steps.size() == 0) {
+ // no optimizable steps found
+ continue;
+ }
+ // compute left hand path
+ NodePath innerPath = toNodePath(steps);
+ if (innerPath == null) {
+ continue;
+ }
+ NodePath path;
+ if (contextPath == null) {
+ path = innerPath;
+ } else {
+ path = new NodePath(contextPath);
+ path.append(innerPath);
+ }
- if (path.length() > 0) {
- // find a range index configuration matching the full path to the predicate expression
- RangeIndexConfigElement rice = findConfiguration(path, false);
- if (rice != null && !rice.isComplex()) {
- // found simple index configuration: replace with call to lookup function
+ if (path.length() > 0) {
+ // replace with call to lookup function
// collect arguments
- Lookup func = rewrite(innerExpr);
+ Lookup func = rewrite(innerExpr, path);
// preserve original comparison: may need it for in-memory lookups
func.setFallback(innerExpr);
func.setLocation(innerExpr.getLine(), innerExpr.getColumn());
- // replace comparison with range:eq
- pred.replace(innerExpr, new InternalFunctionCall(func));
- }
- }
- }
- }
- return true;
- }
-
- private boolean tryRewriteToFields(LocationStep locationStep, RewritableExpression parentExpr, List<Predicate> preds, NodePath contextPath) throws XPathException {
- // without context path, we cannot rewrite the entire query
- if (contextPath != null) {
- List<Expression> args = null;
- SequenceConstructor arg0 = null;
- SequenceConstructor arg1 = null;
-
- List<Predicate> notOptimizable = new ArrayList<Predicate>(preds.size());
-
- // walk through the predicates attached to the current location step
- // check if expression can be optimized
- for (final Predicate pred : preds) {
- if (pred.getLength() != 1) {
- // can only optimize predicates with one expression
- notOptimizable.add(pred);
- continue;
- }
- Expression innerExpr = pred.getExpression(0);
- List<LocationStep> steps = getStepsToOptimize(innerExpr);
- if (steps == null) {
- notOptimizable.add(pred);
- continue;
- }
- // compute left hand path
- NodePath innerPath = toNodePath(steps);
- if (innerPath == null) {
- notOptimizable.add(pred);
- continue;
- }
- NodePath path = new NodePath(contextPath);
- path.append(innerPath);
- if (path.length() > 0) {
- // find a range index configuration matching the full path to the predicate expression
- RangeIndexConfigElement rice = findConfiguration(path, true);
- // found index configuration with sub-fields
- if (rice != null && rice.isComplex() && rice.getNodePath().match(contextPath) && findConfiguration(path, false) == null) {
- // check for a matching sub-path and retrieve field information
- RangeIndexConfigField field = ((ComplexRangeIndexConfigElement) rice).getField(path);
- if (field != null) {
- if (args == null) {
- // initialize args
- args = new ArrayList<Expression>(4);
- arg0 = new SequenceConstructor(getContext());
- args.add(arg0);
- arg1 = new SequenceConstructor(getContext());
- args.add(arg1);
- }
- // field is added to the sequence in first parameter
- arg0.add(new LiteralValue(getContext(), new StringValue(field.getName())));
- // operator
- arg1.add(new LiteralValue(getContext(), new StringValue(getOperator(innerExpr).toString())));
- // append right hand expression as additional parameter
- args.add(getKeyArg(innerExpr));
- } else {
- notOptimizable.add(pred);
- continue;
- }
- } else {
- notOptimizable.add(pred);
- continue;
- }
- } else {
- notOptimizable.add(pred);
- continue;
- }
- }
- if (args != null) {
- // the entire filter expression can be replaced
- RewritableExpression parent = parentExpr;
- // create range:field-equals function
- FieldLookup func = new FieldLookup(getContext(), FieldLookup.signatures[0]);
- func.setFallback(locationStep);
- func.setLocation(locationStep.getLine(), locationStep.getColumn());
- func.setArguments(args);
-
- Expression optimizedExpr = new InternalFunctionCall(func);
- if (notOptimizable.size() > 0) {
- final FilteredExpression filtered = new FilteredExpression(getContext(), optimizedExpr);
- for (Predicate pred : notOptimizable) {
- filtered.addPredicate(pred);
+ pred.replace(innerExpr, new InternalFunctionCall(func));
}
- optimizedExpr = filtered;
}
- parent.replace(locationStep, optimizedExpr);
- return true;
+ // Step 2: return an OptimizeFieldPragma to handle field lookups and optimize the entire
+ // path expression. If the pragma can optimize the path expression, the original code will
+ // not be called.
+ return new OptimizeFieldPragma(OptimizeFieldPragma.OPTIMIZE_RANGE_PRAGMA, null, getContext());
}
}
- return false;
+ return null;
}
- private Lookup rewrite(Expression expression) throws XPathException {
+ protected static Lookup rewrite(Expression expression, NodePath path) throws XPathException {
ArrayList<Expression> eqArgs = new ArrayList<Expression>(2);
if (expression instanceof GeneralComparison) {
GeneralComparison comparison = (GeneralComparison) expression;
eqArgs.add(comparison.getLeft());
eqArgs.add(comparison.getRight());
- Lookup func = Lookup.create(comparison.getContext(), getOperator(expression));
+ Lookup func = Lookup.create(comparison.getContext(), getOperator(expression), path);
func.setArguments(eqArgs);
return func;
} else if (expression instanceof InternalFunctionCall) {
@@ -213,7 +114,7 @@ private Lookup rewrite(Expression expression) throws XPathException {
if (function.isCalledAs("matches")) {
eqArgs.add(function.getArgument(0));
eqArgs.add(function.getArgument(1));
- Lookup func = Lookup.create(function.getContext(), RangeIndex.Operator.MATCH);
+ Lookup func = Lookup.create(function.getContext(), RangeIndex.Operator.MATCH, path);
func.setArguments(eqArgs);
return func;
}
@@ -222,20 +123,7 @@ private Lookup rewrite(Expression expression) throws XPathException {
return null;
}
- private Expression getKeyArg(Expression expression) {
- if (expression instanceof GeneralComparison) {
- return ((GeneralComparison)expression).getRight();
- } else if (expression instanceof InternalFunctionCall) {
- InternalFunctionCall fcall = (InternalFunctionCall) expression;
- Function function = fcall.getFunction();
- if (function instanceof Lookup) {
- return function.getArgument(1);
- }
- }
- return null;
- }
-
- private List<LocationStep> getStepsToOptimize(Expression expr) {
+ protected static List<LocationStep> getStepsToOptimize(Expression expr) {
if (expr instanceof GeneralComparison) {
GeneralComparison comparison = (GeneralComparison) expr;
return BasicExpressionVisitor.findLocationSteps(comparison.getLeft());
@@ -245,13 +133,23 @@ private Expression getKeyArg(Expression expression) {
if (function instanceof Lookup) {
if (function.isCalledAs("matches")) {
return BasicExpressionVisitor.findLocationSteps(function.getArgument(0));
+ } else {
+ Expression original = ((Lookup)function).getFallback();
+ return getStepsToOptimize(original);
}
}
}
return null;
}
- private RangeIndex.Operator getOperator(Expression expr) {
+ protected static RangeIndex.Operator getOperator(Expression expr) {
+ if (expr instanceof InternalFunctionCall) {
+ InternalFunctionCall fcall = (InternalFunctionCall) expr;
+ Function function = fcall.getFunction();
+ if (function instanceof Lookup) {
+ expr = ((Lookup)function).getFallback();
+ }
+ }
RangeIndex.Operator operator = RangeIndex.Operator.EQ;
if (expr instanceof GeneralComparison) {
GeneralComparison comparison = (GeneralComparison) expr;
@@ -292,26 +190,13 @@ private Expression getKeyArg(Expression expression) {
if (function instanceof Lookup && function.isCalledAs("matches")) {
operator = RangeIndex.Operator.MATCH;
}
+ } else if (expr instanceof Lookup && ((Function)expr).isCalledAs("matches")) {
+ operator = RangeIndex.Operator.MATCH;
}
return operator;
}
- /**
- * Scan all index configurations to find one matching path.
- */
- private RangeIndexConfigElement findConfiguration(NodePath path, boolean complex) {
- for (Object configObj : configs) {
- final RangeIndexConfig config = (RangeIndexConfig) configObj;
- final RangeIndexConfigElement rice = config.find(path);
- if (rice != null && ((complex && rice.isComplex()) ||
- (!complex && !rice.isComplex()))) {
- return rice;
- }
- }
- return null;
- }
-
- private NodePath toNodePath(List<LocationStep> steps) {
+ protected static NodePath toNodePath(List<LocationStep> steps) {
NodePath path = new NodePath();
for (LocationStep step: steps) {
if (step == null) {
@@ -335,7 +220,7 @@ private NodePath toNodePath(List<LocationStep> steps) {
return path;
}
- private List<LocationStep> getPrecedingSteps(LocationStep current) {
+ protected static List<LocationStep> getPrecedingSteps(LocationStep current) {
Expression parentExpr = current.getParentExpression();
if (!(parentExpr instanceof RewritableExpression)) {
return null;
View
5 src/org/exist/collections/CollectionConfigurationManager.java
@@ -404,10 +404,7 @@ public Void call() {
LOG.debug("Invalidating collection " + collectionPath);
}
- CollectionConfiguration removed = configurations.remove(new CollectionURI(collectionPath.getRawCollectionPath()));
- if (removed != null && pool != null) {
- pool.getXQueryPool().clear();
- }
+ configurations.remove(new CollectionURI(collectionPath.getRawCollectionPath()));
return null;
}
});
View
4 src/org/exist/xquery/ExtensionExpression.java
@@ -69,10 +69,8 @@ public Sequence eval(Sequence contextSequence, Item contextItem)
for (final Pragma pragma : pragmas) {
Sequence temp = pragma.eval(contextSequence, contextItem);
if (temp != null) {
- if (result != null)
- {throw new XPathException(this,
- "Conflicting pragmas: only one should return a result for eval");}
result = temp;
+ break;
}
}
if (result == null)
View
23 src/org/exist/xquery/Optimizer.java
@@ -71,12 +71,14 @@ public boolean hasOptimized() {
public void visitLocationStep(LocationStep locationStep) {
super.visitLocationStep(locationStep);
// check query rewriters if they want to rewrite the location step
+ Pragma optimizePragma = null;
for (QueryRewriter rewriter : rewriters) {
try {
- if (!rewriter.rewriteLocationStep(locationStep)) {
+ optimizePragma = rewriter.rewriteLocationStep(locationStep);
+ if (optimizePragma != null) {
// expression was rewritten: return
hasOptimized = true;
- return;
+ break;
}
} catch (XPathException e) {
LOG.warn("Exception called while rewriting location step: " + e.getMessage(), e);
@@ -100,10 +102,12 @@ public void visitLocationStep(LocationStep locationStep) {
}
}
}
+
+ final Expression parent = locationStep.getParentExpression();
+
if (optimize) {
// we found at least one Optimizable. Rewrite the whole expression and
// enclose it in an (#exist:optimize#) pragma.
- final Expression parent = locationStep.getParentExpression();
if (!(parent instanceof RewritableExpression)) {
if (LOG.isTraceEnabled())
{LOG.trace("Parent expression of step is not a PathExpr: " + parent);}
@@ -114,6 +118,9 @@ public void visitLocationStep(LocationStep locationStep) {
try {
// Create the pragma
final ExtensionExpression extension = new ExtensionExpression(context);
+ if (optimizePragma != null) {
+ extension.addPragma(optimizePragma);
+ }
extension.addPragma(new Optimize(context, Optimize.OPTIMIZE_PRAGMA, null, false));
extension.setExpression(locationStep);
@@ -162,6 +169,14 @@ public void visitLocationStep(LocationStep locationStep) {
} catch (final XPathException e) {
LOG.warn("Failed to optimize expression: " + locationStep + ": " + e.getMessage(), e);
}
+ } else if (optimizePragma != null) {
+ final ExtensionExpression extension = new ExtensionExpression(context);
+ extension.addPragma(optimizePragma);
+ extension.setExpression(locationStep);
+
+ // Replace the old expression with the pragma
+ final RewritableExpression path = (RewritableExpression) parent;
+ path.replace(locationStep, extension);
}
}
@@ -304,7 +319,7 @@ private int reverseAxis(int axis) {
/**
* Try to find an expression object implementing interface Optimizable.
*/
- private class FindOptimizable extends BasicExpressionVisitor {
+ public static class FindOptimizable extends BasicExpressionVisitor {
List<Optimizable> optimizables = new ArrayList<Optimizable>();
View
8 src/org/exist/xquery/QueryRewriter.java
@@ -37,15 +37,15 @@ public QueryRewriter(XQueryContext context) {
}
/**
- * Implementations should return true if the location step has been rewritten and should
- * not be further optimized.
+ * Rewrite the expression to make use of indexes. The method may also return an additional
+ * pragma to be added to the extension expression which is inserted by the optimizer.
*
* @param locationStep
* @return
* @throws XPathException
*/
- public boolean rewriteLocationStep(LocationStep locationStep) throws XPathException {
- return false;
+ public Pragma rewriteLocationStep(LocationStep locationStep) throws XPathException {
+ return null;
}
protected XQueryContext getContext() {

0 comments on commit 425e97e

Please sign in to comment.