<commit_before>/**
 * END USER LICENSE AGREEMENT (“EULA”)
 *
 * READ THIS AGREEMENT CAREFULLY (date: 9/13/2011):
 * http://www.akiban.com/licensing/20110913
 *
 * BY INSTALLING OR USING ALL OR ANY PORTION OF THE SOFTWARE, YOU ARE ACCEPTING
 * ALL OF THE TERMS AND CONDITIONS OF THIS AGREEMENT. YOU AGREE THAT THIS
 * AGREEMENT IS ENFORCEABLE LIKE ANY WRITTEN AGREEMENT SIGNED BY YOU.
 *
 * IF YOU HAVE PAID A LICENSE FEE FOR USE OF THE SOFTWARE AND DO NOT AGREE TO
 * THESE TERMS, YOU MAY RETURN THE SOFTWARE FOR A FULL REFUND PROVIDED YOU (A) DO
 * NOT USE THE SOFTWARE AND (B) RETURN THE SOFTWARE WITHIN THIRTY (30) DAYS OF
 * YOUR INITIAL PURCHASE.
 *
 * IF YOU WISH TO USE THE SOFTWARE AS AN EMPLOYEE, CONTRACTOR, OR AGENT OF A
 * CORPORATION, PARTNERSHIP OR SIMILAR ENTITY, THEN YOU MUST BE AUTHORIZED TO SIGN
 * FOR AND BIND THE ENTITY IN ORDER TO ACCEPT THE TERMS OF THIS AGREEMENT. THE
 * LICENSES GRANTED UNDER THIS AGREEMENT ARE EXPRESSLY CONDITIONED UPON ACCEPTANCE
 * BY SUCH AUTHORIZED PERSONNEL.
 *
 * IF YOU HAVE ENTERED INTO A SEPARATE WRITTEN LICENSE AGREEMENT WITH AKIBAN FOR
 * USE OF THE SOFTWARE, THE TERMS AND CONDITIONS OF SUCH OTHER AGREEMENT SHALL
 * PREVAIL OVER ANY CONFLICTING TERMS OR CONDITIONS IN THIS AGREEMENT.
 */

package com.akiban.sql.optimizer.rule;

import com.akiban.server.expression.std.Comparison;
import com.akiban.server.types.AkType;
import com.akiban.server.types3.TPreptimeValue;
import com.akiban.server.types3.mcompat.mtypes.MString;
import com.akiban.server.types3.pvalue.PValue;
import com.akiban.sql.optimizer.plan.*;
import com.akiban.sql.optimizer.plan.JoinNode;
import com.akiban.sql.optimizer.plan.JoinNode.JoinType;
import com.akiban.sql.optimizer.plan.ResultSet.ResultField;
import com.akiban.sql.optimizer.plan.Sort.OrderByExpression;
import com.akiban.sql.optimizer.plan.UpdateStatement.UpdateColumn;
import static com.akiban.sql.optimizer.rule.PlanContext.*;

import com.akiban.sql.optimizer.*;

import com.akiban.sql.StandardException;
import com.akiban.sql.parser.*;
import com.akiban.sql.types.DataTypeDescriptor;
import com.akiban.sql.types.TypeId;

import com.akiban.ais.model.Column;
import com.akiban.ais.model.Group;
import com.akiban.ais.model.UserTable;

import com.akiban.server.error.InsertWrongCountException;
import com.akiban.server.error.NoSuchTableException;
import com.akiban.server.error.ProtectedTableDDLException;
import com.akiban.server.error.SQLParserInternalException;
import com.akiban.server.error.UnsupportedSQLException;
import com.akiban.server.error.OrderGroupByNonIntegerConstant;
import com.akiban.server.error.OrderGroupByIntegerOutOfRange;
import com.akiban.server.error.WrongExpressionArityException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/** Turn a parsed SQL AST into this package's format.
 */
public class ASTStatementLoader extends BaseRule
{
    // TODO: Maybe move this into a separate class.
    public static final int IN_TO_OR_MAX_COUNT_DEFAULT = 1;

    private static final Logger logger = LoggerFactory.getLogger(ASTStatementLoader.class);

    @Override
    protected Logger getLogger() {
        return logger;
    }

    public static final WhiteboardMarker<AST> MARKER = 
        new DefaultWhiteboardMarker<AST>();

    /** Recover the {@link AST} put on the whiteboard when loaded. */
    public static AST getAST(PlanContext plan) {
        return plan.getWhiteboard(MARKER);
    }

    @Override
    public void apply(PlanContext plan) {
        AST ast = (AST)plan.getPlan();
        plan.putWhiteboard(MARKER, ast);
        DMLStatementNode stmt = ast.getStatement();
        try {
            plan.setPlan(new Loader((SchemaRulesContext)plan.getRulesContext()).toStatement(stmt));
        }
        catch (StandardException ex) {
            throw new SQLParserInternalException(ex);
        }
    }

    static class Loader {
        private SchemaRulesContext rulesContext;

        Loader(SchemaRulesContext rulesContext) {
            this.rulesContext = rulesContext;
            pushEquivalenceFinder();
        }

        private void pushEquivalenceFinder() {
            columnEquivalences.push(new EquivalenceFinder<ColumnExpression>() {
                @Override
                protected String describeElement(ColumnExpression element) {
                    return element.getSQLsource().getTableName() + "." + element.getColumn().getName();
                }
            });
        }

        private void popEquivalenceFinder() {
            columnEquivalences.pop();
        }
        
        private EquivalenceFinder<ColumnExpression> peekEquivalenceFinder() {
            return columnEquivalences.element();
        }

        /** Convert given statement into appropriate intermediate form. */
        protected BaseStatement toStatement(DMLStatementNode stmt) throws StandardException {
            switch (stmt.getNodeType()) {
            case NodeTypes.CURSOR_NODE:
                return toSelectQuery((CursorNode)stmt);
            case NodeTypes.DELETE_NODE:
                return toDeleteStatement((DeleteNode)stmt);
            case NodeTypes.UPDATE_NODE:
                return toUpdateStatement((UpdateNode)stmt);
            case NodeTypes.INSERT_NODE:
                return toInsertStatement((InsertNode)stmt);
            default:
                throw new StandardException("Unsupported statement type: " +
                                            stmt.statementToString());
            }
        }

        // SELECT
        protected SelectQuery toSelectQuery(CursorNode cursorNode) 
                throws StandardException {
            PlanNode query = toQueryForSelect(cursorNode.getResultSetNode(),
                                              cursorNode.getOrderByList(),
                                              cursorNode.getOffsetClause(),
                                              cursorNode.getFetchFirstClause());
            if (cursorNode.getUpdateMode() == CursorNode.UpdateMode.UPDATE)
                throw new UnsupportedSQLException("FOR UPDATE", cursorNode);
            return new SelectQuery(query, peekEquivalenceFinder());
        }

        // UPDATE
        protected DMLStatement toUpdateStatement(UpdateNode updateNode)
                throws StandardException {
            ResultSetNode rs = updateNode.getResultSetNode();
            PlanNode query = toQuery((SelectNode)rs);
            TableNode targetTable = getTargetTable(updateNode);
            ResultColumnList rcl = rs.getResultColumns();
            List<UpdateColumn> updateColumns = 
                new ArrayList<UpdateColumn>(rcl.size());
            for (ResultColumn result : rcl) {
                Column column = getColumnReferenceColumn(result.getReference(),
                                                         "result column");
                ExpressionNode value = toExpression(result.getExpression());
                updateColumns.add(new UpdateColumn(column, value));
            }
            ReturningValues values = calculateReturningValues (updateNode.getReturningList(),
                        (FromTable)updateNode.getUserData(),
                        targetTable.getTable().getColumns().size());
            query = new UpdateStatement(query, targetTable, 
                        updateColumns, values.table); 

            if (values.row != null) {
                query = new Project (query, values.row);
            }
            return new DMLStatement (query, BaseUpdateStatement.StatementType.UPDATE, 
                    values.results, values.table, targetTable, peekEquivalenceFinder());
        }

        // INSERT
        protected DMLStatement toInsertStatement(InsertNode insertNode)
                throws StandardException {
            PlanNode query = toQueryForSelect(insertNode.getResultSetNode(),
                                              insertNode.getOrderByList(),
                                              insertNode.getOffset(),
                                              insertNode.getFetchFirst());
            if (query instanceof ResultSet)
                query = ((ResultSet)query).getInput();
            TableNode targetTable = getTargetTable(insertNode);
            ResultColumnList rcl = insertNode.getTargetColumnList();
            int ncols = insertNode.getResultSetNode().getResultColumns().size();
            List<Column> targetColumns;
            if (rcl != null) {
                if (ncols != rcl.size())
                    throw new InsertWrongCountException(rcl.size(), ncols);
                targetColumns = new ArrayList<Column>(rcl.size());
                for (ResultColumn resultColumn : rcl) {
                    Column column = getColumnReferenceColumn(resultColumn.getReference(),
                                                             "Unsupported target column");
                    targetColumns.add(column);
                }
            }
            else {
                // No explicit column list: use DDL order.
                List<Column> aisColumns = targetTable.getTable().getColumns();
                if (ncols > aisColumns.size())
                    throw new InsertWrongCountException(aisColumns.size(), ncols);
                targetColumns = new ArrayList<Column>(ncols);
                for (int i = 0; i < ncols; i++) {
                    targetColumns.add(aisColumns.get(i));
                }
            }

            ReturningValues values = calculateReturningValues (insertNode.getReturningList(),
                                (FromTable)insertNode.getUserData(),
                                targetTable.getTable().getColumns().size());
            
            query = new InsertStatement (query, targetTable, 
                    targetColumns, values.table);
            if (values.row != null) {
                query = new Project (query, values.row);
            }
            return new DMLStatement (query, BaseUpdateStatement.StatementType.INSERT, 
                    values.results, values.table, targetTable, peekEquivalenceFinder());
        }
    
        
        // DELETE
        protected DMLStatement toDeleteStatement(DeleteNode deleteNode)
                throws StandardException {
            PlanNode query = toQuery((SelectNode)deleteNode.getResultSetNode());
            TableNode targetTable = getTargetTable(deleteNode);
            
            ReturningValues values = calculateReturningValues (deleteNode.getReturningList(), 
                                        (FromTable)deleteNode.getUserData(),
                                        targetTable.getTable().getColumns().size());
            
            query = new DeleteStatement(query, targetTable, values.table);
            if (values.row != null) {
                query = new Project (query, values.row);
            }
            return new DMLStatement(query, BaseUpdateStatement.StatementType.DELETE, 
                    values.results, values.table, targetTable, peekEquivalenceFinder());
        }
        
        private class ReturningValues {
            public List<ExpressionNode> row = null;
            public TableSource table = null;
            public List<ResultField> results = null;
        }

        protected ReturningValues calculateReturningValues (ResultColumnList rcl, FromTable table, int size)
                throws StandardException {
            ReturningValues values = new ReturningValues();
            if (rcl != null) {
                values.table = (TableSource)toJoinNode(table, true);
                values.row = new ArrayList<ExpressionNode>(rcl.size());
                for (ResultColumn resultColumn : rcl) {
                    values.row.add(toExpression(resultColumn.getExpression()));
                }
                values.results = resultColumns(rcl);
            }
            return values;
        }
        
        /** The query part of SELECT / INSERT, which might be VALUES / UNION */
        protected PlanNode toQueryForSelect(ResultSetNode resultSet,
                                            OrderByList orderByList,
                                            ValueNode offsetClause,
                                            ValueNode fetchFirstClause)
                throws StandardException {
            if (resultSet instanceof SelectNode)
                return toQueryForSelect((SelectNode)resultSet,
                                        orderByList,
                                        offsetClause,
                                        fetchFirstClause);
            else if (resultSet instanceof RowResultSetNode) {
                List<ExpressionNode> row = toExpressionsRow(resultSet);
                List<List<ExpressionNode>> rows = new ArrayList<List<ExpressionNode>>(1);
                rows.add(row);
                return new ExpressionsSource(rows);
            }
            else if (resultSet instanceof RowsResultSetNode) {
                List<List<ExpressionNode>> rows = new ArrayList<List<ExpressionNode>>();
                for (ResultSetNode row : ((RowsResultSetNode)resultSet).getRows()) {
                    rows.add(toExpressionsRow(row));
                }
                return new ExpressionsSource(rows);
            }
            else if (resultSet instanceof UnionNode) {
                UnionNode union = (UnionNode)resultSet;
                boolean all = union.isAll();
                PlanNode left = toQueryForSelect(union.getLeftResultSet());
                PlanNode right = toQueryForSelect(union.getRightResultSet());
                return new Union(left, right, all);
            }
            else
                throw new UnsupportedSQLException("Unsupported query", resultSet);
        }

        protected List<ResultField> resultColumns(ResultColumnList rcl) 
                        throws StandardException {
            List<ResultField> results = new ArrayList<ResultField>(rcl.size());
            for (ResultColumn result : rcl) {
                String name = result.getName();
                DataTypeDescriptor type = result.getType();
                boolean nameDefaulted =
                    (result.getExpression() instanceof ColumnReference) &&
                    (name == ((ColumnReference)result.getExpression()).getColumnName());
                Column column = null;
                ExpressionNode expr = toExpression(result.getExpression());
                if (expr instanceof ColumnExpression) {
                    column = ((ColumnExpression)expr).getColumn();
                    if ((column != null) && nameDefaulted)
                        name = column.getName();
                }
                results.add(new ResultField(name, type, column));
            }
            return results;
        }
        /** A normal SELECT */
        protected PlanNode toQueryForSelect(SelectNode selectNode,
                                            OrderByList orderByList,
                                            ValueNode offsetClause,
                                            ValueNode fetchFirstClause)
                throws StandardException {
            PlanNode query = toQuery(selectNode);

            ResultColumnList rcl = selectNode.getResultColumns();
            List<ResultField> results = resultColumns (rcl);
            List<ExpressionNode> projects = new ArrayList<ExpressionNode>(rcl.size());

            for (ResultColumn result : rcl) {
                ExpressionNode expr = toExpression(result.getExpression());
                projects.add(expr);
            }

            List<OrderByExpression> sorts = new ArrayList<OrderByExpression>();
            if (orderByList != null) {
                for (OrderByColumn orderByColumn : orderByList) {
                    ExpressionNode expression = toOrderGroupBy(orderByColumn.getExpression(), projects, "ORDER");
                    sorts.add(new OrderByExpression(expression,
                                                    orderByColumn.isAscending()));
                }
            }

            // Stupid but legal: 
            //  SELECT 1 FROM t ORDER BY MAX(c); 
            //  SELECT 1 FROM t HAVING MAX(c) > 0; 
            if ((selectNode.getGroupByList() != null) ||
                (selectNode.getHavingClause() != null) ||
                hasAggregateFunction(projects) ||
                hasAggregateFunctionA(sorts)) {
                query = toAggregateSource(query, selectNode.getGroupByList(), projects);
                query = new Select(query, toConditions(selectNode.getHavingClause(), projects));
            }

            if (selectNode.hasWindows())
                throw new UnsupportedSQLException("WINDOW", selectNode);
        
            do_distinct:
            {
                // Distinct puts the Project before any Sort so as to only do one sort.
                if (selectNode.isDistinct()) {
                    Project project = new Project(query, projects);
                    if (sorts.isEmpty()) {
                        query = new Distinct(project);
                        break do_distinct;
                    }
                    else if (adjustSortsForDistinct(sorts, project)) {
                        query = new Sort(project, sorts);
                        query = new Distinct(query, Distinct.Implementation.EXPLICIT_SORT);
                        break do_distinct;
                    }
                    else {
                        query = new AggregateSource(query, new ArrayList<ExpressionNode>((projects)));
                        // Don't break: treat like non-distinct case.
                    }
                }
                if (!sorts.isEmpty()) {
                    query = new Sort(query, sorts);
                }
                query = new Project(query, projects);
            }

            if ((offsetClause != null) || 
                (fetchFirstClause != null))
                query = toLimit(query, offsetClause, fetchFirstClause);

            query = new ResultSet(query, results);

            return query;
        }

        protected PlanNode toQueryForSelect(ResultSetNode resultSet)
                throws StandardException {
            return toQueryForSelect(resultSet, null, null, null);
        }

        protected List<ExpressionNode> toExpressionsRow(ResultSetNode resultSet)
                throws StandardException {
            ResultColumnList resultColumns = resultSet.getResultColumns();
            List<ExpressionNode> row = new ArrayList<ExpressionNode>(resultColumns.size());
            for (ResultColumn resultColumn : resultColumns) {
                row.add(toExpression(resultColumn.getExpression()));
            }
            return row;
        }

        /** The common top-level join and select part of all statements. */
        protected PlanNode toQuery(SelectNode selectNode)
                throws StandardException {
            PlanNode input;
            if (!selectNode.getFromList().isEmpty()) {
                Joinable joins = null;
                for (FromTable fromTable : selectNode.getFromList()) {
                    if (joins == null)
                        joins = toJoinNode(fromTable, true);
                    else
                        joins = joinNodes(joins, toJoinNode(fromTable, true), JoinType.INNER);
                }
                input = joins;
            }
            else {
                // No FROM list means one row with presumably constant Projects.
                input = new ExpressionsSource(Collections.singletonList(Collections.<ExpressionNode>emptyList()));
            }
            ConditionList conditions = toConditions(selectNode.getWhereClause());
            if (hasAggregateFunction(conditions))
                throw new UnsupportedSQLException("Aggregate not allowed in WHERE",
                                                  selectNode.getWhereClause());
            return new Select(input, conditions);
        }

        protected Map<FromTable,Joinable> joinNodes =
            new HashMap<FromTable,Joinable>();

        protected Joinable toJoinNode(FromTable fromTable, boolean required)
                throws StandardException {
            Joinable result;
            if (fromTable instanceof FromBaseTable) {
                TableBinding tb = (TableBinding)fromTable.getUserData();
                if (tb == null)
                    throw new UnsupportedSQLException("FROM table",
                                                      fromTable);
                UserTable userTable = (UserTable)tb.getTable();
                TableNode table = getTableNode(userTable);
                String name = fromTable.getCorrelationName();
                if (name == null) {
                    if (userTable.getName().getSchemaName().equals(rulesContext.getDefaultSchemaName()))
                        name = userTable.getName().getTableName();
                    else
                        name = userTable.getName().toString();
                }
                result = new TableSource(table, required, name);
            }
            else if (fromTable instanceof com.akiban.sql.parser.JoinNode) {
                com.akiban.sql.parser.JoinNode joinNode = 
                    (com.akiban.sql.parser.JoinNode)fromTable;
                JoinType joinType;
                switch (joinNode.getNodeType()) {
                case NodeTypes.JOIN_NODE:
                    joinType = JoinType.INNER;
                    break;
                case NodeTypes.HALF_OUTER_JOIN_NODE:
                    if (((HalfOuterJoinNode)joinNode).isRightOuterJoin())
                        joinType = JoinType.RIGHT;
                    else
                        joinType = JoinType.LEFT;
                    break;
                default:
                    throw new UnsupportedSQLException("Unsupported join type", joinNode);
                }
                JoinNode join = joinNodes(toJoinNode((FromTable)joinNode.getLeftResultSet(),
                                                     required && (joinType != JoinType.RIGHT)),
                                          toJoinNode((FromTable)joinNode.getRightResultSet(),
                                                     required && (joinType != JoinType.LEFT)),
                                          joinType);
                join.setJoinConditions(toConditions(joinNode.getJoinClause()));
                result = join;
            }
            else if (fromTable instanceof FromSubquery) {
                FromSubquery fromSubquery = (FromSubquery)fromTable;
                PlanNode subquery = toQueryForSelect(fromSubquery.getSubquery(),
                                                     fromSubquery.getOrderByList(),
                                                     fromSubquery.getOffset(),
                                                     fromSubquery.getFetchFirst());
                result = new SubquerySource(new Subquery(subquery, peekEquivalenceFinder()),
                                            fromSubquery.getExposedName());
            }
            else
                throw new UnsupportedSQLException("Unsupported FROM non-table", fromTable);
            joinNodes.put(fromTable, result);
            return result;
        }

        protected JoinNode joinNodes(Joinable left, Joinable right, JoinType joinType)
                throws StandardException {
            return new JoinNode(left, right, joinType);
        }

        /** Add a set of conditions to input. */
        protected ConditionList toConditions(ValueNode cnfClause)
                throws StandardException {
            return toConditions(cnfClause, null);
        }

        protected ConditionList toConditions(ValueNode cnfClause,
                                             List<ExpressionNode> projects)
                throws StandardException {
            ConditionList conditions = new ConditionList();
            while (cnfClause != null) {
                if (cnfClause.isBooleanTrue()) break;
                if (!(cnfClause instanceof AndNode))
                    throw new UnsupportedSQLException("Unsupported complex WHERE",
                                                      cnfClause);
                AndNode andNode = (AndNode)cnfClause;
                cnfClause = andNode.getRightOperand();
                ValueNode condition = andNode.getLeftOperand();
                addCondition(conditions, condition, projects);
            }
            return conditions;
        }

        /** Fill the given list with conditions from given AST node.
         * Takes a list because BETWEEN generates <em>two</em> conditions.
         */
        protected void addCondition(List<ConditionExpression> conditions, 
                                    ValueNode condition,
                                    List<ExpressionNode> projects)
                throws StandardException {
            switch (condition.getNodeType()) {
            case NodeTypes.BINARY_EQUALS_OPERATOR_NODE:
                addComparisonCondition(conditions, projects,
                                       (BinaryOperatorNode)condition, Comparison.EQ);
                break;
            case NodeTypes.BINARY_GREATER_THAN_OPERATOR_NODE:
                addComparisonCondition(conditions, projects,
                                       (BinaryOperatorNode)condition, Comparison.GT);
                break;
            case NodeTypes.BINARY_GREATER_EQUALS_OPERATOR_NODE:
                addComparisonCondition(conditions, projects,
                                       (BinaryOperatorNode)condition, Comparison.GE);
                break;
            case NodeTypes.BINARY_LESS_THAN_OPERATOR_NODE:
                addComparisonCondition(conditions, projects,
                                       (BinaryOperatorNode)condition, Comparison.LT);
                break;
            case NodeTypes.BINARY_LESS_EQUALS_OPERATOR_NODE:
                addComparisonCondition(conditions, projects,
                                       (BinaryOperatorNode)condition, Comparison.LE);
                break;
            case NodeTypes.BINARY_NOT_EQUALS_OPERATOR_NODE:
                addComparisonCondition(conditions, projects,
                                       (BinaryOperatorNode)condition, Comparison.NE);
                break;
            case NodeTypes.BETWEEN_OPERATOR_NODE:
                addBetweenCondition(conditions, projects,
                                    (BetweenOperatorNode)condition);
                break;

            case NodeTypes.IN_LIST_OPERATOR_NODE:
                addInCondition(conditions, projects,
                               (InListOperatorNode)condition);
                break;

            case NodeTypes.SUBQUERY_NODE:
                addSubqueryCondition(conditions, projects,
                                     (SubqueryNode)condition);
                break;

            case NodeTypes.LIKE_OPERATOR_NODE:
                addFunctionCondition(conditions, projects,
                                     (TernaryOperatorNode)condition);
                break;
            case NodeTypes.IS_NULL_NODE:
            case NodeTypes.IS_NOT_NULL_NODE:
                addIsNullCondition(conditions, projects,
                                   (IsNullNode)condition);
                break;

            case NodeTypes.IS_NODE:
                addIsCondition(conditions, projects,
                               (IsNode)condition);
                break;

            case NodeTypes.OR_NODE:
            case NodeTypes.AND_NODE:
            case NodeTypes.NOT_NODE:
                addLogicalFunctionCondition(conditions, projects, condition);
                break;

            case NodeTypes.BOOLEAN_CONSTANT_NODE:
                conditions.add(new BooleanConstantExpression(((BooleanConstantNode)condition).getBooleanValue()));
                break;
            case NodeTypes.UNTYPED_NULL_CONSTANT_NODE:
                conditions.add(new BooleanConstantExpression(null));
                break;
            case NodeTypes.PARAMETER_NODE:
                if (condition.getType() == null)
                    condition.setType(new DataTypeDescriptor(TypeId.BOOLEAN_ID, true));
                conditions.add(new ParameterCondition(((ParameterNode)condition)
                                                      .getParameterNumber(),
                                                      condition.getType(), condition));
                break;
            case NodeTypes.CAST_NODE:
                assert condition.getType().getTypeId().isBooleanTypeId();
                conditions.add(new BooleanCastExpression(toExpression(((CastNode)condition)
                                                                      .getCastOperand(),
                                                                      projects),
                                                         condition.getType(), condition));
                break;
            case NodeTypes.JAVA_TO_SQL_VALUE_NODE:
                conditions.add((ConditionExpression)
                               toExpression(((JavaToSQLValueNode)condition).getJavaValueNode(), 
                                            condition, true,
                                            projects));
                break;
            default:
                throw new UnsupportedSQLException("Unsupported WHERE predicate",
                                                  condition);
            }
        }

        protected void addComparisonCondition(List<ConditionExpression> conditions,
                                              List<ExpressionNode> projects,
                                              BinaryOperatorNode binop, Comparison op)
                throws StandardException {
            ExpressionNode left = toExpression(binop.getLeftOperand(), projects);
            ExpressionNode right = toExpression(binop.getRightOperand(), projects);
            conditions.add(new ComparisonCondition(op, left, right,
                                                   binop.getType(), binop));
        }

        protected void addBetweenCondition(List<ConditionExpression> conditions,
                                           List<ExpressionNode> projects,
                                           BetweenOperatorNode between)
                throws StandardException {
            ExpressionNode left = toExpression(between.getLeftOperand(), projects);
            ValueNodeList rightOperandList = between.getRightOperandList();
            ExpressionNode right1 = toExpression(rightOperandList.get(0), projects);
            ExpressionNode right2 = toExpression(rightOperandList.get(1), projects);
            DataTypeDescriptor type = between.getType();<commit_msg>Random expression used as condition means cast to boolean.<commit_after>/**
 * END USER LICENSE AGREEMENT (“EULA”)
 *
 * READ THIS AGREEMENT CAREFULLY (date: 9/13/2011):
 * http://www.akiban.com/licensing/20110913
 *
 * BY INSTALLING OR USING ALL OR ANY PORTION OF THE SOFTWARE, YOU ARE ACCEPTING
 * ALL OF THE TERMS AND CONDITIONS OF THIS AGREEMENT. YOU AGREE THAT THIS
 * AGREEMENT IS ENFORCEABLE LIKE ANY WRITTEN AGREEMENT SIGNED BY YOU.
 *
 * IF YOU HAVE PAID A LICENSE FEE FOR USE OF THE SOFTWARE AND DO NOT AGREE TO
 * THESE TERMS, YOU MAY RETURN THE SOFTWARE FOR A FULL REFUND PROVIDED YOU (A) DO
 * NOT USE THE SOFTWARE AND (B) RETURN THE SOFTWARE WITHIN THIRTY (30) DAYS OF
 * YOUR INITIAL PURCHASE.
 *
 * IF YOU WISH TO USE THE SOFTWARE AS AN EMPLOYEE, CONTRACTOR, OR AGENT OF A
 * CORPORATION, PARTNERSHIP OR SIMILAR ENTITY, THEN YOU MUST BE AUTHORIZED TO SIGN
 * FOR AND BIND THE ENTITY IN ORDER TO ACCEPT THE TERMS OF THIS AGREEMENT. THE
 * LICENSES GRANTED UNDER THIS AGREEMENT ARE EXPRESSLY CONDITIONED UPON ACCEPTANCE
 * BY SUCH AUTHORIZED PERSONNEL.
 *
 * IF YOU HAVE ENTERED INTO A SEPARATE WRITTEN LICENSE AGREEMENT WITH AKIBAN FOR
 * USE OF THE SOFTWARE, THE TERMS AND CONDITIONS OF SUCH OTHER AGREEMENT SHALL
 * PREVAIL OVER ANY CONFLICTING TERMS OR CONDITIONS IN THIS AGREEMENT.
 */

package com.akiban.sql.optimizer.rule;

import com.akiban.server.expression.std.Comparison;
import com.akiban.server.types.AkType;
import com.akiban.server.types3.TPreptimeValue;
import com.akiban.server.types3.mcompat.mtypes.MString;
import com.akiban.server.types3.pvalue.PValue;
import com.akiban.sql.optimizer.plan.*;
import com.akiban.sql.optimizer.plan.JoinNode;
import com.akiban.sql.optimizer.plan.JoinNode.JoinType;
import com.akiban.sql.optimizer.plan.ResultSet.ResultField;
import com.akiban.sql.optimizer.plan.Sort.OrderByExpression;
import com.akiban.sql.optimizer.plan.UpdateStatement.UpdateColumn;
import static com.akiban.sql.optimizer.rule.PlanContext.*;

import com.akiban.sql.optimizer.*;

import com.akiban.sql.StandardException;
import com.akiban.sql.parser.*;
import com.akiban.sql.types.DataTypeDescriptor;
import com.akiban.sql.types.TypeId;

import com.akiban.ais.model.Column;
import com.akiban.ais.model.Group;
import com.akiban.ais.model.UserTable;

import com.akiban.server.error.InsertWrongCountException;
import com.akiban.server.error.NoSuchTableException;
import com.akiban.server.error.ProtectedTableDDLException;
import com.akiban.server.error.SQLParserInternalException;
import com.akiban.server.error.UnsupportedSQLException;
import com.akiban.server.error.OrderGroupByNonIntegerConstant;
import com.akiban.server.error.OrderGroupByIntegerOutOfRange;
import com.akiban.server.error.WrongExpressionArityException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/** Turn a parsed SQL AST into this package's format.
 */
public class ASTStatementLoader extends BaseRule
{
    // TODO: Maybe move this into a separate class.
    public static final int IN_TO_OR_MAX_COUNT_DEFAULT = 1;

    private static final Logger logger = LoggerFactory.getLogger(ASTStatementLoader.class);

    @Override
    protected Logger getLogger() {
        return logger;
    }

    public static final WhiteboardMarker<AST> MARKER = 
        new DefaultWhiteboardMarker<AST>();

    /** Recover the {@link AST} put on the whiteboard when loaded. */
    public static AST getAST(PlanContext plan) {
        return plan.getWhiteboard(MARKER);
    }

    @Override
    public void apply(PlanContext plan) {
        AST ast = (AST)plan.getPlan();
        plan.putWhiteboard(MARKER, ast);
        DMLStatementNode stmt = ast.getStatement();
        try {
            plan.setPlan(new Loader((SchemaRulesContext)plan.getRulesContext()).toStatement(stmt));
        }
        catch (StandardException ex) {
            throw new SQLParserInternalException(ex);
        }
    }

    static class Loader {
        private SchemaRulesContext rulesContext;

        Loader(SchemaRulesContext rulesContext) {
            this.rulesContext = rulesContext;
            pushEquivalenceFinder();
        }

        private void pushEquivalenceFinder() {
            columnEquivalences.push(new EquivalenceFinder<ColumnExpression>() {
                @Override
                protected String describeElement(ColumnExpression element) {
                    return element.getSQLsource().getTableName() + "." + element.getColumn().getName();
                }
            });
        }

        private void popEquivalenceFinder() {
            columnEquivalences.pop();
        }
        
        private EquivalenceFinder<ColumnExpression> peekEquivalenceFinder() {
            return columnEquivalences.element();
        }

        /** Convert given statement into appropriate intermediate form. */
        protected BaseStatement toStatement(DMLStatementNode stmt) throws StandardException {
            switch (stmt.getNodeType()) {
            case NodeTypes.CURSOR_NODE:
                return toSelectQuery((CursorNode)stmt);
            case NodeTypes.DELETE_NODE:
                return toDeleteStatement((DeleteNode)stmt);
            case NodeTypes.UPDATE_NODE:
                return toUpdateStatement((UpdateNode)stmt);
            case NodeTypes.INSERT_NODE:
                return toInsertStatement((InsertNode)stmt);
            default:
                throw new StandardException("Unsupported statement type: " +
                                            stmt.statementToString());
            }
        }

        // SELECT
        protected SelectQuery toSelectQuery(CursorNode cursorNode) 
                throws StandardException {
            PlanNode query = toQueryForSelect(cursorNode.getResultSetNode(),
                                              cursorNode.getOrderByList(),
                                              cursorNode.getOffsetClause(),
                                              cursorNode.getFetchFirstClause());
            if (cursorNode.getUpdateMode() == CursorNode.UpdateMode.UPDATE)
                throw new UnsupportedSQLException("FOR UPDATE", cursorNode);
            return new SelectQuery(query, peekEquivalenceFinder());
        }

        // UPDATE
        protected DMLStatement toUpdateStatement(UpdateNode updateNode)
                throws StandardException {
            ResultSetNode rs = updateNode.getResultSetNode();
            PlanNode query = toQuery((SelectNode)rs);
            TableNode targetTable = getTargetTable(updateNode);
            ResultColumnList rcl = rs.getResultColumns();
            List<UpdateColumn> updateColumns = 
                new ArrayList<UpdateColumn>(rcl.size());
            for (ResultColumn result : rcl) {
                Column column = getColumnReferenceColumn(result.getReference(),
                                                         "result column");
                ExpressionNode value = toExpression(result.getExpression());
                updateColumns.add(new UpdateColumn(column, value));
            }
            ReturningValues values = calculateReturningValues (updateNode.getReturningList(),
                        (FromTable)updateNode.getUserData(),
                        targetTable.getTable().getColumns().size());
            query = new UpdateStatement(query, targetTable, 
                        updateColumns, values.table); 

            if (values.row != null) {
                query = new Project (query, values.row);
            }
            return new DMLStatement (query, BaseUpdateStatement.StatementType.UPDATE, 
                    values.results, values.table, targetTable, peekEquivalenceFinder());
        }

        // INSERT
        protected DMLStatement toInsertStatement(InsertNode insertNode)
                throws StandardException {
            PlanNode query = toQueryForSelect(insertNode.getResultSetNode(),
                                              insertNode.getOrderByList(),
                                              insertNode.getOffset(),
                                              insertNode.getFetchFirst());
            if (query instanceof ResultSet)
                query = ((ResultSet)query).getInput();
            TableNode targetTable = getTargetTable(insertNode);
            ResultColumnList rcl = insertNode.getTargetColumnList();
            int ncols = insertNode.getResultSetNode().getResultColumns().size();
            List<Column> targetColumns;
            if (rcl != null) {
                if (ncols != rcl.size())
                    throw new InsertWrongCountException(rcl.size(), ncols);
                targetColumns = new ArrayList<Column>(rcl.size());
                for (ResultColumn resultColumn : rcl) {
                    Column column = getColumnReferenceColumn(resultColumn.getReference(),
                                                             "Unsupported target column");
                    targetColumns.add(column);
                }
            }
            else {
                // No explicit column list: use DDL order.
                List<Column> aisColumns = targetTable.getTable().getColumns();
                if (ncols > aisColumns.size())
                    throw new InsertWrongCountException(aisColumns.size(), ncols);
                targetColumns = new ArrayList<Column>(ncols);
                for (int i = 0; i < ncols; i++) {
                    targetColumns.add(aisColumns.get(i));
                }
            }

            ReturningValues values = calculateReturningValues (insertNode.getReturningList(),
                                (FromTable)insertNode.getUserData(),
                                targetTable.getTable().getColumns().size());
            
            query = new InsertStatement (query, targetTable, 
                    targetColumns, values.table);
            if (values.row != null) {
                query = new Project (query, values.row);
            }
            return new DMLStatement (query, BaseUpdateStatement.StatementType.INSERT, 
                    values.results, values.table, targetTable, peekEquivalenceFinder());
        }
    
        
        // DELETE
        protected DMLStatement toDeleteStatement(DeleteNode deleteNode)
                throws StandardException {
            PlanNode query = toQuery((SelectNode)deleteNode.getResultSetNode());
            TableNode targetTable = getTargetTable(deleteNode);
            
            ReturningValues values = calculateReturningValues (deleteNode.getReturningList(), 
                                        (FromTable)deleteNode.getUserData(),
                                        targetTable.getTable().getColumns().size());
            
            query = new DeleteStatement(query, targetTable, values.table);
            if (values.row != null) {
                query = new Project (query, values.row);
            }
            return new DMLStatement(query, BaseUpdateStatement.StatementType.DELETE, 
                    values.results, values.table, targetTable, peekEquivalenceFinder());
        }
        
        private class ReturningValues {
            public List<ExpressionNode> row = null;
            public TableSource table = null;
            public List<ResultField> results = null;
        }

        protected ReturningValues calculateReturningValues (ResultColumnList rcl, FromTable table, int size)
                throws StandardException {
            ReturningValues values = new ReturningValues();
            if (rcl != null) {
                values.table = (TableSource)toJoinNode(table, true);
                values.row = new ArrayList<ExpressionNode>(rcl.size());
                for (ResultColumn resultColumn : rcl) {
                    values.row.add(toExpression(resultColumn.getExpression()));
                }
                values.results = resultColumns(rcl);
            }
            return values;
        }
        
        /** The query part of SELECT / INSERT, which might be VALUES / UNION */
        protected PlanNode toQueryForSelect(ResultSetNode resultSet,
                                            OrderByList orderByList,
                                            ValueNode offsetClause,
                                            ValueNode fetchFirstClause)
                throws StandardException {
            if (resultSet instanceof SelectNode)
                return toQueryForSelect((SelectNode)resultSet,
                                        orderByList,
                                        offsetClause,
                                        fetchFirstClause);
            else if (resultSet instanceof RowResultSetNode) {
                List<ExpressionNode> row = toExpressionsRow(resultSet);
                List<List<ExpressionNode>> rows = new ArrayList<List<ExpressionNode>>(1);
                rows.add(row);
                return new ExpressionsSource(rows);
            }
            else if (resultSet instanceof RowsResultSetNode) {
                List<List<ExpressionNode>> rows = new ArrayList<List<ExpressionNode>>();
                for (ResultSetNode row : ((RowsResultSetNode)resultSet).getRows()) {
                    rows.add(toExpressionsRow(row));
                }
                return new ExpressionsSource(rows);
            }
            else if (resultSet instanceof UnionNode) {
                UnionNode union = (UnionNode)resultSet;
                boolean all = union.isAll();
                PlanNode left = toQueryForSelect(union.getLeftResultSet());
                PlanNode right = toQueryForSelect(union.getRightResultSet());
                return new Union(left, right, all);
            }
            else
                throw new UnsupportedSQLException("Unsupported query", resultSet);
        }

        protected List<ResultField> resultColumns(ResultColumnList rcl) 
                        throws StandardException {
            List<ResultField> results = new ArrayList<ResultField>(rcl.size());
            for (ResultColumn result : rcl) {
                String name = result.getName();
                DataTypeDescriptor type = result.getType();
                boolean nameDefaulted =
                    (result.getExpression() instanceof ColumnReference) &&
                    (name == ((ColumnReference)result.getExpression()).getColumnName());
                Column column = null;
                ExpressionNode expr = toExpression(result.getExpression());
                if (expr instanceof ColumnExpression) {
                    column = ((ColumnExpression)expr).getColumn();
                    if ((column != null) && nameDefaulted)
                        name = column.getName();
                }
                results.add(new ResultField(name, type, column));
            }
            return results;
        }
        /** A normal SELECT */
        protected PlanNode toQueryForSelect(SelectNode selectNode,
                                            OrderByList orderByList,
                                            ValueNode offsetClause,
                                            ValueNode fetchFirstClause)
                throws StandardException {
            PlanNode query = toQuery(selectNode);

            ResultColumnList rcl = selectNode.getResultColumns();
            List<ResultField> results = resultColumns (rcl);
            List<ExpressionNode> projects = new ArrayList<ExpressionNode>(rcl.size());

            for (ResultColumn result : rcl) {
                ExpressionNode expr = toExpression(result.getExpression());
                projects.add(expr);
            }

            List<OrderByExpression> sorts = new ArrayList<OrderByExpression>();
            if (orderByList != null) {
                for (OrderByColumn orderByColumn : orderByList) {
                    ExpressionNode expression = toOrderGroupBy(orderByColumn.getExpression(), projects, "ORDER");
                    sorts.add(new OrderByExpression(expression,
                                                    orderByColumn.isAscending()));
                }
            }

            // Stupid but legal: 
            //  SELECT 1 FROM t ORDER BY MAX(c); 
            //  SELECT 1 FROM t HAVING MAX(c) > 0; 
            if ((selectNode.getGroupByList() != null) ||
                (selectNode.getHavingClause() != null) ||
                hasAggregateFunction(projects) ||
                hasAggregateFunctionA(sorts)) {
                query = toAggregateSource(query, selectNode.getGroupByList(), projects);
                query = new Select(query, toConditions(selectNode.getHavingClause(), projects));
            }

            if (selectNode.hasWindows())
                throw new UnsupportedSQLException("WINDOW", selectNode);
        
            do_distinct:
            {
                // Distinct puts the Project before any Sort so as to only do one sort.
                if (selectNode.isDistinct()) {
                    Project project = new Project(query, projects);
                    if (sorts.isEmpty()) {
                        query = new Distinct(project);
                        break do_distinct;
                    }
                    else if (adjustSortsForDistinct(sorts, project)) {
                        query = new Sort(project, sorts);
                        query = new Distinct(query, Distinct.Implementation.EXPLICIT_SORT);
                        break do_distinct;
                    }
                    else {
                        query = new AggregateSource(query, new ArrayList<ExpressionNode>((projects)));
                        // Don't break: treat like non-distinct case.
                    }
                }
                if (!sorts.isEmpty()) {
                    query = new Sort(query, sorts);
                }
                query = new Project(query, projects);
            }

            if ((offsetClause != null) || 
                (fetchFirstClause != null))
                query = toLimit(query, offsetClause, fetchFirstClause);

            query = new ResultSet(query, results);

            return query;
        }

        protected PlanNode toQueryForSelect(ResultSetNode resultSet)
                throws StandardException {
            return toQueryForSelect(resultSet, null, null, null);
        }

        protected List<ExpressionNode> toExpressionsRow(ResultSetNode resultSet)
                throws StandardException {
            ResultColumnList resultColumns = resultSet.getResultColumns();
            List<ExpressionNode> row = new ArrayList<ExpressionNode>(resultColumns.size());
            for (ResultColumn resultColumn : resultColumns) {
                row.add(toExpression(resultColumn.getExpression()));
            }
            return row;
        }

        /** The common top-level join and select part of all statements. */
        protected PlanNode toQuery(SelectNode selectNode)
                throws StandardException {
            PlanNode input;
            if (!selectNode.getFromList().isEmpty()) {
                Joinable joins = null;
                for (FromTable fromTable : selectNode.getFromList()) {
                    if (joins == null)
                        joins = toJoinNode(fromTable, true);
                    else
                        joins = joinNodes(joins, toJoinNode(fromTable, true), JoinType.INNER);
                }
                input = joins;
            }
            else {
                // No FROM list means one row with presumably constant Projects.
                input = new ExpressionsSource(Collections.singletonList(Collections.<ExpressionNode>emptyList()));
            }
            ConditionList conditions = toConditions(selectNode.getWhereClause());
            if (hasAggregateFunction(conditions))
                throw new UnsupportedSQLException("Aggregate not allowed in WHERE",
                                                  selectNode.getWhereClause());
            return new Select(input, conditions);
        }

        protected Map<FromTable,Joinable> joinNodes =
            new HashMap<FromTable,Joinable>();

        protected Joinable toJoinNode(FromTable fromTable, boolean required)
                throws StandardException {
            Joinable result;
            if (fromTable instanceof FromBaseTable) {
                TableBinding tb = (TableBinding)fromTable.getUserData();
                if (tb == null)
                    throw new UnsupportedSQLException("FROM table",
                                                      fromTable);
                UserTable userTable = (UserTable)tb.getTable();
                TableNode table = getTableNode(userTable);
                String name = fromTable.getCorrelationName();
                if (name == null) {
                    if (userTable.getName().getSchemaName().equals(rulesContext.getDefaultSchemaName()))
                        name = userTable.getName().getTableName();
                    else
                        name = userTable.getName().toString();
                }
                result = new TableSource(table, required, name);
            }
            else if (fromTable instanceof com.akiban.sql.parser.JoinNode) {
                com.akiban.sql.parser.JoinNode joinNode = 
                    (com.akiban.sql.parser.JoinNode)fromTable;
                JoinType joinType;
                switch (joinNode.getNodeType()) {
                case NodeTypes.JOIN_NODE:
                    joinType = JoinType.INNER;
                    break;
                case NodeTypes.HALF_OUTER_JOIN_NODE:
                    if (((HalfOuterJoinNode)joinNode).isRightOuterJoin())
                        joinType = JoinType.RIGHT;
                    else
                        joinType = JoinType.LEFT;
                    break;
                default:
                    throw new UnsupportedSQLException("Unsupported join type", joinNode);
                }
                JoinNode join = joinNodes(toJoinNode((FromTable)joinNode.getLeftResultSet(),
                                                     required && (joinType != JoinType.RIGHT)),
                                          toJoinNode((FromTable)joinNode.getRightResultSet(),
                                                     required && (joinType != JoinType.LEFT)),
                                          joinType);
                join.setJoinConditions(toConditions(joinNode.getJoinClause()));
                result = join;
            }
            else if (fromTable instanceof FromSubquery) {
                FromSubquery fromSubquery = (FromSubquery)fromTable;
                PlanNode subquery = toQueryForSelect(fromSubquery.getSubquery(),
                                                     fromSubquery.getOrderByList(),
                                                     fromSubquery.getOffset(),
                                                     fromSubquery.getFetchFirst());
                result = new SubquerySource(new Subquery(subquery, peekEquivalenceFinder()),
                                            fromSubquery.getExposedName());
            }
            else
                throw new UnsupportedSQLException("Unsupported FROM non-table", fromTable);
            joinNodes.put(fromTable, result);
            return result;
        }

        protected JoinNode joinNodes(Joinable left, Joinable right, JoinType joinType)
                throws StandardException {
            return new JoinNode(left, right, joinType);
        }

        /** Add a set of conditions to input. */
        protected ConditionList toConditions(ValueNode cnfClause)
                throws StandardException {
            return toConditions(cnfClause, null);
        }

        protected ConditionList toConditions(ValueNode cnfClause,
                                             List<ExpressionNode> projects)
                throws StandardException {
            ConditionList conditions = new ConditionList();
            while (cnfClause != null) {
                if (cnfClause.isBooleanTrue()) break;
                if (!(cnfClause instanceof AndNode))
                    throw new UnsupportedSQLException("Unsupported complex WHERE",
                                                      cnfClause);
                AndNode andNode = (AndNode)cnfClause;
                cnfClause = andNode.getRightOperand();
                ValueNode condition = andNode.getLeftOperand();
                addCondition(conditions, condition, projects);
            }
            return conditions;
        }

        /** Fill the given list with conditions from given AST node.
         * Takes a list because BETWEEN generates <em>two</em> conditions.
         */
        protected void addCondition(List<ConditionExpression> conditions, 
                                    ValueNode condition,
                                    List<ExpressionNode> projects)
                throws StandardException {
            DataTypeDescriptor conditionType = null;
            switch (condition.getNodeType()) {
            case NodeTypes.BINARY_EQUALS_OPERATOR_NODE:
                addComparisonCondition(conditions, projects,
                                       (BinaryOperatorNode)condition, Comparison.EQ);
                return;
            case NodeTypes.BINARY_GREATER_THAN_OPERATOR_NODE:
                addComparisonCondition(conditions, projects,
                                       (BinaryOperatorNode)condition, Comparison.GT);
                return;
            case NodeTypes.BINARY_GREATER_EQUALS_OPERATOR_NODE:
                addComparisonCondition(conditions, projects,
                                       (BinaryOperatorNode)condition, Comparison.GE);
                return;
            case NodeTypes.BINARY_LESS_THAN_OPERATOR_NODE:
                addComparisonCondition(conditions, projects,
                                       (BinaryOperatorNode)condition, Comparison.LT);
                return;
            case NodeTypes.BINARY_LESS_EQUALS_OPERATOR_NODE:
                addComparisonCondition(conditions, projects,
                                       (BinaryOperatorNode)condition, Comparison.LE);
                return;
            case NodeTypes.BINARY_NOT_EQUALS_OPERATOR_NODE:
                addComparisonCondition(conditions, projects,
                                       (BinaryOperatorNode)condition, Comparison.NE);
                return;
            case NodeTypes.BETWEEN_OPERATOR_NODE:
                addBetweenCondition(conditions, projects,
                                    (BetweenOperatorNode)condition);
                return;
            case NodeTypes.IN_LIST_OPERATOR_NODE:
                addInCondition(conditions, projects,
                               (InListOperatorNode)condition);
                return;
            case NodeTypes.SUBQUERY_NODE:
                addSubqueryCondition(conditions, projects,
                                     (SubqueryNode)condition);
                return;
            case NodeTypes.LIKE_OPERATOR_NODE:
                addFunctionCondition(conditions, projects,
                                     (TernaryOperatorNode)condition);
                return;
            case NodeTypes.IS_NULL_NODE:
            case NodeTypes.IS_NOT_NULL_NODE:
                addIsNullCondition(conditions, projects,
                                   (IsNullNode)condition);
                return;
            case NodeTypes.IS_NODE:
                addIsCondition(conditions, projects,
                               (IsNode)condition);
                return;
            case NodeTypes.OR_NODE:
            case NodeTypes.AND_NODE:
            case NodeTypes.NOT_NODE:
                addLogicalFunctionCondition(conditions, projects, condition);
                return;
            case NodeTypes.BOOLEAN_CONSTANT_NODE:
                conditions.add(new BooleanConstantExpression(((BooleanConstantNode)condition).getBooleanValue()));
                return;
            case NodeTypes.UNTYPED_NULL_CONSTANT_NODE:
                conditions.add(new BooleanConstantExpression(null));
                return;
            case NodeTypes.PARAMETER_NODE:
                conditionType = condition.getType();
                if (conditionType == null) {
                    conditionType = new DataTypeDescriptor(TypeId.BOOLEAN_ID, true);
                    condition.setType(conditionType);
                }
                conditions.add(new ParameterCondition(((ParameterNode)condition)
                                                      .getParameterNumber(),
                                                      conditionType, condition));
                return;
            case NodeTypes.CAST_NODE:
                // Use given cast type if it's suitable for a condition.
                conditionType = condition.getType();
                // CAST inside to BOOLEAN below.
                condition = ((CastNode)condition).getCastOperand();
                break;
            case NodeTypes.JAVA_TO_SQL_VALUE_NODE:
                conditions.add((ConditionExpression)
                               toExpression(((JavaToSQLValueNode)condition).getJavaValueNode(), 
                                            condition, true,
                                            projects));
                return;
            }
            // Anything else gets CAST to BOOLEAN, which may fail
            // later due to lack of a suitable cast.
            if (conditionType == null)
                conditionType = condition.getType();
            if (conditionType == null)
                conditionType = new DataTypeDescriptor(TypeId.BOOLEAN_ID, true);
            else if (!conditionType.getTypeId().isBooleanTypeId())
                    conditionType = new DataTypeDescriptor(TypeId.BOOLEAN_ID, conditionType.isNullable());
            conditions.add(new BooleanCastExpression(toExpression(condition, projects),
                                                     conditionType, condition));
        }

        protected void addComparisonCondition(List<ConditionExpression> conditions,
                                              List<ExpressionNode> projects,
                                              BinaryOperatorNode binop, Comparison op)
                throws StandardException {
            ExpressionNode left = toExpression(binop.getLeftOperand(), projects);
            ExpressionNode right = toExpression(binop.getRightOperand(), projects);
            conditions.add(new ComparisonCondition(op, left, right,
                                                   binop.getType(), binop));
        }

        protected void addBetweenCondition(List<ConditionExpression> conditions,
                                           List<ExpressionNode> projects,
                                           BetweenOperatorNode between)
                throws StandardException {
            ExpressionNode left = toExpression(between.getLeftOperand(), projects);
            ValueNodeList rightOperandList = between.getRightOperandList();
            ExpressionNode right1 = toExpression(rightOperandList.get(0), projects);
            ExpressionNode right2 = toExpression(rightOperandList.get(1), projects);
            DataTypeDescriptor type = between.getType();
{'fraction_non_alphanumeric': 0.07621691890417115, 'fraction_numerical': 0.0010271389365902198, 'mean_word_length': 1.6452982381659944, 'pattern_counts': {'":': 0, '<': 97, '<?xml version=': 0, '>': 99, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 194, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1219206', 'n_tokens_mistral': 14262, 'n_tokens_neox': 13556, 'n_words': 3173}