/*
 * 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.
 */
package org.apache.phoenix.parse;

import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import com.google.common.collect.ArrayListMultimap;
import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.expression.ExpressionType;
import org.apache.phoenix.expression.function.AvgAggregateFunction;
import org.apache.phoenix.expression.function.CountAggregateFunction;
import org.apache.phoenix.expression.function.CurrentDateFunction;
import org.apache.phoenix.expression.function.CurrentTimeFunction;
import org.apache.phoenix.expression.function.DistinctCountAggregateFunction;
import org.apache.phoenix.expression.function.FunctionExpression;
import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunction;
import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunctionInfo;
import org.apache.phoenix.parse.JoinTableNode.JoinType;
import org.apache.phoenix.parse.LikeParseNode.LikeType;
import org.apache.phoenix.schema.PIndexState;
import org.apache.phoenix.schema.PTable.IndexType;
import org.apache.phoenix.schema.PTableType;
import org.apache.phoenix.schema.SortOrder;
import org.apache.phoenix.schema.TypeMismatchException;
import org.apache.phoenix.schema.stats.StatisticsCollectionScope;
import org.apache.phoenix.schema.types.PDataType;
import org.apache.phoenix.schema.types.PLong;
import org.apache.phoenix.schema.types.PTimestamp;
import org.apache.phoenix.util.SchemaUtil;

import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;

/**
 * 
 * Factory used by parser to construct object model while parsing a SQL statement
 * 
 * 
 * @since 0.1
 */
public class ParseNodeFactory {
	private static final String ARRAY_ELEM = "ARRAY_ELEM";
	// TODO: Use Google's Reflection library instead to find aggregate functions
    @SuppressWarnings("unchecked")
    private static final List<Class<? extends FunctionExpression>> CLIENT_SIDE_BUILT_IN_FUNCTIONS = Arrays.<Class<? extends FunctionExpression>>asList(
        CurrentDateFunction.class,
        CurrentTimeFunction.class,
        AvgAggregateFunction.class
        );
    private static final Map<BuiltInFunctionKey, BuiltInFunctionInfo> BUILT_IN_FUNCTION_MAP = Maps.newHashMap();
    private static final Multimap<String, BuiltInFunctionInfo> BUILT_IN_FUNCTION_MULTIMAP = ArrayListMultimap.create();
    private static final BigDecimal MAX_LONG = BigDecimal.valueOf(Long.MAX_VALUE);


    /**
     *
     * Key used to look up a built-in function using the combination of
     * the lowercase name and the number of arguments. This disambiguates
     * the aggregate MAX(<col>) from the non aggregate MAX(<col1>,<col2>).
     *
     * 
     * @since 0.1
     */
    public static class BuiltInFunctionKey {
        private final String upperName;
        private final int argCount;

        public BuiltInFunctionKey(String lowerName, int argCount) {
            this.upperName = lowerName;
            this.argCount = argCount;
        }

        @Override
        public String toString() {
            return upperName;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + argCount;
            result = prime * result + ((upperName == null) ? 0 : upperName.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null) return false;
            if (getClass() != obj.getClass()) return false;
            BuiltInFunctionKey other = (BuiltInFunctionKey)obj;
            if (argCount != other.argCount) return false;
            if (!upperName.equals(other.upperName)) return false;
            return true;
        }
    }

    private static void addBuiltInFunction(Class<? extends FunctionExpression> f) throws Exception {
        BuiltInFunction d = f.getAnnotation(BuiltInFunction.class);
        if (d == null) {
            return;
        }
        int nArgs = d.args().length;
        BuiltInFunctionInfo value = new BuiltInFunctionInfo(f, d);
        if (d.classType() != FunctionParseNode.FunctionClassType.ABSTRACT) {
            BUILT_IN_FUNCTION_MULTIMAP.put(value.getName(), value);
        }
        if (d.classType() != FunctionParseNode.FunctionClassType.DERIVED) {
            do {
                // Add function to function map, throwing if conflicts found
                // Add entry for each possible version of function based on arguments that are not required to be present (i.e. arg with default value)
                BuiltInFunctionKey key = new BuiltInFunctionKey(value.getName(), nArgs);
                if (BUILT_IN_FUNCTION_MAP.put(key, value) != null) {
                    throw new IllegalStateException("Multiple " + value.getName() + " functions with " + nArgs + " arguments");
                }
            } while (--nArgs >= 0 && d.args()[nArgs].defaultValue().length() > 0);

            // Look for default values that aren't at the end and throw
            while (--nArgs >= 0) {
                if (d.args()[nArgs].defaultValue().length() > 0) {
                    throw new IllegalStateException("Function " + value.getName() + " has non trailing default value of '" + d.args()[nArgs].defaultValue() + "'. Only trailing arguments may have default values");
                }
            }
        }
    }
    /**
     * Reflect this class and populate static structures from it.
     * Don't initialize in static block because we have a circular dependency
     */
    private synchronized static void initBuiltInFunctionMap() {
        if (!BUILT_IN_FUNCTION_MAP.isEmpty()) {
            return;
        }
        Class<? extends FunctionExpression> f = null;
        try {
            // Reflection based parsing which yields direct explicit function evaluation at runtime
            for (int i = 0; i < CLIENT_SIDE_BUILT_IN_FUNCTIONS.size(); i++) {
                f = CLIENT_SIDE_BUILT_IN_FUNCTIONS.get(i);
                addBuiltInFunction(f);
            }
            for (ExpressionType et : ExpressionType.values()) {
                Class<? extends Expression> ec = et.getExpressionClass();
                if (FunctionExpression.class.isAssignableFrom(ec)) {
                    @SuppressWarnings("unchecked")
                    Class<? extends FunctionExpression> c = (Class<? extends FunctionExpression>)ec;
                    addBuiltInFunction(f = c);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Failed initialization of built-in functions at class '" + f + "'", e);
        }
    }

    private static BuiltInFunctionInfo getInfo(String name, List<ParseNode> children) {
        return get(SchemaUtil.normalizeIdentifier(name), children);
    }

    public static BuiltInFunctionInfo get(String normalizedName, List<ParseNode> children) {
        initBuiltInFunctionMap();
        BuiltInFunctionInfo info = BUILT_IN_FUNCTION_MAP.get(new BuiltInFunctionKey(normalizedName,children.size()));
        return info;
    }

    public static Multimap<String, BuiltInFunctionInfo> getBuiltInFunctionMultimap(){
        initBuiltInFunctionMap();
        return BUILT_IN_FUNCTION_MULTIMAP;
    }

    public ParseNodeFactory() {
    }
    
    private static AtomicInteger tempAliasCounter = new AtomicInteger(0);
    
    public static String createTempAlias() {
        return "$" + tempAliasCounter.incrementAndGet();
    }

    public ExplainStatement explain(BindableStatement statement) {
        return new ExplainStatement(statement);
    }

    public AliasedNode aliasedNode(String alias, ParseNode expression) {
    	return new AliasedNode(alias, expression);
    }

    public AddParseNode add(List<ParseNode> children) {
        return new AddParseNode(children);
    }

    public SubtractParseNode subtract(List<ParseNode> children) {
        return new SubtractParseNode(children);
    }

    public MultiplyParseNode multiply(List<ParseNode> children) {
        return new MultiplyParseNode(children);
    }

    public ModulusParseNode modulus(List<ParseNode> children) {
        return new ModulusParseNode(children);
    }

    public AndParseNode and(List<ParseNode> children) {
        return new AndParseNode(children);
    }

    public FamilyWildcardParseNode family(String familyName){
    	    return new FamilyWildcardParseNode(familyName, false);
    }

    public TableWildcardParseNode tableWildcard(TableName tableName) {
        return new TableWildcardParseNode(tableName, false);
    }

    public WildcardParseNode wildcard() {
        return WildcardParseNode.INSTANCE;
    }

    public BetweenParseNode between(ParseNode l, ParseNode r1, ParseNode r2, boolean negate) {
        return new BetweenParseNode(l, r1, r2, negate);
    }

    public BindParseNode bind(String bind) {
        return new BindParseNode(bind);
    }

    public StringConcatParseNode concat(List<ParseNode> children) {
        return new StringConcatParseNode(children);
    }

    public ColumnParseNode column(TableName tableName, String columnName, String alias) {
        return new ColumnParseNode(tableName, columnName, alias);
    }
    
    public ColumnName columnName(String columnName) {
        return new ColumnName(columnName);
    }

    public ColumnName columnName(String familyName, String columnName) {
        return new ColumnName(familyName, columnName);
    }

    public PropertyName propertyName(String propertyName) {
        return new PropertyName(propertyName);
    }

    public PropertyName propertyName(String familyName, String propertyName) {
        return new PropertyName(familyName, propertyName);
    }

    public ColumnDef columnDef(ColumnName columnDefName, String sqlTypeName, boolean isNull, Integer maxLength, Integer scale, boolean isPK, SortOrder sortOrder, String expressionStr, boolean isRowTimestamp) {
        return new ColumnDef(columnDefName, sqlTypeName, isNull, maxLength, scale, isPK, sortOrder, expressionStr, isRowTimestamp);
    }

    public ColumnDef columnDef(ColumnName columnDefName, String sqlTypeName,
            boolean isArray, Integer arrSize, Boolean isNull,
            Integer maxLength, Integer scale, boolean isPK,
        	SortOrder sortOrder, String expressionStr, boolean isRowTimestamp) {
        return new ColumnDef(columnDefName, sqlTypeName,
                isArray, arrSize, isNull,
                maxLength, scale, isPK,
                sortOrder, expressionStr, isRowTimestamp);
    }

    public ColumnDef columnDef(ColumnName columnDefName, String sqlTypeName, boolean isArray, Integer arrSize, Boolean isNull, Integer maxLength, Integer scale, boolean isPK,
            SortOrder sortOrder, boolean isRowTimestamp) {
        return new ColumnDef(columnDefName, sqlTypeName, isArray, arrSize, isNull, maxLength, scale, isPK, sortOrder, null, isRowTimestamp);
    }
    
    public ColumnDefInPkConstraint columnDefInPkConstraint(ColumnName columnDefName, SortOrder sortOrder, boolean isRowTimestamp) {
        return new ColumnDefInPkConstraint(columnDefName, sortOrder, isRowTimestamp);
    }
    
    public PrimaryKeyConstraint primaryKey(String name, List<ColumnDefInPkConstraint> columnDefs) {
        return new PrimaryKeyConstraint(name, columnDefs);
    }
    
    public IndexKeyConstraint indexKey( List<Pair<ParseNode, SortOrder>> parseNodeAndSortOrder) {
        return new IndexKeyConstraint(parseNodeAndSortOrder);
    }

    public CreateTableStatement createTable(TableName tableName, ListMultimap<String,Pair<String,Object>> props, List<ColumnDef> columns, PrimaryKeyConstraint pkConstraint, List<ParseNode> splits, PTableType tableType, boolean ifNotExists, TableName baseTableName, ParseNode tableTypeIdNode, int bindCount, Boolean immutableRows) {
        return new CreateTableStatement(tableName, props, columns, pkConstraint, splits, tableType, ifNotExists, baseTableName, tableTypeIdNode, bindCount, immutableRows);
    }

    public CreateSchemaStatement createSchema(String schemaName, boolean ifNotExists) {
        return new CreateSchemaStatement(schemaName, ifNotExists);
    }

    public CreateIndexStatement createIndex(NamedNode indexName, NamedTableNode dataTable, IndexKeyConstraint ikConstraint, List<ColumnName> includeColumns, List<ParseNode> splits, ListMultimap<String,Pair<String,Object>> props, boolean ifNotExists, IndexType indexType,boolean async, int bindCount, Map<String, UDFParseNode> udfParseNodes) {
        return new CreateIndexStatement(indexName, dataTable, ikConstraint, includeColumns, splits, props, ifNotExists, indexType, async, bindCount, udfParseNodes);
    }

    public CreateSequenceStatement createSequence(TableName tableName, ParseNode startsWith,
            ParseNode incrementBy, ParseNode cacheSize, ParseNode minValue, ParseNode maxValue,
            boolean cycle, boolean ifNotExits, int bindCount) {
        return new CreateSequenceStatement(tableName, startsWith, incrementBy, cacheSize, minValue,
                maxValue, cycle, ifNotExits, bindCount);
    }

    public CreateFunctionStatement createFunction(PFunction functionInfo, boolean temporary, boolean isReplace) {
        return new CreateFunctionStatement(functionInfo, temporary, isReplace);
    }

    public AddJarsStatement addJars(List<LiteralParseNode> jarPaths) {
        return new AddJarsStatement(jarPaths);
    }

    public ListJarsStatement listJars() {
        return new ListJarsStatement();
    }

    public DeleteJarStatement deleteJar(LiteralParseNode jarPath) {
        return new DeleteJarStatement(jarPath);
    }

    public DropFunctionStatement dropFunction(String functionName, boolean ifExists) {
        return new DropFunctionStatement(functionName, ifExists);
    }

    public DropSequenceStatement dropSequence(TableName tableName, boolean ifExits, int bindCount){
        return new DropSequenceStatement(tableName, ifExits, bindCount);
    }

    public SequenceValueParseNode currentValueFor(TableName tableName) {
        return new SequenceValueParseNode(tableName, SequenceValueParseNode.Op.CURRENT_VALUE, null);
    }

    public SequenceValueParseNode nextValueFor(TableName tableName, ParseNode numToAllocateNode) {
        return new SequenceValueParseNode(tableName, SequenceValueParseNode.Op.NEXT_VALUE, numToAllocateNode);
    }

    public AddColumnStatement addColumn(NamedTableNode table,  PTableType tableType, List<ColumnDef> columnDefs, boolean ifNotExists, ListMultimap<String,Pair<String,Object>> props) {
        return new AddColumnStatement(table, tableType, columnDefs, ifNotExists, props);
    }

    public DropColumnStatement dropColumn(NamedTableNode table,  PTableType tableType, List<ColumnName> columnNodes, boolean ifExists) {
        return new DropColumnStatement(table, tableType, columnNodes, ifExists);
    }

    public DropTableStatement dropTable(TableName tableName, PTableType tableType, boolean ifExists, boolean cascade) {
        return new DropTableStatement(tableName, tableType, ifExists, cascade, false);
    }

    public DropIndexStatement dropIndex(NamedNode indexName, TableName tableName, boolean ifExists) {
        return new DropIndexStatement(indexName, tableName, ifExists);
    }

    public AlterIndexStatement alterIndex(NamedTableNode indexTableNode, String dataTableName, boolean ifExists, PIndexState state, boolean isRebuildAll, boolean async, ListMultimap<String,Pair<String,Object>> props) {
        return new AlterIndexStatement(indexTableNode, dataTableName, ifExists, state, isRebuildAll, async, props);
    }

    public AlterIndexStatement alterIndex(NamedTableNode indexTableNode, String dataTableName, boolean ifExists, PIndexState state) {
        return new AlterIndexStatement(indexTableNode, dataTableName, ifExists, state, false, false);
    }

    public TraceStatement trace(boolean isTraceOn, double samplingRate) {
        return new TraceStatement(isTraceOn, samplingRate);
    }

    public AlterSessionStatement alterSession(Map<String,Object> props) {
        return new AlterSessionStatement(props);
    }

    public TableName table(String schemaName, String tableName) {
        return TableName.createNormalized(schemaName,tableName);
    }

    public NamedNode indexName(String name) {
        return new NamedNode(name);
    }

    @Deprecated
    public NamedTableNode namedTable(String alias, TableName name) {
        return new NamedTableNode(alias, name);
    }
    
    @Deprecated
    public NamedTableNode namedTable(String alias, TableName name, List<ColumnDef> dyn_columns) {
        return new NamedTableNode(alias, name,dyn_columns);
    }
    
    public NamedTableNode namedTable(String alias, TableName name, Double tableSamplingRate) {
        return new NamedTableNode(alias, name, tableSamplingRate);
    }
    
    public NamedTableNode namedTable(String alias, TableName name, List<ColumnDef> dyn_columns, Double tableSamplingRate) {
    	return new NamedTableNode(alias, name,dyn_columns, tableSamplingRate);
    }
    
    public NamedTableNode namedTable(String alias, TableName name, List<ColumnDef> dyn_columns, LiteralParseNode tableSampleNode) {
    	Double tableSamplingRate;
    	if(tableSampleNode==null||tableSampleNode.getValue()==null){
    		tableSamplingRate=ConcreteTableNode.DEFAULT_TABLE_SAMPLING_RATE;
    	}else if(tableSampleNode.getValue() instanceof Integer){
    		tableSamplingRate=(double)((int)tableSampleNode.getValue());
    	}else{
    		tableSamplingRate=((BigDecimal) tableSampleNode.getValue()).doubleValue();
    	}
    	return new NamedTableNode(alias, name, dyn_columns, tableSamplingRate);
    }

    public BindTableNode bindTable(String alias, TableName name) {
        return new BindTableNode(alias, name);
    }

    public CaseParseNode caseWhen(List<ParseNode> children) {
        return new CaseParseNode(children);
    }

    public DivideParseNode divide(List<ParseNode> children) {
        return new DivideParseNode(children);
    }

    public UpdateStatisticsStatement updateStatistics(NamedTableNode table, StatisticsCollectionScope scope, Map<String,Object> props) {
      return new UpdateStatisticsStatement(table, scope, props);
    }
    
    public ExecuteUpgradeStatement executeUpgrade() {
        return new ExecuteUpgradeStatement();
    }


    public FunctionParseNode functionDistinct(String name, List<ParseNode> args) {
        if (CountAggregateFunction.NAME.equals(SchemaUtil.normalizeIdentifier(name))) {
            BuiltInFunctionInfo info = getInfo(
                    SchemaUtil.normalizeIdentifier(DistinctCountAggregateFunction.NAME), args);
            return new DistinctCountParseNode(DistinctCountAggregateFunction.NAME, args, info);
        } else {
            throw new UnsupportedOperationException("DISTINCT not supported with " + name);
        }
    }

    public FunctionParseNode arrayElemRef(List<ParseNode> args) {
    	return function(ARRAY_ELEM, args);
    }

    public FunctionParseNode function(String name, List<ParseNode> args) {
        BuiltInFunctionInfo info = getInfo(name, args);
        if (info == null) {
            return new UDFParseNode(name, args, info);
        }
        Constructor<? extends FunctionParseNode> ctor = info.getNodeCtor();
        if (ctor == null) {
            return info.isAggregate()
            ? new AggregateFunctionParseNode(name, args, info)
            : new FunctionParseNode(name, args, info);
        } else {
            try {
                return ctor.newInstance(name, args, info);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    public FunctionParseNode function(String name, List<ParseNode> valueNodes,
            List<ParseNode> columnNodes, boolean isAscending) {

        List<ParseNode> args = Lists.newArrayListWithExpectedSize(columnNodes.size() + valueNodes.size() + 1);
        args.addAll(columnNodes);
        args.add(new LiteralParseNode(Boolean.valueOf(isAscending)));
        args.addAll(valueNodes);

        BuiltInFunctionInfo info = getInfo(name, args);
        if(info==null) {
            return new UDFParseNode(name,args,info);
        }
        Constructor<? extends FunctionParseNode> ctor = info.getNodeCtor();
        if (ctor == null) {
            return new AggregateFunctionWithinGroupParseNode(name, args, info);
        } else {
            try {
                return ctor.newInstance(name, args, info);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    public HintNode hint(String hint) {
        return new HintNode(hint);
    }

    public InListParseNode inList(List<ParseNode> children, boolean negate) {
        return new InListParseNode(children, negate);
    }

    public ExistsParseNode exists(ParseNode child, boolean negate) {
        return new ExistsParseNode(child, negate);
    }

    public InParseNode in(ParseNode l, ParseNode r, boolean negate, boolean isSubqueryDistinct) {
        return new InParseNode(l, r, negate, isSubqueryDistinct);
    }

    public IsNullParseNode isNull(ParseNode child, boolean negate) {
        return new IsNullParseNode(child, negate);
    }

    public JoinTableNode join(JoinType type, TableNode lhs, TableNode rhs, ParseNode on, boolean singleValueOnly) {
        return new JoinTableNode(type, lhs, rhs, on, singleValueOnly);
    }

    public DerivedTableNode derivedTable (String alias, SelectStatement select) {
        return new DerivedTableNode(alias, select);
    }

    public LikeParseNode like(ParseNode lhs, ParseNode rhs, boolean negate, LikeType likeType) {
        return new LikeParseNode(lhs, rhs, negate, likeType);
    }

    public LiteralParseNode literal(Object value) {
        return new LiteralParseNode(value);
    }

    public LiteralParseNode realNumber(String text) {
        return new LiteralParseNode(new BigDecimal(text, PDataType.DEFAULT_MATH_CONTEXT));
    }
    
    public LiteralParseNode wholeNumber(String text) {
        int length = text.length();
        // We know it'll fit into long, might still fit into int
        if (length <= PDataType.LONG_PRECISION-1) {
            long l = Long.parseLong(text);
            if (l <= Integer.MAX_VALUE) {
                // Fits into int
                return new LiteralParseNode((int)l);
            }
            return new LiteralParseNode(l);
        }
        // Might still fit into long
        BigDecimal d = new BigDecimal(text, PDataType.DEFAULT_MATH_CONTEXT);
        if (d.compareTo(MAX_LONG) <= 0) {
            return new LiteralParseNode(d.longValueExact());
        }
        // Doesn't fit into long
        return new LiteralParseNode(d);
    }

    public LiteralParseNode intOrLong(String text) {
        long l = Long.parseLong(text);
        if (l <= Integer.MAX_VALUE) {
            // Fits into int
            return new LiteralParseNode((int)l);
        }
        return new LiteralParseNode(l);
    }

    public CastParseNode cast(ParseNode expression, String dataType, Integer maxLength, Integer scale) {
        return new CastParseNode(expression, dataType, maxLength, scale, false);
    }

    public CastParseNode cast(ParseNode expression, PDataType dataType, Integer maxLength, Integer scale) {
        return new CastParseNode(expression, dataType, maxLength, scale, false);
    }

    public CastParseNode cast(ParseNode expression, PDataType dataType, Integer maxLength, Integer scale, boolean arr) {
        return new CastParseNode(expression, dataType, maxLength, scale, arr);
    }

    public CastParseNode cast(ParseNode expression, String dataType, Integer maxLength, Integer scale, boolean arr) {
        return new CastParseNode(expression, dataType, maxLength, scale, arr);
    }

    public RowValueConstructorParseNode rowValueConstructor(List<ParseNode> l) {
        return new RowValueConstructorParseNode(l);
    }

    private void checkTypeMatch (PDataType expectedType, PDataType actualType) throws SQLException {
        if (!expectedType.isCoercibleTo(actualType)) {
            throw TypeMismatchException.newException(expectedType, actualType);
        }
    }

    public LiteralParseNode literal(Object value, PDataType expectedType) throws SQLException {
        PDataType actualType = PDataType.fromLiteral(value);
        if (actualType != null && actualType != expectedType) {
            checkTypeMatch(expectedType, actualType);
            value = expectedType.toObject(value, actualType);
        }
        return new LiteralParseNode(value);
        /*
        Object typedValue = expectedType.toObject(value.toString());
        return new LiteralParseNode(typedValue);
        */
    }

    public LiteralParseNode literal(String value, String sqlTypeName) throws SQLException {
        PDataType expectedType = sqlTypeName == null ? null : PDataType.fromSqlTypeName(SchemaUtil.normalizeIdentifier(sqlTypeName));
        if (expectedType == null || !expectedType.isCoercibleTo(PTimestamp.INSTANCE)) {
            throw TypeMismatchException.newException(expectedType, PTimestamp.INSTANCE);
        }
        Object typedValue = expectedType.toObject(value);
        return new LiteralParseNode(typedValue);
    }

    public LiteralParseNode coerce(LiteralParseNode literalNode, PDataType expectedType) throws SQLException {
        PDataType actualType = literalNode.getType();
        if (actualType != null) {
            Object before = literalNode.getValue();
            checkTypeMatch(expectedType, actualType);
            Object after = expectedType.toObject(before, actualType);
            if (before != after) {
                literalNode = literal(after);
            }
        }
        return literalNode;
    }

    public ComparisonParseNode comparison(CompareOp op, ParseNode lhs, ParseNode rhs) {
        switch (op){
        case LESS:
            return lt(lhs,rhs);
        case LESS_OR_EQUAL:
            return lte(lhs,rhs);
        case EQUAL:
            return equal(lhs,rhs);
        case NOT_EQUAL:
            return notEqual(lhs,rhs);
        case GREATER_OR_EQUAL:
            return gte(lhs,rhs);
        case GREATER:
            return gt(lhs,rhs);
        default:
            throw new IllegalArgumentException("Unexpcted CompareOp of " + op);
        }
    }
    
    public ArrayAnyComparisonNode arrayAny(ParseNode rhs, ComparisonParseNode compareNode) {
        return new ArrayAnyComparisonNode(rhs, compareNode);
    }
    
    public ArrayAllComparisonNode arrayAll(ParseNode rhs, ComparisonParseNode compareNode) {
        return new ArrayAllComparisonNode(rhs, compareNode);
    }

    public ArrayAnyComparisonNode wrapInAny(CompareOp op, ParseNode lhs, ParseNode rhs) {
        return new ArrayAnyComparisonNode(rhs, comparison(op, lhs, elementRef(Arrays.<ParseNode>asList(rhs, literal(1)))));
    }

    public ArrayAllComparisonNode wrapInAll(CompareOp op, ParseNode lhs, ParseNode rhs) {
        return new ArrayAllComparisonNode(rhs, comparison(op, lhs, elementRef(Arrays.<ParseNode>asList(rhs, literal(1)))));
    }

    public ArrayElemRefNode elementRef(List<ParseNode> parseNode) {
        return new ArrayElemRefNode(parseNode);
    }

    public GreaterThanParseNode gt(ParseNode lhs, ParseNode rhs) {
        return new GreaterThanParseNode(lhs, rhs);
    }


    public GreaterThanOrEqualParseNode gte(ParseNode lhs, ParseNode rhs) {
        return new GreaterThanOrEqualParseNode(lhs, rhs);
    }

    public LessThanParseNode lt(ParseNode lhs, ParseNode rhs) {
        return new LessThanParseNode(lhs, rhs);
    }


    public LessThanOrEqualParseNode lte(ParseNode lhs, ParseNode rhs) {
        return new LessThanOrEqualParseNode(lhs, rhs);
    }

    public EqualParseNode equal(ParseNode lhs, ParseNode rhs) {
        return new EqualParseNode(lhs, rhs);
    }

    public ArrayConstructorNode upsertStmtArrayNode(List<ParseNode> upsertStmtArray) {
    	return new ArrayConstructorNode(upsertStmtArray);
    }

    public ParseNode negate(ParseNode child) {
        // Prevents reparsing of -1 from becoming 1*-1 and 1*1*-1 with each re-parsing
        if (LiteralParseNode.ONE.equals(child) && ((LiteralParseNode)child).getType().isCoercibleTo(
                PLong.INSTANCE)) {
            return LiteralParseNode.MINUS_ONE;
        }
        // Special case to convert Long.MIN_VALUE back to a Long. We can't initially represent it
        // as a Long in the parser because we only represent positive values as constants in the
        // parser, and ABS(Long.MIN_VALUE) is too big to fit into a Long. So we convert it back here.
        if (LiteralParseNode.MIN_LONG_AS_BIG_DECIMAL.equals(child)) {
            return LiteralParseNode.MIN_LONG;
        }
        return new MultiplyParseNode(Arrays.asList(child,LiteralParseNode.MINUS_ONE));
    }

    public NotEqualParseNode notEqual(ParseNode lhs, ParseNode rhs) {
        return new NotEqualParseNode(lhs, rhs);
    }

    public ParseNode not(ParseNode child) {
        if (child instanceof ExistsParseNode) {
            return exists(child.getChildren().get(0), !((ExistsParseNode) child).isNegate());
        }
        
        return new NotParseNode(child);
    }


    public OrParseNode or(List<ParseNode> children) {
        return new OrParseNode(children);
    }


    public OrderByNode orderBy(ParseNode expression, boolean nullsLast, boolean orderAscending) {
        return new OrderByNode(expression, nullsLast, orderAscending);
    }

    public SelectStatement select(TableNode from, HintNode hint, boolean isDistinct, List<AliasedNode> select, ParseNode where,
            List<ParseNode> groupBy, ParseNode having, List<OrderByNode> orderBy, LimitNode limit, OffsetNode offset, int bindCount, boolean isAggregate, 
            boolean hasSequence, List<SelectStatement> selects, Map<String, UDFParseNode> udfParseNodes) {

        return new SelectStatement(from, hint, isDistinct, select, where, groupBy == null ? Collections.<ParseNode>emptyList() : groupBy, having,
                orderBy == null ? Collections.<OrderByNode>emptyList() : orderBy, limit, offset, bindCount, isAggregate, hasSequence, selects == null ? Collections.<SelectStatement>emptyList() : selects, udfParseNodes);
    } 
    
    public UpsertStatement upsert(NamedTableNode table, HintNode hint, List<ColumnName> columns, List<ParseNode> values,
            SelectStatement select, int bindCount, 
            Map<String, UDFParseNode> udfParseNodes,
            List<Pair<ColumnName,ParseNode>> onDupKeyPairs) {
        return new UpsertStatement(table, hint, columns, values, select, bindCount, udfParseNodes, onDupKeyPairs);
    }

    public CursorName cursorName(String name){
        return new CursorName(name);
    }

    public DeclareCursorStatement declareCursor(CursorName cursor, SelectStatement select){
        return new DeclareCursorStatement(cursor, select);
    }

    public FetchStatement fetch(CursorName cursor, boolean isNext, int fetchLimit){
        return new FetchStatement(cursor, isNext, fetchLimit);
    }

    public OpenStatement open(CursorName cursor){
        return new OpenStatement(cursor);
    }

    public CloseStatement close(CursorName cursor){
        return new CloseStatement(cursor);
    }

    public DeleteStatement delete(NamedTableNode table, HintNode hint, ParseNode node, List<OrderByNode> orderBy, LimitNode limit, int bindCount, Map<String, UDFParseNode> udfParseNodes) {
        return new DeleteStatement(table, hint, node, orderBy, limit, bindCount, udfParseNodes);
    }

    public SelectStatement select(SelectStatement statement, ParseNode where) {
        return select(statement.getFrom(), statement.getHint(), statement.isDistinct(), statement.getSelect(), where, statement.getGroupBy(), statement.getHaving(),
                statement.getOrderBy(), statement.getLimit(), statement.getOffset(), statement.getBindCount(), statement.isAggregate(), statement.hasSequence(), statement.getSelects(), statement.getUdfParseNodes());
    }

    public SelectStatement select(SelectStatement statement, ParseNode where, ParseNode having) {
        return select(statement.getFrom(), statement.getHint(), statement.isDistinct(), statement.getSelect(), where, statement.getGroupBy(), having,
                statement.getOrderBy(), statement.getLimit(), statement.getOffset(), statement.getBindCount(), statement.isAggregate(), statement.hasSequence(), statement.getSelects(), statement.getUdfParseNodes());
    }
    
    public SelectStatement select(SelectStatement statement, List<AliasedNode> select, ParseNode where, List<ParseNode> groupBy, ParseNode having, List<OrderByNode> orderBy) {
        return select(statement.getFrom(), statement.getHint(), statement.isDistinct(), 
                select, where, groupBy, having, orderBy, statement.getLimit(), statement.getOffset(), statement.getBindCount(), statement.isAggregate(), statement.hasSequence(), statement.getSelects(), statement.getUdfParseNodes());
    }
    
    public SelectStatement select(SelectStatement statement, TableNode table) {
        return select(table, statement.getHint(), statement.isDistinct(), statement.getSelect(), statement.getWhere(), statement.getGroupBy(),
                statement.getHaving(), statement.getOrderBy(), statement.getLimit(), statement.getOffset(), statement.getBindCount(), statement.isAggregate(),
                statement.hasSequence(), statement.getSelects(), statement.getUdfParseNodes());
    }

    public SelectStatement select(SelectStatement statement, TableNode table, ParseNode where) {
        return select(table, statement.getHint(), statement.isDistinct(), statement.getSelect(), where, statement.getGroupBy(),
                statement.getHaving(), statement.getOrderBy(), statement.getLimit(), statement.getOffset(), statement.getBindCount(), statement.isAggregate(),
                statement.hasSequence(), statement.getSelects(), statement.getUdfParseNodes());
    }

    public SelectStatement select(SelectStatement statement, boolean isDistinct, List<AliasedNode> select) {
        return select(statement.getFrom(), statement.getHint(), isDistinct, select, statement.getWhere(), statement.getGroupBy(),
                statement.getHaving(), statement.getOrderBy(), statement.getLimit(), statement.getOffset(), statement.getBindCount(), statement.isAggregate(),
                statement.hasSequence(), statement.getSelects(), statement.getUdfParseNodes());
    }

    public SelectStatement select(SelectStatement statement, boolean isDistinct, List<AliasedNode> select, ParseNode where) {
        return select(statement.getFrom(), statement.getHint(), isDistinct, select, where, statement.getGroupBy(),
                statement.getHaving(), statement.getOrderBy(), statement.getLimit(), statement.getOffset(), statement.getBindCount(), statement.isAggregate(),
                statement.hasSequence(), statement.getSelects(), statement.getUdfParseNodes());
    }

    public SelectStatement select(SelectStatement statement, boolean isDistinct, List<AliasedNode> select, ParseNode where, List<ParseNode> groupBy, boolean isAggregate) {
        return select(statement.getFrom(), statement.getHint(), isDistinct, select, where, groupBy,
                statement.getHaving(), statement.getOrderBy(), statement.getLimit(), statement.getOffset(), statement.getBindCount(), isAggregate,
                statement.hasSequence(), statement.getSelects(), statement.getUdfParseNodes());
    }

    public SelectStatement select(SelectStatement statement, List<OrderByNode> orderBy) {
        return select(statement.getFrom(), statement.getHint(), statement.isDistinct(), statement.getSelect(),
                statement.getWhere(), statement.getGroupBy(), statement.getHaving(), orderBy, statement.getLimit(),
                statement.getOffset(), statement.getBindCount(), statement.isAggregate(), statement.hasSequence(), statement.getSelects(), statement.getUdfParseNodes());
    }

    public SelectStatement select(SelectStatement statement, HintNode hint) {
        return hint == null || hint.isEmpty() ? statement : select(statement.getFrom(), hint, statement.isDistinct(), statement.getSelect(),
                statement.getWhere(), statement.getGroupBy(), statement.getHaving(), statement.getOrderBy(), statement.getLimit(), statement.getOffset(), 
                statement.getBindCount(), statement.isAggregate(), statement.hasSequence(), statement.getSelects(), statement.getUdfParseNodes());
    }

    public SelectStatement select(SelectStatement statement, HintNode hint, ParseNode where) {
        return select(statement.getFrom(), hint, statement.isDistinct(), statement.getSelect(), where, statement.getGroupBy(),
                statement.getHaving(), statement.getOrderBy(), statement.getLimit(), statement.getOffset(), statement.getBindCount(), statement.isAggregate(),
                statement.hasSequence(), statement.getSelects(), statement.getUdfParseNodes());
    }

    public SelectStatement select(SelectStatement statement, List<OrderByNode> orderBy, LimitNode limit, OffsetNode offset, int bindCount, boolean isAggregate) {
        return select(statement.getFrom(), statement.getHint(), statement.isDistinct(), statement.getSelect(),
            statement.getWhere(), statement.getGroupBy(), statement.getHaving(), orderBy, limit, offset,
            bindCount, isAggregate || statement.isAggregate(), statement.hasSequence(), statement.getSelects(), statement.getUdfParseNodes());

    }

    public SelectStatement select(SelectStatement statement, LimitNode limit) {
        return select(statement.getFrom(), statement.getHint(), statement.isDistinct(), statement.getSelect(),
                statement.getWhere(), statement.getGroupBy(), statement.getHaving(), statement.getOrderBy(), limit,
                statement.getOffset(), statement.getBindCount(), statement.isAggregate(), statement.hasSequence(),
                statement.getSelects(), statement.getUdfParseNodes());
    }

    public SelectStatement select(SelectStatement statement, List<OrderByNode> orderBy, LimitNode limit, OffsetNode offset) {
        return select(statement.getFrom(), statement.getHint(), statement.isDistinct(), statement.getSelect(),
            statement.getWhere(), statement.getGroupBy(), statement.getHaving(), orderBy, limit,offset,
            statement.getBindCount(), statement.isAggregate(), statement.hasSequence(), statement.getSelects(), statement.getUdfParseNodes());
    }

    public SelectStatement select(List<SelectStatement> statements, List<OrderByNode> orderBy, LimitNode limit,
            OffsetNode offset, int bindCount, boolean isAggregate) {
        if (statements.size() == 1) return select(statements.get(0), orderBy, limit, offset, bindCount, isAggregate);        

        // Get a list of adjusted aliases from a non-wildcard sub-select if any. 
        // We do not check the number of select nodes among all sub-selects, as 
        // it will be done later at compile stage. Empty or different aliases 
        // are ignored, since they cannot be referred by outer queries.
        List<String> aliases = Lists.<String> newArrayList();
        Map<String, UDFParseNode> udfParseNodes = new HashMap<String, UDFParseNode>(1);
        for (int i = 0; i < statements.size() && aliases.isEmpty(); i++) {
            SelectStatement subselect = statements.get(i);
            udfParseNodes.putAll(subselect.getUdfParseNodes());
            if (!subselect.hasWildcard()) {
                for (AliasedNode aliasedNode : subselect.getSelect()) {
                    String alias = aliasedNode.getAlias();
                    if (alias == null) {
                        alias = SchemaUtil.normalizeIdentifier(aliasedNode.getNode().getAlias());
                    }
                    aliases.add(alias == null ? createTempAlias() : alias);
                }
            }
        }

        List<AliasedNode> aliasedNodes;
        if (aliases.isEmpty()) {
            aliasedNodes = Lists.newArrayList(aliasedNode(null, wildcard()));
        } else {
            aliasedNodes = Lists.newArrayListWithExpectedSize(aliases.size());
            for (String alias : aliases) {
                aliasedNodes.add(aliasedNode(alias, column(null, alias, alias)));
            }
        }
        
        return select(null, HintNode.EMPTY_HINT_NODE, false, aliasedNodes, 
                null, null, null, orderBy, limit,offset, bindCount, false, false, statements, udfParseNodes);
    }

    public SubqueryParseNode subquery(SelectStatement select, boolean expectSingleRow) {
        return new SubqueryParseNode(select, expectSingleRow);
    }

    public LimitNode limit(BindParseNode b) {
        return new LimitNode(b);
    }

    public LimitNode limit(LiteralParseNode l) {
        return new LimitNode(l);
    }

    public OffsetNode offset(BindParseNode b) throws SQLException {
        return new OffsetNode(b);
    }

    public OffsetNode offset(LiteralParseNode l) throws SQLException {
        return new OffsetNode(l);
    }

    public OffsetNode offset(ComparisonParseNode r) throws SQLException {
        return new OffsetNode(r);
    }

    public DropSchemaStatement dropSchema(String schemaName, boolean ifExists, boolean cascade) {
        return new DropSchemaStatement(schemaName, ifExists, cascade);
    }

    public UseSchemaStatement useSchema(String schemaName) {
        return new UseSchemaStatement(schemaName);
    }

    public ChangePermsStatement changePermsStatement(String permsString, boolean isSchemaName, TableName tableName
            , String schemaName, boolean isGroupName, LiteralParseNode userOrGroup, boolean isGrantStatement) {
        return new ChangePermsStatement(permsString, isSchemaName, tableName, schemaName, isGroupName, userOrGroup, isGrantStatement);
    }

    public ShowTablesStatement showTablesStatement(String schema, String pattern) {
        return new ShowTablesStatement(schema, pattern);
    }

    public ShowSchemasStatement showSchemasStatement(String pattern) {
        return new ShowSchemasStatement(pattern);
    }
}