| package org.apache.rya.indexing.IndexPlanValidator; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.apache.rya.indexing.external.tupleSet.ExternalTupleSet; |
| import org.apache.rya.indexing.pcj.matching.QueryVariableNormalizer.VarCollector; |
| import org.eclipse.rdf4j.query.algebra.BindingSetAssignment; |
| import org.eclipse.rdf4j.query.algebra.Filter; |
| import org.eclipse.rdf4j.query.algebra.Join; |
| import org.eclipse.rdf4j.query.algebra.Projection; |
| import org.eclipse.rdf4j.query.algebra.QueryModelNode; |
| import org.eclipse.rdf4j.query.algebra.StatementPattern; |
| import org.eclipse.rdf4j.query.algebra.TupleExpr; |
| import org.eclipse.rdf4j.query.algebra.Var; |
| import org.eclipse.rdf4j.query.algebra.helpers.AbstractQueryModelVisitor; |
| import org.eclipse.rdf4j.query.algebra.helpers.StatementPatternCollector; |
| |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| |
| /** |
| * Processes a {@link TupleExpr} and replaces sets of elements in the tree with {@link ExternalTupleSet} objects. |
| */ |
| public class GeneralizedExternalProcessor { |
| |
| |
| /** |
| * Iterates through list of normalized indexes and replaces all subtrees of query which match index with index. |
| * |
| * @param query |
| * @return TupleExpr |
| */ |
| public static TupleExpr process(TupleExpr query, List<ExternalTupleSet> indexSet) { |
| |
| boolean indexPlaced = false; |
| TupleExpr rtn = query.clone(); |
| QueryNodeCount qnc = new QueryNodeCount(); |
| rtn.visit(qnc); |
| |
| if(qnc.getNodeCount()/2 < indexSet.size()) { |
| return null; |
| } |
| |
| |
| //move BindingSetAssignment Nodes out of the way |
| organizeBSAs(rtn); |
| |
| |
| // test to see if query contains no other nodes |
| // than filter, join, projection, and statement pattern and |
| // test whether query contains duplicate StatementPatterns and filters |
| if (isTupleValid(rtn)) { |
| |
| for (ExternalTupleSet index : indexSet) { |
| |
| // test to see if index contains at least one StatementPattern, |
| // that StatementPatterns are unique, |
| // and that all variables found in filters occur in some |
| // StatementPattern |
| if (isTupleValid(index.getTupleExpr())) { |
| |
| ExternalTupleSet eTup = (ExternalTupleSet) index.clone(); |
| SPBubbleDownVisitor indexVistor = new SPBubbleDownVisitor(eTup); |
| rtn.visit(indexVistor); |
| FilterBubbleManager fbmv = new FilterBubbleManager(eTup); |
| rtn.visit(fbmv); |
| SubsetEqualsVisitor subIndexVis = new SubsetEqualsVisitor(eTup, rtn); |
| rtn.visit(subIndexVis); |
| indexPlaced = subIndexVis.indexPlaced(); |
| if(!indexPlaced) { |
| break; |
| } |
| |
| } |
| |
| } |
| if(indexPlaced) { |
| return rtn; |
| } else { |
| return null; |
| } |
| |
| } else { |
| throw new IllegalArgumentException("Invalid Query."); |
| } |
| } |
| |
| |
| |
| |
| |
| // determines whether query is valid, which requires that a |
| // query must contain a StatementPattern, not contain duplicate |
| // Statement Patterns or Filters, not be comprised of only Projection, |
| // Join, StatementPattern, and Filter nodes, and that any variable |
| // appearing in a Filter must appear in a StatementPattern. |
| private static boolean isTupleValid(QueryModelNode node) { |
| |
| ValidQueryVisitor vqv = new ValidQueryVisitor(); |
| node.visit(vqv); |
| |
| Set<String> spVars = getVarNames(getQNodes("sp", node)); |
| |
| if (vqv.isValid() && spVars.size() > 0) { |
| |
| FilterCollector fvis = new FilterCollector(); |
| node.visit(fvis); |
| List<QueryModelNode> fList = fvis.getFilters(); |
| return fList.size() == Sets.newHashSet(fList).size() && getVarNames(fList).size() <= spVars.size(); |
| |
| } else { |
| return false; |
| } |
| } |
| |
| private static Set<QueryModelNode> getQNodes(QueryModelNode queryNode) { |
| Set<QueryModelNode> rtns = new HashSet<QueryModelNode>(); |
| |
| StatementPatternCollector spc = new StatementPatternCollector(); |
| queryNode.visit(spc); |
| rtns.addAll(spc.getStatementPatterns()); |
| |
| FilterCollector fvis = new FilterCollector(); |
| queryNode.visit(fvis); |
| rtns.addAll(fvis.getFilters()); |
| |
| ExternalTupleCollector eVis = new ExternalTupleCollector(); |
| queryNode.visit(eVis); |
| rtns.addAll(eVis.getExtTup()); |
| |
| return rtns; |
| } |
| |
| private static Set<QueryModelNode> getQNodes(String node, QueryModelNode queryNode) { |
| |
| if (node.equals("sp")) { |
| Set<QueryModelNode> eSet = new HashSet<QueryModelNode>(); |
| StatementPatternCollector spc = new StatementPatternCollector(); |
| queryNode.visit(spc); |
| List<StatementPattern> spList = spc.getStatementPatterns(); |
| eSet.addAll(spList); |
| // returns empty set if list contains duplicate StatementPatterns |
| if (spList.size() > eSet.size()) { |
| return Sets.newHashSet(); |
| } else { |
| return eSet; |
| } |
| } else if (node.equals("filter")) { |
| |
| FilterCollector fvis = new FilterCollector(); |
| queryNode.visit(fvis); |
| |
| return Sets.newHashSet(fvis.getFilters()); |
| } else { |
| |
| throw new IllegalArgumentException("Invalid node type."); |
| } |
| } |
| |
| // moves StatementPatterns in query that also occur in index to bottom of |
| // query tree. |
| private static class SPBubbleDownVisitor extends AbstractQueryModelVisitor<RuntimeException> { |
| |
| private TupleExpr tuple; |
| private QueryModelNode indexQNode; |
| private Set<QueryModelNode> sSet = Sets.newHashSet(); |
| |
| public SPBubbleDownVisitor(ExternalTupleSet index) { |
| |
| this.tuple = index.getTupleExpr(); |
| indexQNode = ((Projection) tuple).getArg(); |
| sSet = getQNodes("sp", indexQNode); |
| |
| } |
| |
| @Override |
| public void meet(Projection node) { |
| // moves external tuples above statement patterns before attempting |
| // to bubble down index statement patterns found in query tree |
| |
| organizeExtTuples(node); |
| super.meet(node); |
| } |
| |
| @Override |
| public void meet(Join node) { |
| // if right node contained in index, move it to bottom of query tree |
| if (sSet.contains(node.getRightArg())) { |
| |
| Set<QueryModelNode> eSet = getQNodes("sp", node); |
| Set<QueryModelNode> compSet = Sets.difference(eSet, sSet); |
| |
| if (eSet.containsAll(sSet)) { |
| QNodeExchanger qne = new QNodeExchanger(node.getRightArg(), compSet); |
| node.visit(qne); |
| node.replaceChildNode(node.getRightArg(), qne.getReplaced()); |
| |
| super.meet(node); |
| } |
| return; |
| } |
| // if left node contained in index, move it to bottom of query tree |
| else if (sSet.contains(node.getLeftArg())) { |
| |
| Set<QueryModelNode> eSet = getQNodes("sp", node); |
| Set<QueryModelNode> compSet = Sets.difference(eSet, sSet); |
| |
| if (eSet.containsAll(sSet)) { |
| |
| QNodeExchanger qne = new QNodeExchanger(node.getLeftArg(), compSet); |
| node.visit(qne); |
| node.replaceChildNode(node.getLeftArg(), qne.getReplaced()); |
| |
| super.meet(node); |
| } |
| return; |
| |
| } else { |
| super.meet(node); |
| } |
| |
| } |
| |
| // moves all ExternalTupleSets in query tree above remaining |
| // StatementPatterns |
| private static void organizeExtTuples(QueryModelNode node) { |
| |
| ExternalTupleCollector eVis = new ExternalTupleCollector(); |
| node.visit(eVis); |
| |
| ExtTupleExchangeVisitor oev = new ExtTupleExchangeVisitor(eVis.getExtTup()); |
| node.visit(oev); |
| } |
| |
| } |
| |
| // given a replacement QueryModelNode and compSet, this visitor replaces the |
| // first |
| // element in the query tree that occurs in compSet with replacement and |
| // returns |
| // the element that was replaced. |
| private static class QNodeExchanger extends AbstractQueryModelVisitor<RuntimeException> { |
| |
| private QueryModelNode toBeReplaced; |
| private QueryModelNode replacement; |
| private Set<QueryModelNode> compSet; |
| |
| public QNodeExchanger(QueryModelNode replacement, Set<QueryModelNode> compSet) { |
| this.replacement = replacement; |
| this.toBeReplaced = replacement; |
| this.compSet = compSet; |
| } |
| |
| public QueryModelNode getReplaced() { |
| return toBeReplaced; |
| } |
| |
| @Override |
| public void meet(Join node) { |
| |
| if (compSet.contains(node.getRightArg())) { |
| this.toBeReplaced = node.getRightArg(); |
| node.replaceChildNode(node.getRightArg(), replacement); |
| return; |
| } else if (compSet.contains(node.getLeftArg())) { |
| this.toBeReplaced = node.getLeftArg(); |
| node.replaceChildNode(node.getLeftArg(), replacement); |
| return; |
| } else { |
| super.meet(node); |
| } |
| |
| } |
| |
| } |
| |
| // moves filter that occurs in both query and index down the query tree so |
| // that that it is positioned |
| // above statement patterns associated with index. Precondition for calling |
| // this method is that |
| // SPBubbleDownVisitor has been called to position index StatementPatterns |
| // within query tree. |
| //could lead to problems if filter optimizer called before external processor |
| private static class FilterBubbleDownVisitor extends AbstractQueryModelVisitor<RuntimeException> { |
| |
| private QueryModelNode filter; |
| private Set<QueryModelNode> compSet; |
| private boolean filterPlaced = false; |
| |
| public FilterBubbleDownVisitor(QueryModelNode filter, Set<QueryModelNode> compSet) { |
| this.filter = filter; |
| this.compSet = compSet; |
| |
| } |
| |
| public boolean filterPlaced() { |
| return filterPlaced; |
| } |
| |
| @Override |
| public void meet(Join node) { |
| |
| if (!compSet.contains(node.getRightArg())) { |
| // looks for placed to position filter node. if right node is |
| // contained in index |
| // and left node is statement pattern node contained in index or |
| // is a join, place |
| // filter above join. |
| if (node.getLeftArg() instanceof Join || !compSet.contains(node.getLeftArg())) { |
| |
| QueryModelNode pNode = node.getParentNode(); |
| ((Filter) filter).setArg(node); |
| pNode.replaceChildNode(node, filter); |
| filterPlaced = true; |
| |
| return; |
| } // otherwise place filter below join and above right arg |
| else { |
| ((Filter) filter).setArg(node.getRightArg()); |
| node.replaceChildNode(node.getRightArg(), filter); |
| filterPlaced = true; |
| return; |
| |
| } |
| } else if (node.getLeftArg() instanceof StatementPattern && !compSet.contains(node.getLeftArg())) { |
| |
| ((Filter) filter).setArg(node.getLeftArg()); |
| node.replaceChildNode(node.getLeftArg(), filter); |
| filterPlaced = true; |
| |
| return; |
| } else { |
| super.meet(node); |
| } |
| } |
| |
| } |
| |
| private static Set<String> getVarNames(Collection<QueryModelNode> nodes) { |
| |
| List<String> tempVars; |
| Set<String> nodeVarNames = Sets.newHashSet(); |
| |
| for (QueryModelNode s : nodes) { |
| tempVars = VarCollector.process(s); |
| for (String t : tempVars) { |
| nodeVarNames.add(t); |
| } |
| } |
| return nodeVarNames; |
| |
| } |
| |
| // visitor which determines whether or not to reposition a filter by calling |
| // FilterBubbleDownVisitor |
| private static class FilterBubbleManager extends AbstractQueryModelVisitor<RuntimeException> { |
| |
| private TupleExpr tuple; |
| private QueryModelNode indexQNode; |
| private Set<QueryModelNode> sSet = Sets.newHashSet(); |
| private Set<QueryModelNode> bubbledFilters = Sets.newHashSet(); |
| |
| public FilterBubbleManager(ExternalTupleSet index) { |
| this.tuple = index.getTupleExpr(); |
| indexQNode = ((Projection) tuple).getArg(); |
| sSet = getQNodes(indexQNode); |
| |
| } |
| |
| @Override |
| public void meet(Filter node) { |
| |
| Set<QueryModelNode> eSet = getQNodes(node); |
| Set<QueryModelNode> compSet = Sets.difference(eSet, sSet); |
| |
| // if index contains filter node and it hasn't already been moved, |
| // move it down |
| // query tree just above position of statement pattern nodes found |
| // in both query tree |
| // and index (assuming that SPBubbleDownVisitor has already been |
| // called) |
| if (sSet.contains(node.getCondition()) && !bubbledFilters.contains(node.getCondition())) { |
| FilterBubbleDownVisitor fbdv = new FilterBubbleDownVisitor(node.clone(), compSet); |
| node.visit(fbdv); |
| bubbledFilters.add(node.getCondition()); |
| // checks if filter correctly placed, and if it has been, |
| // removes old copy of filter |
| if (fbdv.filterPlaced()) { |
| |
| QueryModelNode pNode = node.getParentNode(); |
| TupleExpr cNode = node.getArg(); |
| pNode.replaceChildNode(node, cNode); |
| |
| |
| super.meetNode(pNode); |
| } |
| super.meet(node); |
| |
| } else { |
| super.meet(node); |
| } |
| } |
| } |
| |
| // iterates through the query tree and attempts to match subtrees with |
| // index. When a match is |
| // found, the subtree is replaced by an ExternalTupleSet formed from the |
| // index. Pre-condition for |
| // calling this method is that both SPBubbleDownVisitor and |
| // FilterBubbleManager have been called |
| // to position the StatementPatterns and Filters. |
| private static class SubsetEqualsVisitor extends AbstractQueryModelVisitor<RuntimeException> { |
| |
| private TupleExpr tuple; |
| private QueryModelNode indexQNode; |
| private ExternalTupleSet set; |
| private Set<QueryModelNode> sSet = Sets.newHashSet(); |
| private boolean indexPlaced = false; |
| |
| |
| public SubsetEqualsVisitor(ExternalTupleSet index, TupleExpr query) { |
| this.tuple = index.getTupleExpr(); |
| this.set = index; |
| indexQNode = ((Projection) tuple).getArg(); |
| sSet = getQNodes(indexQNode); |
| |
| } |
| |
| public boolean indexPlaced() { |
| return indexPlaced; |
| } |
| |
| |
| @Override |
| public void meet(Join node) { |
| |
| Set<QueryModelNode> eSet = getQNodes(node); |
| |
| if (eSet.containsAll(sSet) && !(node.getRightArg() instanceof BindingSetAssignment)) { |
| |
| // System.out.println("Eset is " + eSet + " and sSet is " + sSet); |
| |
| if (eSet.equals(sSet)) { |
| node.replaceWith(set); |
| indexPlaced = true; |
| return; |
| } else { |
| if (node.getLeftArg() instanceof StatementPattern && sSet.size() == 1) { |
| if(sSet.contains(node.getLeftArg())) { |
| node.setLeftArg(set); |
| indexPlaced = true; |
| } else if(sSet.contains(node.getRightArg())) { |
| node.setRightArg(set); |
| indexPlaced = true; |
| } else { |
| return; |
| } |
| } |
| else { |
| super.meet(node); |
| } |
| } |
| } else if (eSet.containsAll(sSet)) { |
| |
| super.meet(node); |
| |
| } else { |
| return; |
| } |
| |
| } |
| //to account for index consisting of only filter and BindingSetAssignment nodes |
| @Override |
| public void meet(Filter node) { |
| |
| Set<QueryModelNode> eSet = getQNodes(node); |
| |
| if (eSet.containsAll(sSet)) { |
| |
| if (eSet.equals(sSet)) { |
| node.replaceWith(set); |
| indexPlaced = true; |
| return; |
| } else { |
| node.getArg().visit(this); |
| } |
| } |
| } |
| |
| |
| @Override |
| public void meet(StatementPattern node) { |
| return; |
| } |
| } |
| |
| // visitor which determines whether a query is valid (i.e. it does not |
| // contain nodes other than |
| // Projection, Join, Filter, StatementPattern ) |
| private static class ValidQueryVisitor extends AbstractQueryModelVisitor<RuntimeException> { |
| |
| private boolean isValid = true; |
| |
| public boolean isValid() { |
| return isValid; |
| } |
| |
| @Override |
| public void meet(Projection node) { |
| node.getArg().visit(this); |
| } |
| |
| @Override |
| public void meet(Filter node) { |
| node.getArg().visit(this); |
| } |
| |
| |
| |
| |
| |
| @Override |
| public void meetNode(QueryModelNode node) { |
| |
| if (!(node instanceof Join || node instanceof StatementPattern || node instanceof BindingSetAssignment || node instanceof Var)) { |
| isValid = false; |
| return; |
| |
| } else{ |
| super.meetNode(node); |
| } |
| } |
| |
| } |
| |
| // repositions ExternalTuples above StatementPatterns within query tree |
| private static class ExtTupleExchangeVisitor extends AbstractQueryModelVisitor<RuntimeException> { |
| |
| private Set<QueryModelNode> extTuples; |
| |
| public ExtTupleExchangeVisitor(Set<QueryModelNode> extTuples) { |
| this.extTuples = extTuples; |
| } |
| |
| @Override |
| public void meet(Join queryNode) { |
| |
| // if query tree contains external tuples and they are not |
| // positioned above statement pattern node |
| // reposition |
| if (this.extTuples.size() > 0 && !(queryNode.getRightArg() instanceof ExternalTupleSet) |
| && !(queryNode.getRightArg() instanceof BindingSetAssignment)) { |
| |
| if (queryNode.getLeftArg() instanceof ExternalTupleSet) { |
| QueryModelNode temp = queryNode.getLeftArg(); |
| queryNode.setLeftArg(queryNode.getRightArg()); |
| queryNode.setRightArg((TupleExpr)temp); |
| } else { |
| |
| QNodeExchanger qnev = new QNodeExchanger(queryNode.getRightArg(), this.extTuples); |
| queryNode.visit(qnev); |
| queryNode.replaceChildNode(queryNode.getRightArg(), qnev.getReplaced()); |
| super.meet(queryNode); |
| } |
| } else { |
| super.meet(queryNode); |
| } |
| |
| } |
| |
| } |
| |
| private static class ExternalTupleCollector extends AbstractQueryModelVisitor<RuntimeException> { |
| |
| private Set<QueryModelNode> eSet = new HashSet<QueryModelNode>(); |
| |
| @Override |
| public void meetNode(QueryModelNode node) throws RuntimeException { |
| if (node instanceof ExternalTupleSet) { |
| eSet.add(node); |
| } |
| super.meetNode(node); |
| } |
| |
| public Set<QueryModelNode> getExtTup() { |
| return eSet; |
| } |
| |
| } |
| |
| private static class FilterCollector extends AbstractQueryModelVisitor<RuntimeException> { |
| |
| private List<QueryModelNode> filterList = Lists.newArrayList(); |
| |
| public List<QueryModelNode> getFilters() { |
| return filterList; |
| } |
| |
| @Override |
| public void meet(Filter node) { |
| filterList.add(node.getCondition()); |
| super.meet(node); |
| } |
| |
| } |
| |
| private static void organizeBSAs(QueryModelNode node) { |
| |
| BindingSetAssignmentCollector bsac = new BindingSetAssignmentCollector(); |
| node.visit(bsac); |
| |
| if (bsac.containsBSAs()) { |
| Set<QueryModelNode> bsaSet = bsac.getBindingSetAssignments(); |
| BindingSetAssignmentExchangeVisitor bsaev = new BindingSetAssignmentExchangeVisitor(bsaSet); |
| node.visit(bsaev); |
| } |
| } |
| |
| // repositions ExternalTuples above StatementPatterns within query tree |
| private static class BindingSetAssignmentExchangeVisitor extends AbstractQueryModelVisitor<RuntimeException> { |
| |
| private Set<QueryModelNode> bsas; |
| |
| public BindingSetAssignmentExchangeVisitor(Set<QueryModelNode> bsas) { |
| this.bsas = bsas; |
| } |
| |
| @Override |
| public void meet(Join queryNode) { |
| |
| // if query tree contains external tuples and they are not |
| // positioned above statement pattern node |
| // reposition |
| if (this.bsas.size() > 0 && !(queryNode.getRightArg() instanceof BindingSetAssignment)) { |
| QNodeExchanger qnev = new QNodeExchanger(queryNode.getRightArg(), bsas); |
| queryNode.visit(qnev); |
| queryNode.replaceChildNode(queryNode.getRightArg(), qnev.getReplaced()); |
| super.meet(queryNode); |
| } else { |
| super.meet(queryNode); |
| } |
| |
| } |
| |
| } |
| |
| |
| public static class BindingSetAssignmentCollector extends AbstractQueryModelVisitor<RuntimeException> { |
| |
| private Set<QueryModelNode> bindingSetList = Sets.newHashSet(); |
| |
| public Set<QueryModelNode> getBindingSetAssignments() { |
| return bindingSetList; |
| } |
| |
| public boolean containsBSAs() { |
| return bindingSetList.size() > 0; |
| } |
| |
| @Override |
| public void meet(BindingSetAssignment node) { |
| bindingSetList.add(node); |
| super.meet(node); |
| } |
| |
| } |
| |
| |
| |
| public static class QueryNodeCount extends AbstractQueryModelVisitor<RuntimeException> { |
| |
| private int nodeCount; |
| |
| public QueryNodeCount() { |
| nodeCount = 0; |
| } |
| |
| public int getNodeCount() { |
| return nodeCount; |
| } |
| |
| |
| @Override |
| public void meet(StatementPattern node) { |
| nodeCount += 1; |
| return; |
| } |
| |
| @Override |
| public void meet(Filter node) { |
| nodeCount += 1; |
| node.getArg().visit(this); |
| } |
| |
| } |
| |
| |
| |
| } |