blob: f4cbc9c9490ff750a0911725a6aabf4e9434094b [file] [log] [blame]
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;
}
}