| 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.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| |
| import org.apache.rya.indexing.external.tupleSet.ExternalTupleSet; |
| import org.apache.rya.rdftriplestore.inference.DoNotExpandSP; |
| import org.apache.rya.rdftriplestore.utils.FixedStatementPattern; |
| import org.eclipse.rdf4j.query.algebra.Filter; |
| import org.eclipse.rdf4j.query.algebra.Join; |
| import org.eclipse.rdf4j.query.algebra.StatementPattern; |
| import org.eclipse.rdf4j.query.algebra.TupleExpr; |
| import org.eclipse.rdf4j.query.algebra.helpers.AbstractQueryModelVisitor; |
| |
| import com.google.common.collect.Collections2; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| |
| /** |
| * A given TupleExpr can be broken up into "join segments", which are sections of the TupleExpr where nodes can |
| * be freely exchanged. This class creates a list of permuted TupleExpr from a specified TupleExpr by permuting the nodes |
| * in each join segment. |
| */ |
| public class TupleReArranger { |
| |
| private static Map<Join, List<List<TupleExpr>>> joinArgs; |
| private static Map<Join, List<Filter>> filterArgs; |
| |
| |
| public static Iterator<TupleExpr> getPlans(Iterator<TupleExpr> indexPlans) { |
| |
| final Iterator<TupleExpr> iter = indexPlans; |
| |
| return new Iterator<TupleExpr>() { |
| |
| private TupleExpr next = null; |
| private boolean hasNextCalled = false; |
| private boolean isEmpty = false; |
| Iterator<TupleExpr> tuples = null; |
| |
| @Override |
| public boolean hasNext() { |
| |
| if (!hasNextCalled && !isEmpty) { |
| if (tuples != null && tuples.hasNext()) { |
| next = tuples.next(); |
| hasNextCalled = true; |
| return true; |
| } else { |
| while (iter.hasNext()) { |
| tuples = getTupleReOrderings(iter.next()).iterator(); |
| if (tuples == null) { |
| throw new IllegalStateException("Plans cannot be null!"); |
| } |
| next = tuples.next(); |
| hasNextCalled = true; |
| return true; |
| } |
| isEmpty = true; |
| return false; |
| } |
| } else { |
| return !isEmpty; |
| } |
| } |
| |
| @Override |
| public TupleExpr next() { |
| |
| if (hasNextCalled) { |
| hasNextCalled = false; |
| return next; |
| } else if (isEmpty) { |
| throw new NoSuchElementException(); |
| } else { |
| if (this.hasNext()) { |
| hasNextCalled = false; |
| return next; |
| } else { |
| throw new NoSuchElementException(); |
| } |
| } |
| } |
| |
| @Override |
| public void remove() { |
| throw new UnsupportedOperationException("Cannot delete from iterator!"); |
| } |
| }; |
| } |
| |
| |
| //Give a TupleExpr, return list of join segment permuted TupleExpr |
| public static List<TupleExpr> getTupleReOrderings(TupleExpr te) { |
| |
| joinArgs = Maps.newHashMap(); |
| filterArgs = Maps.newHashMap(); |
| |
| NodeCollector nc = new NodeCollector(); |
| te.visit(nc); |
| joinArgs = nc.getPerms(); |
| List<Join> joins = Lists.newArrayList(joinArgs.keySet()); |
| |
| return getPlans(getReOrderings(joins), te); |
| |
| } |
| |
| |
| //iterates through the reOrder maps, and for each reOrder map builds a new, reordered tupleExpr |
| private static List<TupleExpr> getPlans(List<Map<Join, List<TupleExpr>>> reOrderings, TupleExpr te) { |
| |
| List<TupleExpr> queryPlans = Lists.newArrayList(); |
| PermInserter pm = new PermInserter(); |
| |
| for (Map<Join, List<TupleExpr>> order : reOrderings) { |
| TupleExpr clone = te.clone(); |
| pm.setReOrderMap(order); |
| clone.visit(pm); |
| queryPlans.add(clone); |
| } |
| |
| return queryPlans; |
| } |
| |
| |
| |
| //recursive method which produces a list of maps. Each map associates a join with |
| //a list of the non-join arguments below it contained in same join segment. The list |
| //represents an ordering of the |
| //non-join arguments and creating a TupleExpr from this map yields a new TupleExpr |
| //whose non-join arguments are permuted |
| private static List<Map<Join, List<TupleExpr>>> getReOrderings(List<Join> joins) { |
| Map<Join, List<TupleExpr>> reOrder = Maps.newHashMap(); |
| List<Map<Join, List<TupleExpr>>> reOrderings = Lists.newArrayList(); |
| getReOrderings(joins, reOrder, reOrderings); |
| return reOrderings; |
| |
| } |
| |
| private static void getReOrderings(List<Join> joins, Map<Join, List<TupleExpr>> reOrder, |
| List<Map<Join, List<TupleExpr>>> reOrderings) { |
| |
| if (joins.isEmpty()) { |
| reOrderings.add(reOrder); |
| return; |
| } |
| |
| List<Join> joinsCopy = Lists.newArrayList(joins); |
| Join join = joinsCopy.remove(0); |
| List<List<TupleExpr>> joinArgPerms = joinArgs.get(join); |
| for (List<TupleExpr> tupList : joinArgPerms) { |
| Map<Join, List<TupleExpr>> newReOrder = Maps.newHashMap(reOrder); |
| newReOrder.put(join, tupList); |
| getReOrderings(joinsCopy, newReOrder, reOrderings); |
| } |
| |
| return; |
| |
| } |
| |
| |
| //creates a map which associates each first join of a TupleExpr join segment with all permutations of |
| //the non-join nodes after it. More specifically, each join is associated with a list of TupleExpr |
| //lists, where each list represents an ordering of the non-join nodes following the associated join |
| private static class NodeCollector extends AbstractQueryModelVisitor<RuntimeException> { |
| |
| private static List<Filter> filterList; |
| |
| public Map<Join, List<List<TupleExpr>>> getPerms() { |
| return joinArgs; |
| } |
| |
| @Override |
| public void meet(Join node) { |
| |
| filterList = Lists.newArrayList(); |
| |
| List<TupleExpr> args = Lists.newArrayList(); |
| args = getJoinArgs(node, args); |
| List<List<TupleExpr>> argPerms = Lists.newArrayList(Collections2.permutations(args)); |
| joinArgs.put(node, argPerms); |
| filterArgs.put(node, filterList); |
| |
| for (TupleExpr te : args) { |
| if (!(te instanceof StatementPattern) && !(te instanceof ExternalTupleSet)) { |
| te.visit(this); |
| } |
| } |
| |
| } |
| |
| |
| //get all non-join nodes below tupleExpr in same join segment |
| private static List<TupleExpr> getJoinArgs(TupleExpr tupleExpr, List<TupleExpr> joinArgs) { |
| if (tupleExpr instanceof Join) { |
| if (!(((Join) tupleExpr).getLeftArg() instanceof FixedStatementPattern) |
| && !(((Join) tupleExpr).getRightArg() instanceof DoNotExpandSP)) { |
| Join join = (Join) tupleExpr; |
| getJoinArgs(join.getLeftArg(), joinArgs); |
| getJoinArgs(join.getRightArg(), joinArgs); |
| } // assumes all filter occur above first join of segment -- |
| // this should be the state |
| // after PrecompJoinOptimizer is called |
| } else if (tupleExpr instanceof Filter) { |
| filterList.add((Filter) tupleExpr); |
| getJoinArgs(((Filter) tupleExpr).getArg(), joinArgs); |
| } else { |
| joinArgs.add(tupleExpr); |
| } |
| |
| return joinArgs; |
| } |
| |
| } |
| |
| |
| |
| //for a given reOrder map, searches through TupleExpr and places each reordered collection |
| //of nodes at appropriate join |
| private static class PermInserter extends AbstractQueryModelVisitor<RuntimeException> { |
| |
| private Map<Join, List<TupleExpr>> reOrderMap = Maps.newHashMap(); |
| |
| public void setReOrderMap(Map<Join, List<TupleExpr>> reOrderMap) { |
| this.reOrderMap = reOrderMap; |
| } |
| |
| @Override |
| public void meet(Join node) { |
| |
| List<TupleExpr> reOrder = reOrderMap.get(node); |
| if (reOrder != null) { |
| List<Filter> filterList = Lists.newArrayList(filterArgs.get(node)); |
| node.replaceWith(getNewJoin(reOrder, getFilterChain(filterList))); |
| |
| for (TupleExpr te : reOrder) { |
| if (!(te instanceof StatementPattern) && !(te instanceof ExternalTupleSet)) { |
| te.visit(this); |
| } |
| } |
| } |
| super.meet(node); |
| } |
| } |
| |
| |
| // chain filters together and return front and back of chain |
| private static List<TupleExpr> getFilterChain(List<Filter> filters) { |
| List<TupleExpr> filterTopBottom = Lists.newArrayList(); |
| Filter filterChainTop = null; |
| Filter filterChainBottom = null; |
| |
| for (Filter filter : filters) { |
| if (filterChainTop == null) { |
| filterChainTop = filter.clone(); |
| } else if (filterChainBottom == null) { |
| filterChainBottom = filter.clone(); |
| filterChainTop.setArg(filterChainBottom); |
| } else { |
| Filter newFilter = filter.clone(); |
| filterChainBottom.setArg(newFilter); |
| filterChainBottom = newFilter; |
| } |
| } |
| if (filterChainTop != null) { |
| filterTopBottom.add(filterChainTop); |
| } |
| if (filterChainBottom != null) { |
| filterTopBottom.add(filterChainBottom); |
| } |
| return filterTopBottom; |
| } |
| |
| // build newJoin node given remaining joinArgs and chain of filters |
| private static TupleExpr getNewJoin(List<TupleExpr> args, List<TupleExpr> filterChain) { |
| TupleExpr newJoin; |
| List<TupleExpr> joinArgs = Lists.newArrayList(args); |
| |
| if (joinArgs.size() > 1) { |
| if (filterChain.size() > 0) { |
| TupleExpr finalJoinArg = joinArgs.remove(0).clone(); |
| TupleExpr tempJoin; |
| TupleExpr temp = filterChain.get(0); |
| |
| if (joinArgs.size() > 1) { |
| tempJoin = new Join(joinArgs.remove(0).clone(), joinArgs.remove(0).clone()); |
| for (TupleExpr te : joinArgs) { |
| tempJoin = new Join(tempJoin, te.clone()); |
| } |
| } else { |
| tempJoin = joinArgs.remove(0).clone(); |
| } |
| |
| if (filterChain.size() == 1) { |
| ((Filter) temp).setArg(tempJoin); |
| } else { |
| ((Filter) filterChain.get(1)).setArg(tempJoin); |
| } |
| newJoin = new Join(temp, finalJoinArg); |
| } else { |
| newJoin = new Join(joinArgs.remove(0).clone(), joinArgs.remove(0).clone()); |
| |
| for (TupleExpr te : joinArgs) { |
| newJoin = new Join(newJoin, te.clone()); |
| } |
| } |
| } else if (joinArgs.size() == 1) { |
| if (filterChain.size() > 0) { |
| newJoin = filterChain.get(0); |
| if (filterChain.size() == 1) { |
| ((Filter) newJoin).setArg(joinArgs.get(0).clone()); |
| } else { |
| ((Filter) filterChain.get(1)).setArg(joinArgs.get(0).clone()); |
| } |
| } else { |
| newJoin = joinArgs.get(0).clone(); |
| } |
| } else { |
| throw new IllegalStateException("JoinArgs size cannot be zero."); |
| } |
| return newJoin; |
| } |
| |
| } |