Package org.openrdf.sail.rdbms.optimizers

Source Code of org.openrdf.sail.rdbms.optimizers.SelectQueryOptimizer

/*
* Copyright Aduna (http://www.aduna-software.com/) (c) 2008.
*
* Licensed under the Aduna BSD-style license.
*/
package org.openrdf.sail.rdbms.optimizers;

import static org.openrdf.sail.rdbms.algebra.ColumnVar.createCtx;
import static org.openrdf.sail.rdbms.algebra.ColumnVar.createObj;
import static org.openrdf.sail.rdbms.algebra.ColumnVar.createPred;
import static org.openrdf.sail.rdbms.algebra.ColumnVar.createSubj;
import static org.openrdf.sail.rdbms.algebra.base.SqlExprSupport.coalesce;
import static org.openrdf.sail.rdbms.algebra.base.SqlExprSupport.eq;
import static org.openrdf.sail.rdbms.algebra.base.SqlExprSupport.isNull;
import static org.openrdf.sail.rdbms.algebra.base.SqlExprSupport.or;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.openrdf.model.BNode;
import org.openrdf.model.Literal;
import org.openrdf.model.Resource;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.query.BindingSet;
import org.openrdf.query.Dataset;
import org.openrdf.query.algebra.And;
import org.openrdf.query.algebra.Distinct;
import org.openrdf.query.algebra.Filter;
import org.openrdf.query.algebra.Join;
import org.openrdf.query.algebra.LeftJoin;
import org.openrdf.query.algebra.Order;
import org.openrdf.query.algebra.OrderElem;
import org.openrdf.query.algebra.Projection;
import org.openrdf.query.algebra.ProjectionElem;
import org.openrdf.query.algebra.ProjectionElemList;
import org.openrdf.query.algebra.QueryModel;
import org.openrdf.query.algebra.Slice;
import org.openrdf.query.algebra.StatementPattern;
import org.openrdf.query.algebra.TupleExpr;
import org.openrdf.query.algebra.Union;
import org.openrdf.query.algebra.ValueExpr;
import org.openrdf.query.algebra.Var;
import org.openrdf.query.algebra.StatementPattern.Scope;
import org.openrdf.query.algebra.evaluation.QueryOptimizer;
import org.openrdf.query.impl.DatasetImpl;
import org.openrdf.sail.rdbms.RdbmsValueFactory;
import org.openrdf.sail.rdbms.algebra.BNodeColumn;
import org.openrdf.sail.rdbms.algebra.ColumnVar;
import org.openrdf.sail.rdbms.algebra.DatatypeColumn;
import org.openrdf.sail.rdbms.algebra.IdColumn;
import org.openrdf.sail.rdbms.algebra.JoinItem;
import org.openrdf.sail.rdbms.algebra.LabelColumn;
import org.openrdf.sail.rdbms.algebra.LanguageColumn;
import org.openrdf.sail.rdbms.algebra.LongLabelColumn;
import org.openrdf.sail.rdbms.algebra.LongURIColumn;
import org.openrdf.sail.rdbms.algebra.NumberValue;
import org.openrdf.sail.rdbms.algebra.RefIdColumn;
import org.openrdf.sail.rdbms.algebra.SelectProjection;
import org.openrdf.sail.rdbms.algebra.SelectQuery;
import org.openrdf.sail.rdbms.algebra.SqlEq;
import org.openrdf.sail.rdbms.algebra.SqlOr;
import org.openrdf.sail.rdbms.algebra.URIColumn;
import org.openrdf.sail.rdbms.algebra.UnionItem;
import org.openrdf.sail.rdbms.algebra.base.RdbmsQueryModelVisitorBase;
import org.openrdf.sail.rdbms.algebra.base.SqlExpr;
import org.openrdf.sail.rdbms.algebra.factories.SqlExprFactory;
import org.openrdf.sail.rdbms.exceptions.RdbmsException;
import org.openrdf.sail.rdbms.exceptions.UnsupportedRdbmsOperatorException;
import org.openrdf.sail.rdbms.managers.TransTableManager;
import org.openrdf.sail.rdbms.model.RdbmsResource;

/**
* Rewrites the core algebra model with a relation optimised model, using SQL.
*
* @author James Leigh
*/
public class SelectQueryOptimizer extends RdbmsQueryModelVisitorBase<RdbmsException> implements
    QueryOptimizer
{

  private static final String ALIAS = "t";

  private SqlExprFactory sql;

  private int aliasCount = 0;

  private BindingSet bindings;

  private Dataset dataset;

  private RdbmsValueFactory vf;

  private TransTableManager tables;

  public void setSqlExprFactory(SqlExprFactory sql) {
    this.sql = sql;
  }

  public void setValueFactory(RdbmsValueFactory vf) {
    this.vf = vf;
  }

  public void setTransTableManager(TransTableManager statements) {
    this.tables = statements;
  }

  public void optimize(QueryModel query, BindingSet bindings)
    throws RdbmsException
  {
    if (!query.getDefaultGraphs().isEmpty() || !query.getNamedGraphs().isEmpty()) {
      this.dataset = new DatasetImpl(query.getDefaultGraphs(), query.getNamedGraphs());
    }
    this.bindings = bindings;
    query.visit(this);
  }

  @Override
  public void meet(Distinct node)
    throws RdbmsException
  {
    super.meet(node);
    if (node.getArg() instanceof SelectQuery) {
      SelectQuery query = (SelectQuery)node.getArg();
      query.setDistinct(true);
      node.replaceWith(query);
    }
  }

  @Override
  public void meet(Union node)
    throws RdbmsException
  {
    super.meet(node);
    for (TupleExpr arg : node.getArgs()) {
      if (!(arg instanceof SelectQuery)) {
        return;
      }
      SelectQuery sq = (SelectQuery)arg;
      if (sq.isComplex()) {
        return;
      }
    }
    assert node.getNumberOfArguments() > 0;
    UnionItem union = new UnionItem("u" + aliasCount++);
    SelectQuery query = new SelectQuery();
    query.setFrom(union);
    for (TupleExpr arg : node.getArgs()) {
      SelectQuery sub = (SelectQuery)arg.clone();
      union.addUnion(sub.getFrom().clone());
      mergeSelectClause(query, sub);
    }
    addProjectionsFromUnion(query, union);
    node.replaceWith(query);
  }

  /**
   * This happens when both sides of the union have the same variable name with
   * an implied value.
   */
  private void addProjectionsFromUnion(SelectQuery query, UnionItem union) {
    for (ColumnVar var : union.getSelectColumns()) {
      if (!query.hasSqlSelectVarName(var.getName())) {
        SelectProjection proj = new SelectProjection();
        proj.setVar(var);
        proj.setId(new RefIdColumn(var));
        proj.setStringValue(coalesce(new URIColumn(var), new BNodeColumn(var), new LabelColumn(var),
            new LongLabelColumn(var), new LongURIColumn(var)));
        proj.setDatatype(new DatatypeColumn(var));
        proj.setLanguage(new LanguageColumn(var));
        query.addSqlSelectVar(proj);
      }
    }
  }

  @Override
  public void meet(Join node)
    throws RdbmsException
  {
    super.meet(node);
    for (TupleExpr arg : node.getArgs()) {
      if (!(arg instanceof SelectQuery)) {
        return;
      }
      SelectQuery sq = (SelectQuery)arg;
      if (sq.isComplex()) {
        return;
      }
    }
    assert node.getNumberOfArguments() > 0;
    SelectQuery left = null;
    for (TupleExpr arg : node.getArgs()) {
      if (left == null) {
        left = (SelectQuery)arg.clone();
      }
      else {
        SelectQuery right = (SelectQuery)arg.clone();
        filterOn(left, right);
        mergeSelectClause(left, right);
        left.addJoin(right);
      }
    }
    node.replaceWith(left);
  }

  @Override
  public void meet(LeftJoin node)
    throws RdbmsException
  {
    super.meet(node);
    TupleExpr l = node.getLeftArg();
    TupleExpr r = node.getRightArg();
    if (!(l instanceof SelectQuery && r instanceof SelectQuery)) {
      return;
    }
    SelectQuery left = (SelectQuery)l;
    SelectQuery right = (SelectQuery)r;
    if (left.isComplex() || right.isComplex()) {
      return;
    }
    Set<String> names = new HashSet<String>(left.getBindingNames());
    names.retainAll(right.getBindingNames());
    if (names.isEmpty()) {
      return;
    }
    left = left.clone();
    right = right.clone();
    filterOn(left, right);
    mergeSelectClause(left, right);
    left.addLeftJoin(right);
    List<SqlExpr> filters = new ArrayList<SqlExpr>();
    if (node.getCondition() != null) {
      for (ValueExpr expr : flatten(node.getCondition())) {
        try {
          filters.add(sql.createBooleanExpr(expr));
        }
        catch (UnsupportedRdbmsOperatorException e) {
          return;
        }
      }
    }
    for (SqlExpr filter : filters) {
      right.addFilter(filter);
    }
    node.replaceWith(left);
  }

  @Override
  public void meet(StatementPattern sp)
    throws RdbmsException
  {
    super.meet(sp);
    Var subjVar = sp.getSubjectVar();
    Var predVar = sp.getPredicateVar();
    Var objVar = sp.getObjectVar();
    Var ctxVar = sp.getContextVar();

    Value subjValue = getVarValue(subjVar, bindings);
    Value predValue = getVarValue(predVar, bindings);
    Value objValue = getVarValue(objVar, bindings);
    Value ctxValue = getVarValue(ctxVar, bindings);

    if (subjValue instanceof Literal || predValue instanceof Literal || predValue instanceof BNode
        || ctxValue instanceof Literal)
    {
      // subj and ctx must be a Resource and pred must be a URI
      return;
    }

    Resource[] contexts = getContexts(sp, ctxValue);
    if (contexts == null) {
      return;
    }

    String alias = getTableAlias(predValue) + aliasCount++;
    Number predId = getInternalId(predValue);
    String tableName;
    boolean present;
    try {
      tableName = tables.getTableName(predId);
      present = tables.isPredColumnPresent(predId);
    }
    catch (SQLException e) {
      throw new RdbmsException(e);
    }
    JoinItem from = new JoinItem(alias, tableName, predId);

    ColumnVar s = createSubj(alias, subjVar, (Resource)subjValue);
    ColumnVar p = createPred(alias, predVar, (URI)predValue, !present);
    ColumnVar o = createObj(alias, objVar, objValue);
    ColumnVar c = createCtx(alias, ctxVar, (Resource)ctxValue);

    s.setTypes(tables.getSubjTypes(predId));
    o.setTypes(tables.getObjTypes(predId));

    SelectQuery query = new SelectQuery();
    query.setFrom(from);
    Map<String, ColumnVar> vars = new HashMap<String, ColumnVar>(4);
    for (ColumnVar var : new ColumnVar[] { s, p, o, c }) {
      from.addVar(var);
      Value value = var.getValue();
      if (vars.containsKey(var.getName())) {
        IdColumn existing = new IdColumn(vars.get(var.getName()));
        from.addFilter(new SqlEq(new IdColumn(var), existing));
      }
      else if (value != null && !var.isImplied()) {
        try {
          NumberValue vc = new NumberValue(vf.getInternalId(value));
          from.addFilter(new SqlEq(new RefIdColumn(var), vc));
        }
        catch (RdbmsException e) {
          throw new RdbmsException(e);
        }
      }
      else {
        vars.put(var.getName(), var);
      }
      if (!var.isHiddenOrConstant() && value == null) {
        SelectProjection proj = new SelectProjection();
        proj.setVar(var);
        proj.setId(new RefIdColumn(var));
        proj.setStringValue(coalesce(new URIColumn(var), new BNodeColumn(var), new LabelColumn(var),
            new LongLabelColumn(var), new LongURIColumn(var)));
        proj.setDatatype(new DatatypeColumn(var));
        proj.setLanguage(new LanguageColumn(var));
        query.addSqlSelectVar(proj);
      }
    }
    if (contexts.length > 0) {
      RdbmsResource[] ids = vf.asRdbmsResource(contexts);
      RefIdColumn var = new RefIdColumn(c);
      SqlExpr in = null;
      for (RdbmsResource id : ids) {
        NumberValue longValue;
        try {
          longValue = new NumberValue(vf.getInternalId(id));
        }
        catch (RdbmsException e) {
          throw new RdbmsException(e);
        }
        SqlEq eq = new SqlEq(var.clone(), longValue);
        if (in == null) {
          in = eq;
        }
        else {
          in = new SqlOr(in, eq);
        }
      }
      from.addFilter(in);
    }
    sp.replaceWith(query);
  }

  @Override
  public void meet(Filter node)
    throws RdbmsException
  {
    super.meet(node);
    if (node.getArg() instanceof SelectQuery) {
      SelectQuery query = (SelectQuery)node.getArg();
      ValueExpr condition = null;
      for (ValueExpr expr : flatten(node.getCondition())) {
        try {
          query.addFilter(sql.createBooleanExpr(expr));
        }
        catch (UnsupportedRdbmsOperatorException e) {
          if (condition == null) {
            condition = expr;
          }
          else {
            condition = new And(condition, expr);
          }
        }
      }
      if (condition == null) {
        node.replaceWith(node.getArg());
      }
      else {
        node.setCondition(condition);
      }
    }
  }

  @Override
  public void meet(Projection node)
    throws RdbmsException
  {
    super.meet(node);
    if (node.getArg() instanceof SelectQuery) {
      SelectQuery query = (SelectQuery)node.getArg();
      Map<String, String> bindingVars = new HashMap<String, String>();
      List<SelectProjection> selection = new ArrayList<SelectProjection>();
      ProjectionElemList list = node.getProjectionElemList();
      for (ProjectionElem e : list.getElements()) {
        String source = e.getSourceName();
        String target = e.getTargetName();
        bindingVars.put(target, source);
        SelectProjection s = query.getSelectProjection(source);
        if (s != null) {
          selection.add(s);
        }
      }
      query.setBindingVars(bindingVars);
      query.setSqlSelectVar(selection);
      node.replaceWith(query);
    }
  }

  @Override
  public void meet(Slice node)
    throws RdbmsException
  {
    super.meet(node);
    if (node.getArg() instanceof SelectQuery) {
      SelectQuery query = (SelectQuery)node.getArg();
      if (node.getOffset() > 0) {
        query.setOffset(node.getOffset());
      }
      if (node.getLimit() >= 0) {
        query.setLimit(node.getLimit());
      }
      node.replaceWith(query);
    }
  }

  @Override
  public void meet(Order node)
    throws RdbmsException
  {
    super.meet(node);
    if (!(node.getArg() instanceof SelectQuery)) {
      return;
    }
    SelectQuery query = (SelectQuery)node.getArg();
    try {
      for (OrderElem e : node.getElements()) {
        ValueExpr expr = e.getExpr();
        boolean asc = e.isAscending();
        query.addOrder(sql.createBNodeExpr(expr), asc);
        query.addOrder(sql.createUriExpr(expr), asc);
        query.addOrder(sql.createNumericExpr(expr), asc);
        query.addOrder(sql.createDatatypeExpr(expr), asc);
        query.addOrder(sql.createTimeExpr(expr), asc);
        query.addOrder(sql.createLanguageExpr(expr), asc);
        query.addOrder(sql.createLabelExpr(expr), asc);
      }
      node.replaceWith(query);
    }
    catch (UnsupportedRdbmsOperatorException e) {
      // unsupported
    }
  }

  private void filterOn(SelectQuery left, SelectQuery right) {
    Map<String, ColumnVar> lvars = left.getVarMap();
    Map<String, ColumnVar> rvars = right.getVarMap();
    Set<String> names = new HashSet<String>(rvars.keySet());
    names.retainAll(lvars.keySet());
    for (String name : names) {
      ColumnVar l = lvars.get(name);
      ColumnVar r = rvars.get(name);
      if (!l.isImplied() && !r.isImplied()) {
        IdColumn rid = new IdColumn(r);
        SqlExpr filter = eq(rid, new IdColumn(l));
        if (r.isNullable()) {
          filter = or(isNull(rid), filter);
        }
        right.addFilter(filter);
      }
    }
  }

  private Number getInternalId(Value predValue)
    throws RdbmsException
  {
    try {
      return vf.getInternalId(predValue);
    }
    catch (RdbmsException e) {
      throw new RdbmsException(e);
    }
  }

  private Resource[] getContexts(StatementPattern sp, Value ctxValue) {
    if (dataset == null) {
      if (ctxValue != null) {
        return new Resource[] { (Resource)ctxValue };
      }
      return new Resource[0];
    }
    Set<URI> graphs = getGraphs(sp);
    if (graphs.isEmpty()) {
      return null; // Search zero contexts
    }
    if (ctxValue == null) {
      return graphs.toArray(new Resource[graphs.size()]);
    }

    if (graphs.contains(ctxValue)) {
      return new Resource[] { (Resource)ctxValue };
    }
    // pattern specifies a context that is not part of the dataset
    return null;
  }

  private Set<URI> getGraphs(StatementPattern sp) {
    if (sp.getScope() == Scope.DEFAULT_CONTEXTS) {
      return dataset.getDefaultGraphs();
    }
    return dataset.getNamedGraphs();
  }

  private String getTableAlias(Value predValue) {
    if (predValue != null) {
      String localName = ((URI)predValue).getLocalName();
      if (localName.length() >= 1) {
        String alias = localName.substring(0, 1);
        if (isLetters(alias)) {
          return alias;
        }
      }
    }
    return ALIAS;
  }

  private Value getVarValue(Var var, BindingSet bindings) {
    if (var == null) {
      return null;
    }
    else if (var.hasValue()) {
      return var.getValue();
    }
    else {
      return bindings.getValue(var.getName());
    }
  }

  private boolean isLetters(String alias) {
    for (int i = 0, n = alias.length(); i < n; i++) {
      if (!Character.isLetter(alias.charAt(i))) {
        return false;
      }
    }
    return true;
  }

  private void mergeSelectClause(SelectQuery left, SelectQuery right) {
    for (SelectProjection proj : right.getSqlSelectVar()) {
      if (!left.hasSqlSelectVar(proj)) {
        proj = proj.clone();
        ColumnVar var = proj.getVar();
        String name = var.getName();
        ColumnVar existing = left.getVar(name);
        if (existing != null) {
          proj.setVar(existing);
        }
        left.addSqlSelectVar(proj);
      }
    }
  }

  private List<ValueExpr> flatten(ValueExpr condition) {
    return flatten(condition, new ArrayList<ValueExpr>());
  }

  private List<ValueExpr> flatten(ValueExpr condition, List<ValueExpr> conditions) {
    if (condition instanceof And) {
      And and = (And)condition;
      for (ValueExpr arg : and.getArgs()) {
        flatten(arg, conditions);
      }
    }
    else {
      conditions.add(condition);
    }
    return conditions;
  }
}
TOP

Related Classes of org.openrdf.sail.rdbms.optimizers.SelectQueryOptimizer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.