/*
* Copyright Aduna (http://www.aduna-software.com/) (c) 2007.
* Copyright James Leigh (c) 2006.
*
* Licensed under the Aduna BSD-style license.
*/
package org.openrdf.query.algebra.evaluation.impl;
import java.util.Set;
import org.openrdf.model.Value;
import org.openrdf.query.BindingSet;
import org.openrdf.query.Dataset;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.algebra.Bound;
import org.openrdf.query.algebra.EmptySet;
import org.openrdf.query.algebra.Extension;
import org.openrdf.query.algebra.ExtensionElem;
import org.openrdf.query.algebra.Filter;
import org.openrdf.query.algebra.SameTerm;
import org.openrdf.query.algebra.TupleExpr;
import org.openrdf.query.algebra.ValueConstant;
import org.openrdf.query.algebra.ValueExpr;
import org.openrdf.query.algebra.Var;
import org.openrdf.query.algebra.evaluation.QueryOptimizer;
import org.openrdf.query.algebra.helpers.QueryModelVisitorBase;
/**
* A query optimizer that embeds {@link Filter}s with {@link SameTerm}
* operators in statement patterns as much as possible. Operators like
* sameTerm(X, Y) are processed by renaming X to Y (or vice versa). Operators
* like sameTerm(X, <someURI>) are processed by assigning the URI to all
* occurring variables with name X.
*
* @author Arjohn Kampman
*/
public class SameTermFilterOptimizer implements QueryOptimizer {
/**
* Applies generally applicable optimizations to the supplied query: variable
* assignments are inlined.
*
* @param tupleExpr
* @return optimized TupleExpr
* @throws QueryEvaluationException
*/
public void optimize(TupleExpr tupleExpr, Dataset dataset, BindingSet bindings) {
tupleExpr.visit(new SameTermFilterVisitor());
}
protected class SameTermFilterVisitor extends QueryModelVisitorBase<RuntimeException> {
@Override
public void meet(SameTerm sameTerm) {
super.meet(sameTerm);
if (sameTerm.getParentNode() instanceof Filter) {
// SameTerm applies to the filter's argument
Filter filter = (Filter)sameTerm.getParentNode();
ValueExpr leftArg = sameTerm.getLeftArg();
ValueExpr rightArg = sameTerm.getRightArg();
// Verify that vars are bound by filterArg
Set<String> bindingNames = filter.getArg().getBindingNames();
if (leftArg instanceof Var && !bindingNames.contains(((Var)leftArg).getName())
|| rightArg instanceof Var && !bindingNames.contains(((Var)rightArg).getName()))
{
// One or both var(s) are unbound, this expression will never
// return any results
filter.replaceWith(new EmptySet());
return;
}
if (leftArg instanceof Var && rightArg instanceof Var) {
// Rename rightArg to leftArg
renameVar((Var)rightArg, (Var)leftArg, filter);
}
else if (leftArg instanceof Var && rightArg instanceof ValueConstant) {
bindVar((Var)leftArg, (ValueConstant)rightArg, filter);
}
else if (rightArg instanceof Var && leftArg instanceof ValueConstant) {
bindVar((Var)rightArg, (ValueConstant)leftArg, filter);
}
}
}
private void renameVar(Var oldVar, Var newVar, Filter filter) {
filter.getArg().visit(new VarRenamer(oldVar.getName(), newVar.getName()));
// Replace SameTerm-filter with an Extension, the old variable name
// might still be relevant to nodes higher in the tree
Extension extension = new Extension(filter.getArg());
extension.addElement(new ExtensionElem(new Var(newVar.getName()), oldVar.getName()));
filter.replaceWith(extension);
}
private void bindVar(Var var, ValueConstant valueConstant, Filter filter) {
filter.getArg().visit(new VarBinder(var.getName(), valueConstant.getValue()));
// No need to keep the comparison, but we do need to make sure
// that the variable is not null in case it comes from an
// optional statement pattern. Replace the SameTerm constraint with a
// Bound constraint.
filter.setCondition(new Bound(var));
}
}
protected class VarRenamer extends QueryModelVisitorBase<RuntimeException> {
private String oldName;
private String newName;
public VarRenamer(String oldName, String newName) {
this.oldName = oldName;
this.newName = newName;
}
@Override
public void meet(Var var) {
if (var.getName().equals(oldName)) {
var.setName(newName);
}
}
}
protected class VarBinder extends QueryModelVisitorBase<RuntimeException> {
private String varName;
private Value value;
public VarBinder(String varName, Value value) {
this.varName = varName;
this.value = value;
}
@Override
public void meet(Var var) {
if (var.getName().equals(varName)) {
var.setValue(value);
}
}
}
}