package org.wonderdb.query.plan;
/*******************************************************************************
* Copyright 2013 Vilas Athavale
*
* Licensed 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.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import org.wonderdb.cluster.Shard;
import org.wonderdb.core.collection.WonderDBList;
import org.wonderdb.expression.AndExpression;
import org.wonderdb.expression.BasicExpression;
import org.wonderdb.expression.Expression;
import org.wonderdb.expression.Operand;
import org.wonderdb.expression.VariableOperand;
import org.wonderdb.query.parse.CollectionAlias;
import org.wonderdb.query.parse.StaticOperand;
import org.wonderdb.schema.CollectionMetadata;
import org.wonderdb.schema.SchemaMetadata;
import org.wonderdb.types.IndexNameMeta;
public class QueryPlanBuilder {
private static QueryPlanBuilder builder = new QueryPlanBuilder();
private QueryPlanBuilder() {
}
public static QueryPlanBuilder getInstance() {
return builder;
}
public List<QueryPlan> build(List<CollectionAlias> collectionNames, Shard shard, AndExpression exp, Set<Object> pinnedBlocks) {
List<QueryPlan> qPlan = new ArrayList<QueryPlan>();
if (exp == null || exp.getExpList() == null || exp.getExpList().size() == 0) {
for (int i = 0; i < collectionNames.size(); i++) {
CollectionMetadata colMeta = SchemaMetadata.getInstance().getCollectionMetadata(collectionNames.get(i).getCollectionName());
WonderDBList dbList = colMeta.getRecordList(new Shard(""));
qPlan.add(new FullTableScan(dbList, collectionNames.get(i), pinnedBlocks));
}
return qPlan;
}
List<BasicExpression> list1 = exp.getExpList();
List<BasicExpression> list = filterNonIndexable(list1);
if (list == null || list.size() == 0 ) {
for (int i = 0; i < collectionNames.size(); i++) {
CollectionMetadata colMeta = SchemaMetadata.getInstance().getCollectionMetadata(collectionNames.get(i).getCollectionName());
WonderDBList dbList = colMeta.getRecordList(new Shard(""));
qPlan.add(new FullTableScan(dbList, collectionNames.get(i), pinnedBlocks));
}
}
Set<CollectionAlias> reducedCollections = new HashSet<CollectionAlias>();
Map<CollectionAlias, List<BasicExpression>> expListByCollection = separateExpressionsByCollection(list);
while (reducedCollections.size() < collectionNames.size()) {
List<BasicExpression> staticExpressions = getStaticExpressions(list, reducedCollections);
Map<CollectionAlias, Set<Integer>> staticCollectionsMap = separateColumnsByCollections(staticExpressions);
Map<CollectionAlias, IndexNameMeta> bestStaticCollectionsIndex = getBestMatchIndex(staticCollectionsMap, reducedCollections);
if (bestStaticCollectionsIndex.size() == 0) {
for (int i = 0; i < collectionNames.size(); i++) {
CollectionAlias ca = collectionNames.get(i);
if (!reducedCollections.contains(ca)) {
CollectionMetadata colMeta = SchemaMetadata.getInstance().getCollectionMetadata(ca.getCollectionName());
WonderDBList dbList = colMeta.getRecordList(new Shard(""));
reducedCollections.add(ca);
qPlan.add(new FullTableScan(dbList, ca, pinnedBlocks));
break;
}
}
} else {
Iterator<CollectionAlias> iter = bestStaticCollectionsIndex.keySet().iterator();
while (iter.hasNext()) {
CollectionAlias ca = iter.next();
if (!reducedCollections.contains(ca)) {
qPlan.add(new IndexRangeScan(ca, bestStaticCollectionsIndex.get(ca), shard, expListByCollection.get(ca), null, pinnedBlocks));
reducedCollections.add(ca);
}
}
}
}
return qPlan;
}
private Map<CollectionAlias, List<BasicExpression>> separateExpressionsByCollection(List<BasicExpression> list) {
Map<CollectionAlias, List<BasicExpression>> retVal = new HashMap<CollectionAlias, List<BasicExpression>>();
for (int i = 0; i < list.size(); i++) {
BasicExpression exp = list.get(i);
Operand left = exp.getLeftOperand();
Operand right = exp.getRightOperand();
if (left instanceof VariableOperand) {
VariableOperand vo = (VariableOperand) left;
List<BasicExpression> l = retVal.get(vo.getCollectionAlias());
if (l == null) {
l = new ArrayList<BasicExpression>();
retVal.put(vo.getCollectionAlias(), l);
}
l.add(exp);
}
if (right instanceof VariableOperand) {
VariableOperand vo = (VariableOperand) right;
List<BasicExpression> l = retVal.get(vo.getCollectionAlias());
if (l == null) {
l = new ArrayList<BasicExpression>();
retVal.put(vo.getCollectionAlias(), l);
}
l.add(exp);
}
}
return retVal;
}
private List<BasicExpression> getStaticExpressions(List<BasicExpression> list, Set<CollectionAlias> reducedCollections) {
List<BasicExpression> retList = new ArrayList<BasicExpression>();
for (int i = 0; i < list.size(); i++) {
BasicExpression exp = list.get(i);
if (exp.getLeftOperand() instanceof StaticOperand || exp.getRightOperand() instanceof StaticOperand) {
retList.add(exp);
continue;
}
if (exp.getLeftOperand() instanceof VariableOperand) {
VariableOperand vo = (VariableOperand) exp.getLeftOperand();
if (reducedCollections.contains(vo.getCollectionAlias())) {
retList.add(exp);
continue;
}
}
if (exp.getRightOperand() instanceof VariableOperand) {
VariableOperand vo = (VariableOperand) exp.getRightOperand();
if (reducedCollections.contains(vo.getCollectionAlias())) {
retList.add(exp);
continue;
}
}
}
return retList;
}
@SuppressWarnings("unused")
private List<QueryPlan> orderPlans(List<QueryPlan> plans, List<BasicExpression> expList) {
List<QueryPlan> finalOrder = new ArrayList<QueryPlan>();
Set<CollectionAlias> availableCollections = new HashSet<CollectionAlias>();
while (true) {
if (plans.size() == 0) {
break;
}
QueryPlan p = plans.remove(0);
if (p instanceof FullTableScan) {
finalOrder.add(p);
availableCollections.add(p.getCollectionAlias());
continue;
}
IndexRangeScan irc = null;
if (p instanceof IndexRangeScan) {
irc = (IndexRangeScan) p;
}
if (isReducible(irc, expList, availableCollections)) {
finalOrder.add(p);
availableCollections.add(p.getCollectionAlias());
} else {
plans.add(p);
}
}
return finalOrder;
}
private boolean isReducible(IndexRangeScan p, List<BasicExpression> expList, Set<CollectionAlias> availableCollections) {
IndexNameMeta idx = p.getIndex();
List<Integer> indexCols = idx.getColumnIdList();
CollectionAlias ca = p.getCollectionAlias();
for (int i = 0; i < expList.size(); i++) {
BasicExpression exp = expList.get(i);
Operand l = exp.getLeftOperand();
Operand r = exp.getRightOperand();
if (l instanceof VariableOperand) {
VariableOperand left = (VariableOperand) l;
if (left.getCollectionAlias().equals(ca)) {
if (left.getColumnId().equals(indexCols.get(0))) {
if (r instanceof StaticOperand) {
return true;
}
if (r instanceof VariableOperand) {
VariableOperand right = (VariableOperand) r;
if (availableCollections.contains(right.getCollectionAlias())) {
return true;
}
}
}
}
}
if (l instanceof StaticOperand) {
if (r instanceof StaticOperand) {
continue;
}
if (r instanceof VariableOperand) {
VariableOperand right = (VariableOperand) r;
if (right.getCollectionAlias().equals(ca)) {
return true;
}
}
}
if (r instanceof VariableOperand) {
VariableOperand right = (VariableOperand) r;
if (right.getCollectionAlias().equals(ca)) {
if (right.getColumnId().equals(indexCols.get(0))) {
if (l instanceof StaticOperand) {
return true;
}
if (l instanceof VariableOperand) {
VariableOperand left = (VariableOperand) l;
if (availableCollections.contains(left.getCollectionAlias())) {
return true;
}
}
}
}
}
if (r instanceof StaticOperand) {
if (l instanceof StaticOperand) {
continue;
}
if (l instanceof VariableOperand) {
VariableOperand left = (VariableOperand) l;
if (left.getCollectionAlias().equals(ca)) {
return true;
}
}
}
}
return false;
}
@SuppressWarnings("unused")
private void orderBasedOnReahability(List<QueryPlan> qPlan, Map<CollectionAlias, Set<String>> map, List<BasicExpression> expList) {
// first pass
// put all full table scans in reachable Columns list.
Map<CollectionAlias, Set<String>> reachableColumns = new HashMap<CollectionAlias, Set<String>>();
List<QueryPlan> finalOrder = new ArrayList<QueryPlan>();
Stack<QueryPlan> reachableOrder = new Stack<QueryPlan>();
for (int i = 0; i < qPlan.size(); i++) {
QueryPlan p = qPlan.get(i);
if (p instanceof FullTableScan) {
finalOrder.add(p);
Set<String> set = map.get(p.getCollectionAlias());
if (set != null && set.size() > 0) {
reachableColumns.put(p.getCollectionAlias(), set);
}
}
}
for (int i = 0; i < qPlan.size(); i++) {
QueryPlan p = qPlan.get(i);
if (p instanceof FullTableScan) {
continue;
}
IndexRangeScan irs = (IndexRangeScan) p;
isReachable(irs, reachableColumns, expList);
}
}
@SuppressWarnings("unused")
private boolean isReachable(IndexRangeScan irs, Map<CollectionAlias, Set<String>> reachableColumns, List<BasicExpression> expList) {
Set<String> idxCols = new HashSet<String>();
for (int i = 0; i < irs.getIndex().getColumnIdList().size(); i++) {
String columnName = SchemaMetadata.getInstance().getColumnName(irs.getIndex().getCollectionName(), irs.getIndex().getColumnIdList().get(i));
idxCols.add(columnName);
}
for (int i = 0; i < expList.size(); i++) {
Operand left = expList.get(i).getLeftOperand();
Operand right = expList.get(i).getRightOperand();
VariableOperand l = null;
if (left instanceof VariableOperand) {
l = (VariableOperand) left;
if (l.getCollectionAlias().equals(irs.collectionAlias) && idxCols.contains(l.getColumnId())) {
// if (right instanceof StaticOperand)
}
}
}
return false;
}
private boolean isOneStaticIndex(Operand o, Map<CollectionAlias, IndexNameMeta> bestMatchIndex) {
boolean oneStaticIndex = false;
if (o instanceof VariableOperand) {
VariableOperand vo = (VariableOperand) o;
CollectionAlias ca = new CollectionAlias(vo.getCollectionAlias());
IndexNameMeta idx = bestMatchIndex.get(ca);
if (idx != null && idx.getColumnIdList().get(0) == vo.getColumnId()) {
// we found it!
// we will use index all the way
oneStaticIndex = true;
}
}
return oneStaticIndex;
}
@SuppressWarnings("unused")
private boolean isOneStaticIndex(List<BasicExpression> list, Map<CollectionAlias, IndexNameMeta> bestMatchIndex) {
boolean oneStaticIndex = false;
for (int i = 0; i < list.size(); i++) {
BasicExpression bexp = list.get(i);
Operand o = bexp.getLeftOperand();
if (o instanceof StaticOperand) {
Operand r = bexp.getRightOperand();
oneStaticIndex = isOneStaticIndex(r, bestMatchIndex);
if (oneStaticIndex) {
break;
}
}
o = bexp.getRightOperand();
if (o instanceof StaticOperand) {
Operand l = bexp.getLeftOperand();
oneStaticIndex = isOneStaticIndex(l, bestMatchIndex);
if (oneStaticIndex) {
break;
}
}
}
return oneStaticIndex;
}
private List<BasicExpression> filterNonIndexable(List<BasicExpression> list) {
List<BasicExpression> retVal = new ArrayList<BasicExpression>();
if (list == null || list.size() == 0) {
return retVal;
}
for (int i = 0; i < list.size(); i++) {
BasicExpression exp = list.get(i);
int op = exp.getOperator();
if (op != Expression.OR &&
op != Expression.IN &&
op != Expression.NE) {
retVal.add(exp);
}
}
return retVal;
}
private Map<CollectionAlias, IndexNameMeta> getBestMatchIndex(Map<CollectionAlias, Set<Integer>> map, Set<CollectionAlias> reducedCollections) {
Map<CollectionAlias, IndexNameMeta> retMap = new HashMap<CollectionAlias, IndexNameMeta>();
Iterator<CollectionAlias> iter = map.keySet().iterator();
while (iter.hasNext()) {
CollectionAlias collectionName = iter.next();
if (!reducedCollections.contains(collectionName)) {
Set<Integer> colSet = map.get(collectionName);
IndexNameMeta idx = getBestMatchIndex(collectionName, colSet);
if (idx != null) {
retMap.put(collectionName, getBestMatchIndex(collectionName, colSet));
}
}
}
return retMap;
}
IndexNameMeta getBestMatchIndex(CollectionAlias collectionAlias, Set<Integer> colSet) {
List<IndexNameMeta> idxList = SchemaMetadata.getInstance().getIndexes(collectionAlias.getCollectionName());
if (idxList == null || idxList.size() == 0) {
return null;
}
int maxMatchCount = 0;
IndexNameMeta currentBestIndex = null;
for (int i = 0; i < idxList.size(); i++) {
int colMatchCount = 0;
IndexNameMeta meta = idxList.get(i);
List<Integer> colList = meta.getColumnIdList();
for (int x = 0; x < colList.size(); x++) {
Integer col = colList.get(x);
if (colSet.contains(col)) {
colMatchCount++;
}
}
if (colMatchCount > maxMatchCount) {
maxMatchCount = colMatchCount;
currentBestIndex = meta;
}
}
return currentBestIndex;
}
private Map<CollectionAlias, Set<Integer>> separateColumnsByCollections(List<BasicExpression> list) {
Map<CollectionAlias, Set<Integer>> map = new HashMap<CollectionAlias, Set<Integer>>();
for (int i = 0; i < list.size(); i++) {
BasicExpression exp = list.get(i);
Operand o = exp.getLeftOperand();
if (o instanceof VariableOperand) {
VariableOperand vo = (VariableOperand) o;
operandToColumn(vo, map);
}
o = exp.getRightOperand();
if (o instanceof VariableOperand) {
VariableOperand vo = (VariableOperand) o;
operandToColumn(vo, map);
}
}
return map;
}
private void operandToColumn(VariableOperand vo, Map<CollectionAlias, Set<Integer>> map) {
Set<Integer> s = map.get(vo.getCollectionAlias());
if (s == null) {
s = new HashSet<Integer>();
map.put(new CollectionAlias(vo.getCollectionAlias()), s);
}
s.add(vo.getColumnId());
}
@SuppressWarnings("unused")
private void fixExpList(List<BasicExpression> expList) {
for (int i = 0; i < expList.size(); i++) {
BasicExpression exp = expList.get(i);
if (exp.getLeftOperand() instanceof StaticOperand && exp.getRightOperand() instanceof VariableOperand && exp.getOperator() == Expression.EQ) {
replaceOperands(expList, exp.getRightOperand(), exp.getLeftOperand());
}
if (exp.getLeftOperand() instanceof VariableOperand && exp.getRightOperand() instanceof StaticOperand && exp.getOperator() == Expression.EQ) {
replaceOperands(expList, exp.getLeftOperand(), exp.getRightOperand());
}
}
}
private void replaceOperands(List<BasicExpression> expList, Operand op1, Operand op2) {
for (int i = 0; i < expList.size(); i++) {
Operand left = expList.get(i).getLeftOperand();
Operand right = expList.get(i).getRightOperand();
if (left instanceof VariableOperand && right instanceof VariableOperand) {
if (left.equals(op1)) {
expList.get(i).setLeftOperand(op2);
}
if (right.equals(op1)) {
expList.get(i).setRightOperand(op2);
}
}
}
}
}