package jeql.engine.query;
import java.util.Iterator;
import java.util.List;
import jeql.api.error.ExecutionException;
import jeql.api.row.RowList;
import jeql.api.table.Table;
import jeql.engine.CompilationException;
import jeql.engine.Scope;
import jeql.engine.query.group.GroupBinder;
import jeql.engine.query.group.GroupByEvaluator;
import jeql.engine.query.group.GroupScope;
import jeql.engine.query.join.IndexedJoinRowList;
import jeql.engine.query.join.NestedLoopJoinRowList;
import jeql.engine.query.join.SimpleNestedLoopJoinRowList;
import jeql.monitor.Monitor;
import jeql.syntax.AssignmentNode;
import jeql.syntax.FromItem;
import jeql.syntax.FromList;
import jeql.syntax.FunctionNode;
import jeql.syntax.ParseTreeNode;
import jeql.syntax.SelectItemList;
import jeql.syntax.SelectNode;
import jeql.syntax.StatementListNode;
public class SelectEvaluator
{
public static Table eval(SelectNode select, Scope scope)
{
SelectEvaluator queryEval = new SelectEvaluator(select);
return queryEval.eval(scope);
}
public static void evalAliases(StatementListNode stmtList, Scope scope)
{
if (stmtList == null)
return;
for (Iterator i = stmtList.getStatements().iterator(); i.hasNext(); ) {
AssignmentNode node = (AssignmentNode) i.next();
node.eval(scope);
}
}
private SelectNode select;
private BaseQueryScope baseScope = null;
public SelectEvaluator(SelectNode select) {
this.select = select;
}
public Table eval(Scope scope)
{
// each select expression gets its own scope
baseScope = new BaseQueryScope(scope);
baseScope.prepareFromItems(select.getFromList());
SelectItemList selectList = select.getSelectList();
selectList.expand(baseScope);
selectList.checkUniqueNames();
bind(select, baseScope);
RowList baseRS = evalFromWhereSplit();
RowList selectRS = null;
boolean isGrouped = isGrouped(select, baseScope);
if (isGrouped) {
selectRS = evalGroupBy(select, baseScope, baseRS);
}
else {
selectRS = evalSelect(select, baseScope, baseRS);
}
RowList finalRS = evalDistinctOrderLimit(select, selectRS);
return createTable(finalRS);
}
private Table createTable(RowList rs)
{
//rs = (RowList) Monitor.wrap(select.getLine(), select.monitorTag(), rs);
// column names are already present in input RL, so just reuse them
Table tbl = new Table(rs);
return tbl;
}
private boolean isGrouped(SelectNode select, BaseQueryScope scope)
{
return select.isGrouped() || scope.hasAggregateFunctions();
}
private void bind(SelectNode select, QueryScope scope)
{
ParseTreeNode whereExpr = select.getWhere();
if (whereExpr != null)
whereExpr.bind(baseScope);
bindSplitBy(select.getSplitBy(), baseScope);
// bind LET block stmts
bindAliasBlock(select.getAliasList(), baseScope);
// bind select list
select.getSelectList().bind(baseScope);
}
private void bindSplitBy(ParseTreeNode splitByExpr, QueryScope scope)
{
if (splitByExpr != null) {
splitByExpr.bind(baseScope);
// add SPLIT BY pseudo-column variables to scope
scope.setVariableType(QueryScope.VAR_SPLITINDEX, Integer.class);
scope.setVariable(QueryScope.VAR_SPLITINDEX, null);
scope.setVariableType(QueryScope.VAR_SPLITVALUE, splitByExpr.getType(baseScope));
scope.setVariable(QueryScope.VAR_SPLITVALUE, null);
}
}
/**
* Enter alias vars into scope, with defined type but undefined value
*
* @param scope
*/
private void bindAliasBlock(StatementListNode stmtList, QueryScope scope)
{
if (stmtList == null)
return;
for (Iterator i = stmtList.getStatements().iterator(); i.hasNext(); ) {
AssignmentNode node = (AssignmentNode) i.next();
node.bind(scope);
String name = node.getVariableName();
Class nodeType = node.getType(scope);
/**
* Set the type of the variable, which is now known.
* The value is not yet known and so is set to null.
*/
scope.setVariableType(name, nodeType);
scope.setVariable(name, null);
}
}
private RowList evalSelect(SelectNode select, QueryScope scope, RowList baseRS)
{
RowList selectRS = new SelectedItemsRowList(baseRS, select.getSelectList(), select.getAliasList(), scope);
return selectRS;
}
//*
private RowList evalGroupBy(SelectNode select, BaseQueryScope scope, RowList baseRS)
{
GroupBinder groupBinder = new GroupBinder(select, scope);
List aggFunArgList = groupBinder.getAggFunArgs();
GroupScope groupScope = groupBinder.getScope(scope);
SelectItemList selectList = select.getSelectList();
// now re-bind in group scope (which will not bind below agg functions)
selectList.bind(groupScope);
GroupByEvaluator ge = new GroupByEvaluator(
scope, aggFunArgList, select.getAliasList(),
groupScope, selectList);
RowList selectRS = ge.eval(baseRS);
return selectRS;
}
/**
* Computes final rowList with DISTINCT/ORDER BY/LIMIT+OFFSET
* evaluated if specified.
*
* @param selectList
* @param selectRS
* @return
*/
private RowList evalDistinctOrderLimit(SelectNode select, RowList selectRS)
{
RowList finalRS = selectRS;
// evaluate DISTINCT
if (select.hasDistinct()) {
finalRS = (new DistinctEvaluator(finalRS)).eval();
}
// evaluate ORDER BY
if (select.hasOrderBy()) {
finalRS = (new OrderByEvaluator(select, finalRS)).eval();
}
// evaluate LIMIT and OFFSET
if (select.hasLimit()) {
// eval using a stream
finalRS = new LimitRowList(finalRS, select.getLimitValue(), select.getOffsetValue());
// finalRS = (new LimitEvaluator(select, finalRS)).eval();
}
return finalRS;
}
private RowList evalFromWhereSplit()
{
RowList baseRS = buildFrom();
RowList splitRS = buildSplitRowStream(baseRS, select.getSplitBy());
RowList whereRS = buildFilteredRowStream(splitRS, select.getWhere());
return whereRS;
}
private RowList buildFrom()
{
RowList baseRS = null;
if (! select.hasFromList()) {
throw new CompilationException(select, "Empty/Missing FROM clause not supported");
}
if (select.getFromList().size() == 1) {
baseRS = buildSingleTableRowStream();
}
else {
baseRS = buildJoin();
}
/*
else
throw new CompilationException(select, "JOINing more than 2 tables not currently supported");
*/
return baseRS;
}
private RowList buildFilteredRowStream(RowList baseRS,
ParseTreeNode filterExpr)
{
// short-circuit if there is no WHERE clause
if (filterExpr == null)
return baseRS;
RowList rowStr = new FilterRowList(baseRS, filterExpr, baseScope);
return rowStr;
}
private RowList buildSplitRowStream(RowList baseRS,
ParseTreeNode splitExpr)
{
// short-circuit if there is no SPLIT BY clause
if (splitExpr == null)
return baseRS;
if (! (splitExpr instanceof FunctionNode)
|| ! ((FunctionNode) splitExpr).isSplitFunction()) {
throw new CompilationException(select, "Invalid Splitting function: " + splitExpr);
}
RowList rowStr = new SplitRowList(baseRS, splitExpr, baseScope);
return rowStr;
}
/*
private RowList buildNoTableRowStream()
{
Object lastObj = baseScope.getVariable(Scope.LAST_TABLE);
if (lastObj instanceof Table) {
Table tbl = (Table) lastObj;
return tbl.getRows();
}
throw new ExecutionException(select, "No default table computed");
}
*/
private RowList buildSingleTableRowStream()
{
return baseScope.getFromTable(0).getRows();
}
/*
private RowList buildSimpleNestedLoopJoin()
{
RowList rs0 = select.getFromList().getItem(0).eval(baseScope);
FromItem from1 = select.getFromList().getItem(1);
RowList rs1 = from1.eval(baseScope);
SimpleNestedLoopJoinRowList joinRS = new SimpleNestedLoopJoinRowList();
joinRS.addRowStream(rs0);
joinRS.addRowStream(rs1);
ParseTreeNode joinCondExpr = from1.getJoinConditionExpr();
return buildFilteredRowStream(joinRS, joinCondExpr);
}
private RowList buildNestedLoopSingleJoin()
{
RowList rs0 = select.getFromList().getItem(0).eval(baseScope);
FromItem from1 = select.getFromList().getItem(1);
RowList rs1 = from1.eval(baseScope);
ParseTreeNode joinCondExpr = from1.getJoinConditionExpr();
NestedLoopJoinRowList joinRS = new NestedLoopJoinRowList(rs0, rs1,
joinCondExpr, from1.getJoinType(), baseScope);
return joinRS;
}
*/
private RowList buildJoin()
{
FromList fromList = select.getFromList();
RowList rs0 = fromList.getItem(0).eval(baseScope);
RowList joinedRS = rs0;
for (int i = 1; i < fromList.size(); i++) {
FromItem from = fromList.getItem(i);
joinedRS = addJoin(joinedRS, from);
}
return joinedRS;
}
private RowList addJoin(RowList innerRS, FromItem from) {
RowList joinRS = from.eval(baseScope);
ParseTreeNode joinCondExpr = from.getJoinConditionExpr();
RowList joinedRS = null;
// see if join can be indexed
int indexSide = IndexedJoinRowList.indexableOperandSide(joinCondExpr, from, joinRS.getSchema());
if (indexSide >= 0) {
joinedRS = new IndexedJoinRowList(innerRS, joinRS, indexSide,
joinCondExpr, from.getJoinType(), baseScope);
}
else {
// default is to do a nested loop join
joinedRS = new NestedLoopJoinRowList(innerRS, joinRS,
joinCondExpr, from.getJoinType(), baseScope);
}
return joinedRS;
}
}