/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-07 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 program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id$
*/
package org.exist.xquery;
import org.exist.xquery.pragmas.Optimize;
import org.apache.log4j.Logger;
import org.exist.xquery.functions.ExtFulltext;
import org.exist.xquery.util.ExpressionDumper;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 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:
*
* <pre>declare option exist:optimize "enable=yes|no";</pre>
*
*/
public class Optimizer extends DefaultExpressionVisitor {
private static final Logger LOG = Logger.getLogger(Optimizer.class);
private XQueryContext context;
private int predicates = 0;
private boolean hasOptimized = false;
public Optimizer(XQueryContext context) {
this.context = context;
}
public boolean hasOptimized() {
return hasOptimized;
}
public void visitLocationStep(LocationStep locationStep) {
super.visitLocationStep(locationStep);
boolean optimize = false;
// only location steps with predicates can be optimized:
if (locationStep.hasPredicates()) {
List preds = locationStep.getPredicates();
// 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 (Iterator i = preds.iterator(); i.hasNext(); ) {
Predicate pred = (Predicate) i.next();
FindOptimizable find = new FindOptimizable();
pred.accept(find);
List list = find.getOptimizables();
if (list.size() > 0 && canOptimize(list)) {
optimize = true;
break;
}
}
}
if (optimize) {
// we found at least one Optimizable. Rewrite the whole expression and
// enclose it in an (#exist:optimize#) pragma.
Expression parent = locationStep.getParentExpression();
if (!(parent instanceof PathExpr)) {
LOG.warn("Parent expression of step is not a PathExpr: " + parent);
return;
}
if (LOG.isTraceEnabled())
LOG.trace("Rewriting expression: " + ExpressionDumper.dump(locationStep));
hasOptimized = true;
PathExpr path = (PathExpr) parent;
try {
// Create the pragma
ExtensionExpression extension = new ExtensionExpression(context);
extension.addPragma(new Optimize(context, Optimize.OPTIMIZE_PRAGMA, null, false));
extension.setExpression(locationStep);
// Replace the old expression with the pragma
path.replaceExpression(locationStep, extension);
} catch (XPathException e) {
LOG.warn("Failed to optimize expression: " + locationStep + ": " + e.getMessage(), e);
}
}
}
public void visitFilteredExpr(FilteredExpression filtered) {
super.visitFilteredExpr(filtered);
boolean optimize = false;
List preds = filtered.getPredicates();
// 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 (Iterator i = preds.iterator(); i.hasNext(); ) {
Predicate pred = (Predicate) i.next();
FindOptimizable find = new FindOptimizable();
pred.accept(find);
List list = find.getOptimizables();
if (list.size() > 0 && canOptimize(list)) {
optimize = true;
break;
}
}
if (optimize) {
// we found at least one Optimizable. Rewrite the whole expression and
// enclose it in an (#exist:optimize#) pragma.
Expression parent = filtered.getParent();
if (!(parent instanceof PathExpr)) {
LOG.warn("Parent expression of step is not a PathExpr: " + parent);
return;
}
if (LOG.isTraceEnabled())
LOG.trace("Rewriting expression: " + ExpressionDumper.dump(filtered));
hasOptimized = true;
PathExpr path = (PathExpr) parent;
try {
// Create the pragma
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.replaceExpression(filtered, extension);
} catch (XPathException e) {
LOG.warn("Failed to optimize expression: " + filtered + ": " + e.getMessage(), e);
}
}
}
public void visitAndExpr(OpAnd and) {
if (predicates > 0) {
Expression parent = and.getParent();
if (!(parent instanceof PathExpr)) {
LOG.warn("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.warn("Boolean operator is not a top-level expression in the predicate: " + parent.getClass().getName());
return;
}
predicate = (Predicate) parent;
}
if (LOG.isTraceEnabled())
LOG.trace("Rewriting boolean expression: " + ExpressionDumper.dump(and));
hasOptimized = true;
LocationStep step = (LocationStep) predicate.getParent();
Predicate newPred = new Predicate(context);
newPred.add(and.getRight());
step.insertPredicate(predicate, newPred);
path.replaceExpression(and, and.getLeft());
}
}
public void visitPredicate(Predicate predicate) {
++predicates;
super.visitPredicate(predicate);
--predicates;
}
private boolean canOptimize(List list) {
for (int j = 0; j < list.size(); j++) {
Optimizable optimizable = (Optimizable) list.get(j);
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;
}
/**
* Try to find an expression object implementing interface Optimizable.
*/
private 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++) {
Expression next = expression.getExpression(i);
next.accept(this);
}
}
public void visitFtExpression(ExtFulltext fulltext) {
optimizables.add(fulltext);
}
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(function);
}
}
}
}