org.exist.xquery.Optimizer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of exist-core Show documentation
Show all versions of exist-core Show documentation
eXist-db NoSQL Database Core
/*
* eXist-db Open Source Native XML Database
* Copyright (C) 2001 The eXist-db Authors
*
* [email protected]
* http://www.exist-db.org
*
* This library 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.1 of the License, or (at your option) any later version.
*
* This library 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 Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.exist.xquery;
import org.exist.dom.QName;
import org.exist.storage.DBBroker;
import org.exist.xquery.functions.array.ArrayConstructor;
import org.exist.xquery.pragmas.Optimize;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.xquery.util.ExpressionDumper;
import org.exist.xquery.value.AtomicValue;
import org.exist.xquery.value.Type;
import javax.annotation.Nullable;
import java.util.*;
import static org.apache.commons.lang3.ArrayUtils.isNotEmpty;
/**
* Analyzes the query and marks optimizable expressions for the query engine.
* This class just searches for potentially optimizable expressions in the query tree and
* encloses those expressions with an (#exist:optimize#) pragma. The real optimization
* work is not done by this class but by the pragma (see {@link org.exist.xquery.pragmas.Optimize}).
* The pragma may also decide that the optimization is not applicable and just execute
* the expression without any optimization.
*
* Currently, the optimizer is disabled by default. To enable it, set attribute enable-query-rewriting
* to yes in conf.xml:
*
* <xquery enable-java-binding="no" enable-query-rewriting="yes">...
*
* To enable/disable the optimizer for a single query, use an option:
*
* declare option exist:optimize "enable=yes|no";
*
*/
public class Optimizer extends DefaultExpressionVisitor {
private static final Logger LOG = LogManager.getLogger(Optimizer.class);
private XQueryContext context;
private int predicates = 0;
private boolean hasOptimized = false;
private List rewriters;
public Optimizer(XQueryContext context) {
this.context = context;
final DBBroker broker = context.getBroker();
this.rewriters = broker != null ? broker.getIndexController().getQueryRewriters(context) : Collections.emptyList();
}
public boolean hasOptimized() {
return hasOptimized;
}
@Override
public void visitLocationStep(final LocationStep locationStep) {
super.visitLocationStep(locationStep);
// check query rewriters if they want to rewrite the location step
Pragma optimizePragma = null;
for (QueryRewriter rewriter : rewriters) {
try {
optimizePragma = rewriter.rewriteLocationStep(locationStep);
if (optimizePragma != null) {
// expression was rewritten: return
hasOptimized = true;
break;
}
} catch (XPathException e) {
LOG.warn("Exception called while rewriting location step: {}", e.getMessage(), e);
}
}
boolean optimize = false;
// only location steps with predicates can be optimized:
@Nullable final Predicate[] preds = locationStep.getPredicates();
if (preds != null) {
// walk through the predicates attached to the current location step.
// try to find a predicate containing an expression which is an instance
// of Optimizable.
for (final Predicate pred : preds) {
final FindOptimizable find = new FindOptimizable();
pred.accept(find);
final List list = find.getOptimizables();
if (list.size() > 0 && canOptimize(list)) {
optimize = true;
break;
}
}
}
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.
if (!(parent instanceof RewritableExpression)) {
if (LOG.isTraceEnabled())
{
LOG.trace("Parent expression of step is not a PathExpr: {}", parent);}
return;
}
hasOptimized = true;
final RewritableExpression path = (RewritableExpression) parent;
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);
// Replace the old expression with the pragma
path.replace(locationStep, extension);
if (LOG.isTraceEnabled())
{
LOG.trace("Rewritten expression: {}", ExpressionDumper.dump(parent));}
} 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);
}
}
public void visitFilteredExpr(FilteredExpression filtered) {
super.visitFilteredExpr(filtered);
// check if filtered expression can be simplified:
// handles expressions like //foo/(baz)[...]
if (filtered.getExpression() instanceof LocationStep) {
// single location step: simplify by directly attaching it to the parent path expression
final LocationStep step = (LocationStep) filtered.getExpression();
final Expression parent = filtered.getParent();
if (parent instanceof RewritableExpression) {
final List preds = filtered.getPredicates();
final boolean optimizable = hasOptimizable(preds);
if (optimizable) {
// copy predicates
for (Predicate pred : preds) {
step.addPredicate(pred);
}
((RewritableExpression) parent).replace(filtered, step);
step.setParent(parent);
visitLocationStep(step);
return;
}
}
}
// check if there are any predicates which could be optimized
final List preds = filtered.getPredicates();
final boolean optimize = hasOptimizable(preds);
if (optimize) {
// we found at least one Optimizable. Rewrite the whole expression and
// enclose it in an (#exist:optimize#) pragma.
final Expression parent = filtered.getParent();
if (!(parent instanceof RewritableExpression)) {
if (LOG.isTraceEnabled())
{
LOG.trace("Parent expression: {} of step does not implement RewritableExpression", parent.getClass().getName());}
return;
}
if (LOG.isTraceEnabled())
{
LOG.trace("Rewriting expression: {}", ExpressionDumper.dump(filtered));}
hasOptimized = true;
final RewritableExpression path = (RewritableExpression) parent;
try {
// Create the pragma
final ExtensionExpression extension = new ExtensionExpression(context);
extension.addPragma(new Optimize(context, Optimize.OPTIMIZE_PRAGMA, null, false));
extension.setExpression(filtered);
// Replace the old expression with the pragma
path.replace(filtered, extension);
} catch (final XPathException e) {
LOG.warn("Failed to optimize expression: {}: {}", filtered, e.getMessage(), e);
}
}
}
private boolean hasOptimizable(List preds) {
// walk through the predicates attached to the current location step.
// try to find a predicate containing an expression which is an instance
// of Optimizable.
for (final Predicate pred : preds) {
final FindOptimizable find = new FindOptimizable();
pred.accept(find);
final List list = find.getOptimizables();
if (list.size() > 0 && canOptimize(list)) {
return true;
}
}
return false;
}
public void visitAndExpr(OpAnd and) {
if (predicates > 0) {
// inside a filter expression, we can often replace a logical and with
// a chain of filters, which can then be further optimized
Expression parent = and.getParent();
if (!(parent instanceof PathExpr)) {
if (LOG.isTraceEnabled())
{
LOG.trace("Parent expression of boolean operator is not a PathExpr: {}", parent);}
return;
}
PathExpr path;
Predicate predicate;
if (parent instanceof Predicate) {
predicate = (Predicate) parent;
path = predicate;
} else {
path = (PathExpr) parent;
parent = path.getParent();
if (!(parent instanceof Predicate) || path.getLength() > 1) {
LOG.debug("Boolean operator is not a top-level expression in the predicate: {}", parent == null ? "?" : parent.getClass().getName());
return;
}
predicate = (Predicate) parent;
}
if (LOG.isTraceEnabled())
{
LOG.trace("Rewriting boolean expression: {}", ExpressionDumper.dump(and));}
hasOptimized = true;
final LocationStep step = (LocationStep) predicate.getParent();
final Predicate newPred = new Predicate(context);
newPred.add(simplifyPath(and.getRight()));
step.insertPredicate(predicate, newPred);
path.replace(and, simplifyPath(and.getLeft()));
} else if (and.isRewritable()) {
and.getLeft().accept(this);
and.getRight().accept(this);
}
}
public void visitOrExpr(OpOr or) {
if (or.isRewritable()) {
or.getLeft().accept(this);
or.getRight().accept(this);
}
}
@Override
public void visitGeneralComparison(GeneralComparison comparison) {
// Check if the left operand is a path expression ending in a
// text() step. This step is unnecessary and makes it hard
// to further optimize the expression. We thus try to remove
// the extra text() step automatically.
// TODO should insert a pragma instead of removing the step
// we don't know at this point if there's an index to use
// Expression expr = comparison.getLeft();
// if (expr instanceof PathExpr) {
// PathExpr pathExpr = (PathExpr) expr;
// Expression last = pathExpr.getLastExpression();
// if (pathExpr.getLength() > 1 && last instanceof Step && ((Step)last).getTest().getType() == Type.TEXT) {
// pathExpr.remove(last);
// }
// }
comparison.getLeft().accept(this);
comparison.getRight().accept(this);
}
public void visitPredicate(Predicate predicate) {
++predicates;
super.visitPredicate(predicate);
--predicates;
}
/**
* Check if a global variable can be inlined, usually if it
* references a literal value or sequence thereof.
*
* @param ref the variable reference
*/
@Override
public void visitVariableReference(final VariableReference ref) {
final String ns = ref.getName().getNamespaceURI();
if (ns != null && ns.length() > 0) {
final Module[] modules = context.getModules(ns);
if (isNotEmpty(modules)) {
for (final Module module : modules) {
if (module != null && !module.isInternalModule()) {
final Collection vars = ((ExternalModule) module).getVariableDeclarations();
for (final VariableDeclaration var: vars) {
if (var.getName().equals(ref.getName()) && var.getExpression().isPresent()) {
var.getExpression().get().accept(this);
final Expression expression = simplifyPath(var.getExpression().get());
final InlineableVisitor visitor = new InlineableVisitor();
expression.accept(visitor);
if (visitor.isInlineable()) {
final Expression parent = ref.getParent();
if (parent instanceof RewritableExpression) {
if (LOG.isDebugEnabled()) {
LOG.debug("{} line {}: inlining variable {}", ref.getSource().toString(), ref.getLine(), ref.getName());
}
((RewritableExpression) parent).replace(ref, expression);
}
}
return; // exit function!
}
}
}
}
}
}
}
private boolean canOptimize(List list) {
for (final Optimizable optimizable : list) {
final int axis = optimizable.getOptimizeAxis();
if (!(axis == Constants.CHILD_AXIS || axis == Constants.DESCENDANT_AXIS ||
axis == Constants.DESCENDANT_SELF_AXIS || axis == Constants.ATTRIBUTE_AXIS ||
axis == Constants.DESCENDANT_ATTRIBUTE_AXIS || axis == Constants.SELF_AXIS
)) {
return false;
}
}
return true;
}
private int reverseAxis(int axis) {
switch (axis) {
case Constants.CHILD_AXIS:
return Constants.PARENT_AXIS;
case Constants.DESCENDANT_AXIS:
return Constants.ANCESTOR_AXIS;
case Constants.DESCENDANT_SELF_AXIS:
return Constants.ANCESTOR_SELF_AXIS;
}
return Constants.UNKNOWN_AXIS;
}
private Expression simplifyPath(Expression expression) {
if (!(expression instanceof PathExpr)) {
return expression;
}
final PathExpr path = (PathExpr) expression;
if (path.getLength() != 1) {
return path;
}
return path.getExpression(0);
}
/**
* Try to find an expression object implementing interface Optimizable.
*/
public static class FindOptimizable extends BasicExpressionVisitor {
List optimizables = new ArrayList<>();
public List getOptimizables() {
return optimizables;
}
public void visitPathExpr(PathExpr expression) {
for (int i = 0; i < expression.getLength(); i++) {
final Expression next = expression.getExpression(i);
next.accept(this);
}
}
public void visitGeneralComparison(GeneralComparison comparison) {
optimizables.add(comparison);
}
public void visitPredicate(Predicate predicate) {
predicate.getExpression(0).accept(this);
}
public void visitBuiltinFunction(Function function) {
if (function instanceof Optimizable) {
optimizables.add((Optimizable) function);
}
}
}
/**
* Traverses an expression subtree to check if it could be inlined.
*/
static class InlineableVisitor extends DefaultExpressionVisitor {
private boolean inlineable = true;
public boolean isInlineable() {
return inlineable;
}
@Override
public void visit(Expression expr) {
if (expr instanceof LiteralValue) {
return;
}
if (expr instanceof Atomize ||
expr instanceof DynamicCardinalityCheck ||
expr instanceof DynamicNameCheck ||
expr instanceof DynamicTypeCheck ||
expr instanceof UntypedValueCheck ||
expr instanceof ConcatExpr ||
expr instanceof ArrayConstructor) {
expr.accept(this);
} else {
inlineable = false;
}
}
@Override
public void visitPathExpr(PathExpr expr) {
// continue to check for numeric operators and other simple constructs,
// abort for all other path expressions with length > 1
if (expr instanceof OpNumeric ||
expr instanceof SequenceConstructor ||
expr.getLength() == 1) {
super.visitPathExpr(expr);
} else {
inlineable = false;
}
}
@Override
public void visitUserFunction(UserDefinedFunction function) {
inlineable = false;
}
@Override
public void visitBuiltinFunction(Function function) {
inlineable = false;
}
@Override
public void visitFunctionCall(FunctionCall call) {
inlineable = false;
}
@Override
public void visitForExpression(ForExpr forExpr) {
inlineable = false;
}
@Override
public void visitLetExpression(LetExpr letExpr) {
inlineable = false;
}
@Override
public void visitOrderByClause(OrderByClause orderBy) {
inlineable = false;
}
@Override
public void visitGroupByClause(GroupByClause groupBy) {
inlineable = false;
}
@Override
public void visitWhereClause(WhereClause where) {
inlineable = false;
}
@Override
public void visitConditional(ConditionalExpression conditional) {
inlineable = false;
}
@Override
public void visitLocationStep(LocationStep locationStep) {
}
@Override
public void visitPredicate(Predicate predicate) {
super.visitPredicate(predicate);
}
@Override
public void visitDocumentConstructor(DocumentConstructor constructor) {
inlineable = false;
}
@Override
public void visitElementConstructor(ElementConstructor constructor) {
inlineable = false;
}
@Override
public void visitTextConstructor(DynamicTextConstructor constructor) {
inlineable = false;
}
@Override
public void visitAttribConstructor(AttributeConstructor constructor) {
inlineable = false;
}
@Override
public void visitAttribConstructor(DynamicAttributeConstructor constructor) {
inlineable = false;
}
@Override
public void visitUnionExpr(Union union) {
inlineable = false;
}
@Override
public void visitIntersectionExpr(Intersect intersect) {
inlineable = false;
}
@Override
public void visitVariableDeclaration(VariableDeclaration decl) {
inlineable = false;
}
@Override
public void visitTryCatch(TryCatchExpression tryCatch) {
inlineable = false;
}
@Override
public void visitCastExpr(CastExpression expression) {
inlineable = false;
}
@Override
public void visitGeneralComparison(GeneralComparison comparison) {
inlineable = false;
}
@Override
public void visitAndExpr(OpAnd and) {
inlineable = false;
}
@Override
public void visitOrExpr(OpOr or) {
inlineable = false;
}
@Override
public void visitFilteredExpr(FilteredExpression filtered) {
inlineable = false;
}
@Override
public void visitVariableReference(VariableReference ref) {
inlineable = false;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy