package net.sourceforge.mayfly.evaluation.select;
import net.sourceforge.mayfly.MayflyException;
import net.sourceforge.mayfly.datastore.DataStore;
import net.sourceforge.mayfly.evaluation.Aggregator;
import net.sourceforge.mayfly.evaluation.Expression;
import net.sourceforge.mayfly.evaluation.NoColumn;
import net.sourceforge.mayfly.evaluation.ResultRow;
import net.sourceforge.mayfly.evaluation.ResultRows;
import net.sourceforge.mayfly.evaluation.condition.And;
import net.sourceforge.mayfly.evaluation.condition.Condition;
import net.sourceforge.mayfly.evaluation.from.From;
import net.sourceforge.mayfly.evaluation.from.FromElement;
import net.sourceforge.mayfly.evaluation.from.InnerJoin;
import net.sourceforge.mayfly.evaluation.what.Selected;
import net.sourceforge.mayfly.evaluation.what.What;
/* At least currently, this is written as a mutable object. It is a short-lived
one. */
public class Planner {
private final What what;
// mutated as we make joins explicit
private From from;
// mutated as we move conditions from WHERE to ON
private Condition where;
// mutated when we resolve
private Aggregator groupBy;
private final Distinct distinct;
private final OrderBy orderBy;
private final Limit limit;
public Planner(What what, From from, Condition where, Aggregator groupBy,
Distinct distinct, OrderBy orderBy, Limit limit) {
this.what = what;
this.from = from;
this.where = where;
this.groupBy = groupBy;
this.distinct = distinct;
this.orderBy = orderBy;
this.limit = limit;
}
public OptimizedSelect plan(Evaluator evaluator) {
Evaluator aliasEvaluator = new AliasEvaluator(what, evaluator);
optimize(aliasEvaluator);
ResultRow dummyRow = dummyRow(aliasEvaluator);
Selected selected = what.selected(dummyRow);
check(aliasEvaluator, selected, dummyRow);
return new OptimizedSelect(
aliasEvaluator, selected, dummyRow,
from.soleElement(), where, groupBy, distinct, orderBy, what, limit);
}
private void check(Evaluator evaluator, Selected selected, ResultRow dummyRow) {
for (Expression element : selected) {
element.evaluate(dummyRow, evaluator);
}
where.evaluate(dummyRow, evaluator);
where.rejectAggregates("WHERE");
groupBy = groupBy.resolve(dummyRow, evaluator);
ResultRow groupedDummyRow = groupBy.check(dummyRow, evaluator, selected);
ResultRows afterDistinct =
distinct.distinct(selected, new ResultRows(groupedDummyRow));
orderBy.check(afterDistinct.singleRow(), groupedDummyRow,
dummyRow, evaluator);
if (orderBy.isEmpty() && limit.isSpecified()) {
throw new MayflyException("Must specify ORDER BY with LIMIT");
}
}
private ResultRow dummyRow(Evaluator evaluator) {
FromElement element = from.soleElement();
return element.dummyRow(evaluator);
}
/**
* @internal
* Currently this method makes joins explicit and also moves
* conditions from WHERE to ON. The whole thing would probably
* be cleaner if those were separated. The
* {@link #dummyRow(int, DataStore, String)}
* method could make use of the joins which were built up
* in the first step.
*/
private void optimize(Evaluator evaluator) {
if (evaluator == null) {
throw new NullPointerException("evaluator is required");
}
ResultRow fullDummyRow = dummyRow(0, evaluator);
where.evaluate(fullDummyRow, evaluator);
while (from.size() > 1) {
// x y z -> join(x, y) z
FromElement first = from.element(0);
FromElement second = from.element(1);
Condition on =
moveWhereToOn(first, second, evaluator);
InnerJoin explicitJoin = new InnerJoin(first, second, on);
from = from.without(0).without(0).with(0, explicitJoin);
}
moveAllWhereToOn(evaluator);
}
private void moveAllWhereToOn(Evaluator evaluator) {
// Currently handled in optimize method
}
ResultRow dummyRow(int index, Evaluator evaluator) {
ResultRow dummyRow = from.element(index).dummyRow(evaluator);
if (index >= from.size() - 1) {
return dummyRow;
}
else {
return dummyRow.combine(
dummyRow(index + 1, evaluator));
}
}
private Condition moveWhereToOn(
FromElement first, FromElement second,
Evaluator evaluator) {
MoveResult result = new MoveResult();
moveToResult(first, second, evaluator, result, where);
where = result.nonMovable;
return result.toBeMoved;
}
private void moveToResult(FromElement first, FromElement second,
Evaluator evaluator,
final MoveResult moveResult, Condition toAnalyze) {
if (canMove(toAnalyze, first, second, evaluator)) {
moveResult.toBeMoved = toAnalyze.makeAnd(moveResult.toBeMoved);
}
else if (toAnalyze instanceof And) {
And and = (And) toAnalyze;
moveToResult(first, second, evaluator, moveResult, and.leftSide);
moveToResult(first, second, evaluator, moveResult, and.rightSide);
}
else {
moveResult.nonMovable = toAnalyze;
}
}
static class MoveResult {
Condition toBeMoved = Condition.TRUE;
Condition nonMovable = Condition.TRUE;
}
static boolean canMove(Condition condition,
FromElement first, FromElement second,
Evaluator evaluator) {
if (condition.firstAggregate() != null) {
return false;
}
InnerJoin join = new InnerJoin(first, second, Condition.TRUE);
ResultRow partialDummyRow = join.dummyRow(evaluator);
try {
condition.check(partialDummyRow);
return true;
}
catch (NoColumn e) {
return false;
}
}
}