/* Copyright (c) 2001-2018, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb;

import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.HsqlNameManager.SimpleName;
import org.hsqldb.ParserDQL.CompileContext;
import org.hsqldb.RangeGroup.RangeGroupSimple;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.index.Index;
import org.hsqldb.lib.ArrayListIdentity;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.HsqlList;
import org.hsqldb.lib.OrderedHashSet;
import org.hsqldb.lib.OrderedIntHashSet;
import org.hsqldb.lib.Set;
import org.hsqldb.navigator.RowSetNavigatorData;
import org.hsqldb.persist.PersistentStore;
import org.hsqldb.result.Result;
import org.hsqldb.types.ArrayType;
import org.hsqldb.types.CharacterType;
import org.hsqldb.types.Collation;
import org.hsqldb.types.NullType;
import org.hsqldb.types.RowType;
import org.hsqldb.types.Type;
import org.hsqldb.types.Types;

/**
 * Expression class.
 *
 * @author Campbell Burnet (campbell-burnet@users dot sourceforge.net)
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @version 2.4.1
 * @since 1.9.0
 */
public class Expression implements Cloneable {

    public static final int LEFT    = 0;
    public static final int RIGHT   = 1;
    public static final int THIRD   = 2;
    public static final int UNARY   = 1;
    public static final int BINARY  = 2;
    public static final int TERNARY = 3;

    //
    //
    static final Expression[] emptyArray = new Expression[]{};

    //
    static final Expression EXPR_TRUE  = new ExpressionLogical(true);
    static final Expression EXPR_FALSE = new ExpressionLogical(false);

    //
    static final OrderedIntHashSet aggregateFunctionSet =
        new OrderedIntHashSet();

    static {
        aggregateFunctionSet.add(OpTypes.COUNT);
        aggregateFunctionSet.add(OpTypes.AVG);
        aggregateFunctionSet.add(OpTypes.MAX);
        aggregateFunctionSet.add(OpTypes.MIN);
        aggregateFunctionSet.add(OpTypes.SUM);
        aggregateFunctionSet.add(OpTypes.EVERY);
        aggregateFunctionSet.add(OpTypes.SOME);
        aggregateFunctionSet.add(OpTypes.STDDEV_POP);
        aggregateFunctionSet.add(OpTypes.STDDEV_SAMP);
        aggregateFunctionSet.add(OpTypes.VAR_POP);
        aggregateFunctionSet.add(OpTypes.VAR_SAMP);
        aggregateFunctionSet.add(OpTypes.ARRAY_AGG);
        aggregateFunctionSet.add(OpTypes.USER_AGGREGATE);
        aggregateFunctionSet.add(OpTypes.GROUP_CONCAT);
        aggregateFunctionSet.add(OpTypes.MEDIAN);
    }

    static final OrderedIntHashSet columnExpressionSet =
        new OrderedIntHashSet();

    static {
        columnExpressionSet.add(OpTypes.COLUMN);
    }

    static final OrderedIntHashSet subqueryExpressionSet =
        new OrderedIntHashSet();

    static {
        subqueryExpressionSet.add(OpTypes.ROW_SUBQUERY);
        subqueryExpressionSet.add(OpTypes.TABLE_SUBQUERY);
    }

    static final OrderedIntHashSet subqueryAggregateExpressionSet =
        new OrderedIntHashSet();

    static {
        subqueryAggregateExpressionSet.addAll(aggregateFunctionSet);
        subqueryAggregateExpressionSet.addAll(subqueryExpressionSet);
    }

    static final OrderedIntHashSet functionExpressionSet =
        new OrderedIntHashSet();

    static {
        functionExpressionSet.add(OpTypes.SQL_FUNCTION);
        functionExpressionSet.add(OpTypes.FUNCTION);
    }

    static final OrderedIntHashSet sequenceExpressionSet =
        new OrderedIntHashSet();

    static {
        sequenceExpressionSet.add(OpTypes.ROWNUM);
        sequenceExpressionSet.add(OpTypes.SEQUENCE);
    }

    static final OrderedIntHashSet emptyExpressionSet =
        new OrderedIntHashSet();

    // type
    protected int opType;

    // type qualifier
    protected int exprSubType;

    //
    SimpleName alias;

    // aggregate
    private boolean hasAggregate;
    boolean         isDistinctAggregate;

    // VALUE
    protected Object       valueData;
    protected Expression[] nodes;
    Type[]                 nodeDataTypes;
    TableDerived           table;

    // for query and value lists, etc
    boolean isCorrelated;

    // for COLUMN
    int columnIndex = -1;

    // data type
    protected Type dataType;

    //
    int queryTableColumnIndex = -1;    // >= 0 when it is used for order by

    // self position in the result table
    int resultTableColumnIndex = -1;

    // index of a session-dependent field
    int parameterIndex = -1;

    //
    int rangePosition = -1;

    //
    boolean isColumnCondition;
    boolean isColumnEqual;
    boolean isSingleColumnCondition;
    boolean isSingleColumnEqual;
    boolean isSingleColumnNull;
    boolean isSingleColumnNotNull;
    byte    nullability = SchemaObject.Nullability.NULLABLE_UNKNOWN;

    //
    Collation    collation;
    RangeGroup[] rangeGroups;
    RangeGroup   rangeGroup;

    Expression(int type) {
        opType = type;
        nodes  = emptyArray;
    }

    // IN condition optimisation

    /**
     * Creates a SUBQUERY expression.
     */
    Expression(int type, TableDerived table) {

        switch (type) {

            case OpTypes.ARRAY :
                opType = OpTypes.ARRAY;
                break;

            case OpTypes.ARRAY_SUBQUERY :
                opType = OpTypes.ARRAY_SUBQUERY;
                break;

            case OpTypes.TABLE_SUBQUERY :
                opType = OpTypes.TABLE_SUBQUERY;
                break;

            case OpTypes.ROW_SUBQUERY :
            case OpTypes.SCALAR_SUBQUERY :
                opType = OpTypes.ROW_SUBQUERY;
                break;

            default :
                throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
        }

        nodes      = emptyArray;
        this.table = table;
    }

    /**
     * ROW, ARRAY etc.
     */
    Expression(int type, Expression[] list) {

        this(type);

        this.nodes = list;
    }

    static String getContextSQL(Expression expression) {

        if (expression == null) {
            return null;
        }

        String ddl = expression.getSQL();

        switch (expression.opType) {

            case OpTypes.VALUE :
            case OpTypes.COLUMN :
            case OpTypes.ROW :
            case OpTypes.FUNCTION :
            case OpTypes.SQL_FUNCTION :
            case OpTypes.ALTERNATIVE :
            case OpTypes.CASEWHEN :
            case OpTypes.CAST :
                return ddl;
        }

        StringBuffer sb = new StringBuffer();

        ddl = sb.append('(').append(ddl).append(')').toString();

        return ddl;
    }

    /**
     * For use with CHECK constraints. Under development.
     *
     * Currently supports a subset of expressions and is suitable for CHECK
     * search conditions that refer only to the inserted/updated row.
     *
     * For full DDL reporting of VIEW select queries and CHECK search
     * conditions, future improvements here are dependent upon improvements to
     * SELECT query parsing, so that it is performed in a number of passes.
     * An early pass should result in the query turned into an Expression tree
     * that contains the information in the original SQL without any
     * alterations, and with tables and columns all resolved. This Expression
     * can then be preserved for future use. Table and column names that
     * are not user-defined aliases should be kept as the HsqlName structures
     * so that table or column renaming is reflected in the precompiled
     * query.
     */
    public String getSQL() {

        StringBuffer sb = new StringBuffer(64);

        switch (opType) {

            case OpTypes.VALUE :
                if (valueData == null) {
                    return Tokens.T_NULL;
                }

                return dataType.convertToSQLString(valueData);

            case OpTypes.ROW :
                sb.append('(');

                for (int i = 0; i < nodes.length; i++) {
                    if (i > 0) {
                        sb.append(',');
                    }

                    sb.append(nodes[i].getSQL());
                }

                sb.append(')');

                return sb.toString();

            //
            case OpTypes.VALUELIST :
                for (int i = 0; i < nodes.length; i++) {
                    if (i > 0) {
                        sb.append(',');
                    }

                    sb.append(nodes[i].getSQL());
                }

                return sb.toString();
        }

        switch (opType) {

            case OpTypes.ARRAY :
                sb.append(Tokens.T_ARRAY).append('[');

                for (int i = 0; i < nodes.length; i++) {
                    if (i > 0) {
                        sb.append(',');
                    }

                    sb.append(nodes[i].getSQL());
                }

                sb.append(']');
                break;

            case OpTypes.ARRAY_SUBQUERY :

            //
            case OpTypes.ROW_SUBQUERY :
            case OpTypes.TABLE_SUBQUERY :
                sb.append('(');
                sb.append(')');
                break;

            default :
                throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
        }

        return sb.toString();
    }

    protected String describe(Session session, int blanks) {

        StringBuffer sb = new StringBuffer(64);

        sb.append('\n');

        for (int i = 0; i < blanks; i++) {
            sb.append(' ');
        }

        switch (opType) {

            case OpTypes.VALUE :
                sb.append("VALUE = ").append(
                    dataType.convertToSQLString(valueData));
                sb.append(", TYPE = ").append(dataType.getNameString());

                return sb.toString();

            case OpTypes.ARRAY :
                sb.append("ARRAY ");

                return sb.toString();

            case OpTypes.ARRAY_SUBQUERY :
                sb.append("ARRAY SUBQUERY");

                return sb.toString();

            //
            case OpTypes.ROW_SUBQUERY :
            case OpTypes.TABLE_SUBQUERY :
                sb.append("QUERY ");
                sb.append(table.queryExpression.describe(session, blanks));

                return sb.toString();

            case OpTypes.ROW :
                sb.append("ROW = ");

                for (int i = 0; i < nodes.length; i++) {
                    sb.append(nodes[i].describe(session, blanks + 1));
                    sb.append(' ');
                }
                break;

            case OpTypes.VALUELIST :
                sb.append("VALUELIST ");

                for (int i = 0; i < nodes.length; i++) {
                    sb.append(nodes[i].describe(session, blanks + 1));
                    sb.append(' ');
                }
                break;
        }

        return sb.toString();
    }

    /**
     * Set the data type
     */
    void setDataType(Session session, Type type) {

        if (opType == OpTypes.VALUE) {
            valueData = type.convertToType(session, valueData, dataType);
        }

        dataType = type;
    }

    /**
     * SIMPLE_COLUMN expressions can be of different Expression subclass types
     */
    public boolean equals(Expression other) {

        if (other == this) {
            return true;
        }

        if (other == null) {
            return false;
        }

        if (opType != other.opType || exprSubType != other.exprSubType
                || !equals(dataType, other.dataType)) {
            return false;
        }

        switch (opType) {

            case OpTypes.VALUE :
                return equals(valueData, other.valueData);

            case OpTypes.ARRAY :

            //
            case OpTypes.ARRAY_SUBQUERY :
            case OpTypes.ROW_SUBQUERY :
            case OpTypes.TABLE_SUBQUERY :
                return table.queryExpression.isEquivalent(
                    other.table.queryExpression);

            default :
                return equals(nodes, other.nodes);
        }
    }

    public boolean equals(Object other) {

        if (other == this) {
            return true;
        }

        if (other instanceof Expression) {
            return equals((Expression) other);
        }

        return false;
    }

    public int hashCode() {

        int val = opType + exprSubType;

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] != null) {
                val += nodes[i].hashCode();
            }
        }

        return val;
    }

    static boolean equals(Object o1, Object o2) {

        if (o1 == o2) {
            return true;
        }

        return (o1 == null) ? false
                            : o1.equals(o2);
    }

    static boolean equals(Expression[] row1, Expression[] row2) {

        if (row1 == row2) {
            return true;
        }

        if (row1.length != row2.length) {
            return false;
        }

        int len = row1.length;

        for (int i = 0; i < len; i++) {
            Expression e1     = row1[i];
            Expression e2     = row2[i];
            boolean    equals = (e1 == null) ? e2 == null
                                             : e1.equals(e2);

            if (!equals) {
                return false;
            }
        }

        return true;
    }

    /**
     * For GROUP only.
     */
    boolean isComposedOf(Expression[] exprList, int start, int end,
                         OrderedIntHashSet excludeSet) {

        switch (opType) {

            case OpTypes.VALUE :
            case OpTypes.DYNAMIC_PARAM :
            case OpTypes.PARAMETER :
            case OpTypes.VARIABLE : {
                return true;
            }
        }

        if (excludeSet.contains(opType)) {
            return true;
        }

        for (int i = start; i < end; i++) {
            if (equals(exprList[i])) {
                return true;
            }
        }

        switch (opType) {

            case OpTypes.COLUMN :
                return false;

            case OpTypes.LIKE :
            case OpTypes.MATCH_SIMPLE :
            case OpTypes.MATCH_PARTIAL :
            case OpTypes.MATCH_FULL :
            case OpTypes.MATCH_UNIQUE_SIMPLE :
            case OpTypes.MATCH_UNIQUE_PARTIAL :
            case OpTypes.MATCH_UNIQUE_FULL :
            case OpTypes.UNIQUE :
            case OpTypes.EXISTS :
            case OpTypes.ARRAY :
            case OpTypes.ARRAY_SUBQUERY :
            case OpTypes.TABLE_SUBQUERY :
                return false;

            case OpTypes.ROW_SUBQUERY : {
                if (table == null) {
                    break;
                }

                if (!(table.getQueryExpression()
                        instanceof QuerySpecification)) {
                    return false;
                }

                QuerySpecification qs =
                    (QuerySpecification) table.getQueryExpression();
                OrderedHashSet set = new OrderedHashSet();

                for (int i = start; i < end; i++) {
                    if (exprList[i].opType == OpTypes.COLUMN) {
                        set.add(exprList[i]);
                    }
                }

                return qs.collectOuterColumnExpressions(null, set) == null;
            }
        }

        if (aggregateFunctionSet.contains(opType)) {
            return false;
        }

        if (nodes.length == 0) {
            return true;
        }

        boolean result = true;

        for (int i = 0; i < nodes.length; i++) {
            result &= (nodes[i] == null
                       || nodes[i].isComposedOf(exprList, start, end,
                                                excludeSet));
        }

        return result;
    }

    /**
     * For HAVING only.
     */
    boolean isComposedOf(OrderedHashSet expressions, RangeGroup[] rangeGroups,
                         OrderedIntHashSet excludeSet) {

        switch (opType) {

            case OpTypes.VALUE :
            case OpTypes.DYNAMIC_PARAM :
            case OpTypes.PARAMETER :
            case OpTypes.VARIABLE : {
                return true;
            }
        }

        if (excludeSet.contains(opType)) {
            return true;
        }

        for (int i = 0; i < expressions.size(); i++) {
            if (equals(expressions.get(i))) {
                return true;
            }
        }

        if (opType == OpTypes.COLUMN) {
            for (int i = 0; i < rangeGroups.length; i++) {
                RangeVariable[] ranges = rangeGroups[i].getRangeVariables();

                for (int j = 0; j < ranges.length; j++) {
                    if (ranges[j] == getRangeVariable()) {
                        return true;
                    }
                }
            }
        }

        switch (opType) {

            case OpTypes.FUNCTION :
            case OpTypes.SQL_FUNCTION :
                if (nodes.length == 0) {
                    return true;
                }
        }

/*
        case OpCodes.LIKE :
        case OpCodes.ALL :
        case OpCodes.ANY :
        case OpCodes.IN :
        case OpCodes.MATCH_SIMPLE :
        case OpCodes.MATCH_PARTIAL :
        case OpCodes.MATCH_FULL :
        case OpCodes.MATCH_UNIQUE_SIMPLE :
        case OpCodes.MATCH_UNIQUE_PARTIAL :
        case OpCodes.MATCH_UNIQUE_FULL :
        case OpCodes.UNIQUE :
        case OpCodes.EXISTS :
        case OpCodes.TABLE_SUBQUERY :
        case OpCodes.ROW_SUBQUERY :
*/
        if (aggregateFunctionSet.contains(opType)) {
            return false;
        }

        if (nodes.length == 0) {
            return false;
        }

        boolean result = true;

        for (int i = 0; i < nodes.length; i++) {
            result &= (nodes[i] == null
                       || nodes[i].isComposedOf(expressions, rangeGroups,
                                                excludeSet));
        }

        return result;
    }

    Expression replaceColumnReferences(RangeVariable range,
                                       Expression[] list) {

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] == null) {
                continue;
            }

            nodes[i] = nodes[i].replaceColumnReferences(range, list);
        }

        if (table != null && table.queryExpression != null) {
            table.queryExpression.replaceColumnReferences(range, list);
        }

        return this;
    }

    void replaceRangeVariables(RangeVariable[] ranges,
                               RangeVariable[] newRanges) {

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] == null) {
                continue;
            }

            nodes[i].replaceRangeVariables(ranges, newRanges);
        }

        if (table != null && table.queryExpression != null) {
            table.queryExpression.replaceRangeVariables(ranges, newRanges);
        }
    }

    void resetColumnReferences() {

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] == null) {
                continue;
            }

            nodes[i].resetColumnReferences();
        }
    }

    Expression replaceExpressions(OrderedHashSet expressions,
                                  int resultRangePosition) {

        if (opType == OpTypes.VALUE) {
            return this;
        }

        if (opType == OpTypes.SIMPLE_COLUMN) {
            return this;
        }

        Expression exp = (Expression) expressions.get(this);

        if (exp != null) {
            Expression col = new ExpressionColumn(this,
                                                  exp.resultTableColumnIndex,
                                                  resultRangePosition);

            return col;
        }

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] == null) {
                continue;
            }

            nodes[i] = nodes[i].replaceExpressions(expressions,
                                                   resultRangePosition);
        }

        if (table != null) {
            if (table.queryExpression != null) {
                table.queryExpression.replaceExpressions(expressions,
                        resultRangePosition);
            }
        }

        return this;
    }

    boolean hasAggregate() {
        return hasAggregate;
    }

    boolean isDistinctAggregate() {
        return isDistinctAggregate;
    }

    void setAggregate() {
        hasAggregate = true;
    }

    boolean isSelfAggregate() {
        return false;
    }

    /**
     * Set the column alias
     */
    void setAlias(SimpleName name) {
        alias = name;
    }

    /**
     * Get the column alias
     */
    String getAlias() {

        if (alias != null) {
            return alias.name;
        }

        return "";
    }

    SimpleName getSimpleName() {
        return alias;
    }

    /**
     * Returns the type of expression
     */
    public int getType() {
        return opType;
    }

    /**
     * Returns the left node
     */
    Expression getLeftNode() {
        return nodes.length > 0 ? nodes[LEFT]
                                : null;
    }

    /**
     * Returns the right node
     */
    Expression getRightNode() {
        return nodes.length > 1 ? nodes[RIGHT]
                                : null;
    }

    void setLeftNode(Expression e) {
        nodes[LEFT] = e;
    }

    void setRightNode(Expression e) {
        nodes[RIGHT] = e;
    }

    int getSubType() {
        return exprSubType;
    }

    void setSubType(int subType) {
        exprSubType = subType;
    }

    /**
     * Returns the range variable for a COLUMN expression
     */
    RangeVariable getRangeVariable() {
        return null;
    }

    /**
     * return the expression for an alias used in an ORDER BY clause
     */
    Expression replaceAliasInOrderBy(Session session, Expression[] columns,
                                     int length) {

        if (isSelfAggregate()) {
            return this;
        }

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] == null) {
                continue;
            }

            nodes[i] = nodes[i].replaceAliasInOrderBy(session, columns,
                    length);
        }

        return this;
    }

    /**
     * collects all range variables in expression tree
     */
    OrderedHashSet collectRangeVariables(OrderedHashSet set) {

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] != null) {
                set = nodes[i].collectRangeVariables(set);
            }
        }

        if (table != null && table.queryExpression != null) {
            set = table.queryExpression.collectRangeVariables(set);
        }

        return set;
    }

    OrderedHashSet collectRangeVariables(RangeVariable[] rangeVariables,
                                         OrderedHashSet set) {

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] != null) {
                set = nodes[i].collectRangeVariables(rangeVariables, set);
            }
        }

        if (table != null && table.queryExpression != null) {
            set = table.queryExpression.collectRangeVariables(rangeVariables,
                    set);
        }

        return set;
    }

    /**
     * collects all schema objects
     */
    void collectObjectNames(Set set) {

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] != null) {
                nodes[i].collectObjectNames(set);
            }
        }

        if (table != null) {
            if (table.queryExpression != null) {
                table.queryExpression.collectObjectNames(set);
            }
        }
    }

    /**
     * return true if given RangeVariable is used in expression tree
     */
    boolean hasReference(RangeVariable range) {

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] != null) {
                if (nodes[i].hasReference(range)) {
                    return true;
                }
            }
        }

        if (table != null && table.queryExpression != null) {
            if (table.queryExpression.hasReference(range)) {
                return true;
            }
        }

        return false;
    }

    /**
     * return true if given RangeVariable is used in expression tree
     */
    boolean hasReference(RangeVariable[] ranges, int exclude) {

        OrderedHashSet set = collectRangeVariables(ranges, null);

        if (set == null) {
            return false;
        }

        for (int j = 0; j < set.size(); j++) {
            if (set.get(j) != ranges[exclude]) {
                return true;
            }
        }

        return false;
    }

    /**
     * resolve tables and collect unresolved column expressions
     */
    public HsqlList resolveColumnReferences(Session session,
            RangeGroup rangeGroup, RangeGroup[] rangeGroups,
            HsqlList unresolvedSet) {

        return resolveColumnReferences(session, rangeGroup,
                                       rangeGroup.getRangeVariables().length,
                                       rangeGroups, unresolvedSet, true);
    }

    public HsqlList resolveColumnReferences(Session session,
            RangeGroup rangeGroup, int rangeCount, RangeGroup[] rangeGroups,
            HsqlList unresolvedSet, boolean acceptsSequences) {

        if (opType == OpTypes.VALUE) {
            return unresolvedSet;
        }

        switch (opType) {

            case OpTypes.TABLE :
            case OpTypes.VALUELIST : {
                if (table != null) {
                    if (rangeGroup.getRangeVariables().length > rangeCount) {
                        RangeVariable[] rangeVars =
                            (RangeVariable[]) ArrayUtil.resizeArray(
                                rangeGroup.getRangeVariables(), rangeCount);

                        rangeGroup = new RangeGroupSimple(rangeVars,
                                                          rangeGroup);
                    }

                    rangeGroups =
                        (RangeGroup[]) ArrayUtil.toAdjustedArray(rangeGroups,
                            rangeGroup, rangeGroups.length, 1);
                    rangeGroup = new RangeGroupSimple(table);
                    rangeCount = 0;
                }

                for (int i = 0; i < nodes.length; i++) {
                    if (nodes[i] == null) {
                        continue;
                    }

                    unresolvedSet = nodes[i].resolveColumnReferences(session,
                            rangeGroup, rangeCount, rangeGroups,
                            unresolvedSet, acceptsSequences);
                }

                return unresolvedSet;
            }
        }

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] == null) {
                continue;
            }

            unresolvedSet = nodes[i].resolveColumnReferences(session,
                    rangeGroup, rangeCount, rangeGroups, unresolvedSet,
                    acceptsSequences);
        }

        switch (opType) {

            case OpTypes.ARRAY :
                break;

            case OpTypes.ARRAY_SUBQUERY :
            case OpTypes.ROW_SUBQUERY :
            case OpTypes.TABLE_SUBQUERY : {
                RangeVariable[] rangeVars = rangeGroup.getRangeVariables();

                if (rangeVars.length > rangeCount) {
                    rangeVars =
                        (RangeVariable[]) ArrayUtil.resizeArray(rangeVars,
                            rangeCount);
                    rangeGroup = new RangeGroupSimple(rangeVars, rangeGroup);
                }

                rangeGroups =
                    (RangeGroup[]) ArrayUtil.toAdjustedArray(rangeGroups,
                        rangeGroup, rangeGroups.length, 1);

                QueryExpression queryExpression = table.queryExpression;

                if (queryExpression != null) {
                    queryExpression.resolveReferences(session, rangeGroups);

                    if (!queryExpression.areColumnsResolved()) {
                        if (unresolvedSet == null) {
                            unresolvedSet = new ArrayListIdentity();
                        }

                        unresolvedSet.addAll(
                            queryExpression.getUnresolvedExpressions());
                    }
                }

                Expression dataExpression = table.dataExpression;

                if (dataExpression != null) {
                    unresolvedSet =
                        dataExpression.resolveColumnReferences(session,
                            rangeGroup, rangeCount, rangeGroups,
                            unresolvedSet, acceptsSequences);
                }

                break;
            }
            default :
        }

        return unresolvedSet;
    }

    public void setCorrelatedReferences(RangeGroup resolvedRangeGroup) {

        if (rangeGroups == null) {
            for (int i = 0; i < nodes.length; i++) {
                if (nodes[i] != null) {
                    nodes[i].setCorrelatedReferences(resolvedRangeGroup);
                }
            }
        } else if (ArrayUtil.find(rangeGroups, resolvedRangeGroup) > -1) {
            for (int idx = rangeGroups.length - 1; idx >= 0; idx--) {
                if (rangeGroups[idx] == resolvedRangeGroup) {
                    break;
                }

                rangeGroups[idx].setCorrelated();
            }

            rangeGroup.setCorrelated();
        }
    }

    public OrderedHashSet getUnkeyedColumns(OrderedHashSet unresolvedSet) {

        if (opType == OpTypes.VALUE) {
            return unresolvedSet;
        }

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] == null) {
                continue;
            }

            unresolvedSet = nodes[i].getUnkeyedColumns(unresolvedSet);
        }

        switch (opType) {

            case OpTypes.ARRAY :
            case OpTypes.ARRAY_SUBQUERY :
            case OpTypes.ROW_SUBQUERY :
            case OpTypes.TABLE_SUBQUERY :
                if (table != null) {
                    if (unresolvedSet == null) {
                        unresolvedSet = new OrderedHashSet();
                    }

                    unresolvedSet.add(this);
                }
                break;

            default :
        }

        return unresolvedSet;
    }

    public void resolveTypes(Session session, Expression parent) {

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] != null) {
                nodes[i].resolveTypes(session, this);
            }
        }

        switch (opType) {

            case OpTypes.VALUE :
                break;

            case OpTypes.VALUELIST :

                /** @todo - should it fall through */
                break;

            case OpTypes.ROW :
                nodeDataTypes = new Type[nodes.length];

                for (int i = 0; i < nodes.length; i++) {
                    if (nodes[i] != null) {
                        nodeDataTypes[i] = nodes[i].dataType;
                    }
                }

                dataType = new RowType(nodeDataTypes);
                break;

            case OpTypes.ARRAY : {
                Type nodeDataType = null;

                for (int i = 0; i < nodes.length; i++) {
                    nodeDataType = Type.getAggregateType(nodeDataType,
                                                         nodes[i].dataType);
                }

                if (nodeDataType != null) {
                    for (int i = 0; i < nodes.length; i++) {
                        if (nodes[i].dataType == null) {
                            nodes[i].valueData =
                                nodeDataType.convertToDefaultType(
                                    session, nodes[i].valueData);
                        } else {
                            nodes[i].valueData =
                                nodeDataType.convertToType(
                                    session, nodes[i].valueData, nodes[i].dataType);
                        }
                    }
                }

                for (int i = 0; i < nodes.length; i++) {
                    nodes[i].dataType = nodeDataType;
                }


                dataType = new ArrayType(nodeDataType, nodes.length);

                return;
            }
            case OpTypes.ARRAY_SUBQUERY : {
                QueryExpression queryExpression = table.queryExpression;

                queryExpression.resolveTypes(session);
                table.prepareTable(session);

                nodeDataTypes = queryExpression.getColumnTypes();
                dataType      = nodeDataTypes[0];

                if (nodeDataTypes.length > 1) {
                    throw Error.error(ErrorCode.X_42564);
                }

                dataType = new ArrayType(dataType, Integer.MAX_VALUE);

                break;
            }
            case OpTypes.ROW_SUBQUERY :
            case OpTypes.TABLE_SUBQUERY : {
                QueryExpression queryExpression = table.queryExpression;

                if (queryExpression != null) {
                    queryExpression.resolveTypes(session);
                }

                Expression dataExpression = table.dataExpression;

                if (dataExpression != null) {
                    dataExpression.resolveTypes(session, null);
                }

                table.prepareTable(session);

                nodeDataTypes = table.getColumnTypes();
                dataType      = nodeDataTypes[0];

                break;
            }
            default :
                throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
        }
    }

    void setAsConstantValue(Session session, Expression parent) {

        valueData = getValue(session);
        opType    = OpTypes.VALUE;
        nodes     = emptyArray;
    }

    void setAsConstantValue(Object value, Expression parent) {

        valueData = value;
        opType    = OpTypes.VALUE;
        nodes     = emptyArray;
    }

    void prepareTable(Session session, Expression row, int degree) {

        if (nodeDataTypes != null) {
            return;
        }

        for (int i = 0; i < nodes.length; i++) {
            Expression e = nodes[i];

            if (e.opType == OpTypes.ROW) {
                if (degree != e.nodes.length) {
                    throw Error.error(ErrorCode.X_42564);
                }
            } else if (degree == 1) {
                nodes[i]       = new Expression(OpTypes.ROW);
                nodes[i].nodes = new Expression[]{ e };
            } else {
                throw Error.error(ErrorCode.X_42564);
            }
        }

        nodeDataTypes = new Type[degree];

        for (int j = 0; j < degree; j++) {
            Type    type                  = row == null ? null
                                                        : row.nodes[j]
                                                            .dataType;
            boolean hasUresolvedParameter = row == null ? false
                                                        : row.nodes[j]
                                                            .isUnresolvedParam();

            for (int i = 0; i < nodes.length; i++) {
                type = Type.getAggregateType(nodes[i].nodes[j].dataType, type);
                hasUresolvedParameter |= nodes[i].nodes[j].isUnresolvedParam();
            }

            if (type == null) {
                type = Type.SQL_VARCHAR_DEFAULT;
            }

            int typeCode = type.typeCode;

            if (hasUresolvedParameter && type.isCharacterType()) {
                if (typeCode == Types.SQL_CHAR
                        || type.precision
                           < Type.SQL_VARCHAR_DEFAULT.precision) {
                    if (typeCode == Types.SQL_CHAR) {
                        typeCode = Types.SQL_VARCHAR;
                    }

                    long precision =
                        Math.max(Type.SQL_VARCHAR_DEFAULT.precision,
                                 type.precision);

                    type = CharacterType.getCharacterType(typeCode, precision,
                                                          type.getCollation());
                }
            }

            nodeDataTypes[j] = type;

            if (row != null && row.nodes[j].isUnresolvedParam()) {
                row.nodes[j].dataType = type;
            }

            for (int i = 0; i < nodes.length; i++) {
                if (nodes[i].nodes[j].isUnresolvedParam()) {
                    nodes[i].nodes[j].dataType = nodeDataTypes[j];

                    continue;
                }

                if (nodes[i].nodes[j].opType == OpTypes.VALUE) {
                    if (nodes[i].nodes[j].valueData == null) {
                        nodes[i].nodes[j].dataType = nodeDataTypes[j];
                    }
                }
            }
        }
    }

    /**
     * Details of IN condition optimisation for 1.9.0
     * Predicates with SELECT are QUERY expressions
     *
     * Predicates with IN list
     *
     * Parser adds a SubQuery to the list for each predicate
     * At type resolution IN lists that are entirely fixed constant or parameter
     * values are selected for possible optimisation. The flags:
     *
     * IN expression right side isCorrelated == true if there are non-constant,
     * non-param expressions in the list (Expressions may have to be resolved
     * against the full set of columns of the query, so must be re-evaluated
     * for each row and evaluated after all the joins have been made)
     *
     * VALUELIST expression isFixedConstantValueList == true when all
     * expressions are fixed constant and none is a param. With this flag,
     * a single-column VALUELIST can be accessed as a HashMap.
     *
     * Predicates may be optimised as joins if isCorrelated == false
     *
     */
    void insertValuesIntoSubqueryTable(Session session,
                                       PersistentStore store) {

        for (int i = 0; i < nodes.length; i++) {
            Object[] values = nodes[i].getRowValue(session);
            Object[] data   = store.getTable().getEmptyRowData();

            for (int j = 0; j < nodeDataTypes.length; j++) {
                data[j] = nodeDataTypes[j].convertToType(session, values[j],
                        nodes[i].nodes[j].dataType);
            }

            Row row = (Row) store.getNewCachedObject(session, data, false);

            try {
                store.indexRow(session, row);
            } catch (HsqlException e) {}
        }
    }

    /**
     * Returns the name of a column as string
     *
     * @return column name
     */
    String getColumnName() {
        return getAlias();
    }

    public ColumnSchema getColumn() {
        return null;
    }

    /**
     * Returns the column index in the table
     */
    int getColumnIndex() {
        return columnIndex;
    }

    /**
     * Returns the data type
     */
    Type getDataType() {
        return dataType;
    }

    byte getNullability() {
        return nullability;
    }

    Type getNodeDataType(int i) {

        if (nodeDataTypes == null) {
            if (i > 0) {
                throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
            }

            return dataType;
        } else {
            return nodeDataTypes[i];
        }
    }

    Type[] getNodeDataTypes() {

        if (nodeDataTypes == null) {
            return new Type[]{ dataType };
        } else {
            return nodeDataTypes;
        }
    }

    int getDegree() {

        switch (opType) {

            case OpTypes.ROW :
                return nodes.length;

            case OpTypes.TABLE :
            case OpTypes.ROW_SUBQUERY :
            case OpTypes.TABLE_SUBQUERY :
                if (table == null) {
                    return nodeDataTypes.length;
                }

                return table.queryExpression.getColumnCount();

            default :
                return 1;
        }
    }

    public Table getTable() {
        return table;
    }

    public void materialise(Session session) {

        if (table == null) {
            return;
        }

        if (table.isCorrelated()) {
            table.materialiseCorrelated(session);
        } else {
            table.materialise(session);
        }
    }

    Object getValue(Session session, Type type) {

        Object o = getValue(session);

        if (o == null || dataType == type) {
            return o;
        }

        return type.convertToType(session, o, dataType);
    }

    public Object getConstantValueNoCheck(Session session) {

        try {
            return getValue(session);
        } catch (HsqlException e) {
            return null;
        }
    }

    public Object[] getRowValue(Session session) {

        switch (opType) {

            case OpTypes.ROW : {
                Object[] data = new Object[nodes.length];

                for (int i = 0; i < nodes.length; i++) {
                    data[i] = nodes[i].getValue(session);
                }

                return data;
            }
            case OpTypes.ROW_SUBQUERY :
            case OpTypes.TABLE_SUBQUERY : {
                return table.queryExpression.getValues(session);
            }
            default :
                throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
        }
    }

    public Object getValue(Session session) {

        switch (opType) {

            case OpTypes.VALUE :
                return valueData;

            case OpTypes.ROW : {
                if (nodes.length == 1) {
                    return nodes[0].getValue(session);
                }

                Object[] row = new Object[nodes.length];

                for (int i = 0; i < nodes.length; i++) {
                    row[i] = nodes[i].getValue(session);
                }

                return row;
            }
            case OpTypes.ARRAY : {
                Object[] array = new Object[nodes.length];

                for (int i = 0; i < nodes.length; i++) {
                    array[i] = nodes[i].getValue(session);
                }

                return array;
            }
            case OpTypes.ARRAY_SUBQUERY : {
                table.materialiseCorrelated(session);

                RowSetNavigatorData nav   = table.getNavigator(session);
                int                 size  = nav.getSize();
                Object[]            array = new Object[size];

                nav.beforeFirst();

                for (int i = 0; nav.next(); i++) {
                    Object[] data = nav.getCurrent();

                    array[i] = data[0];
                }

                return array;
            }
            case OpTypes.TABLE_SUBQUERY :
            case OpTypes.ROW_SUBQUERY : {
                table.materialiseCorrelated(session);

                Object[] value = table.getValues(session);

                if (value.length == 1) {
                    return value[0];
                }

                return value;
            }
            default :
                throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
        }
    }

    public Result getResult(Session session) {

        switch (opType) {

            case OpTypes.ARRAY : {
                RowSetNavigatorData navigator = table.getNavigator(session);
                Object[]            array = new Object[navigator.getSize()];

                navigator.beforeFirst();

                for (int i = 0; navigator.next(); i++) {
                    Object[] data = navigator.getCurrent();

                    array[i] = data[0];
                }

                return Result.newPSMResult(array);
            }
            case OpTypes.TABLE_SUBQUERY : {
                table.materialiseCorrelated(session);

                RowSetNavigatorData navigator = table.getNavigator(session);
                Result              result    = Result.newResult(navigator);

                result.metaData = table.queryExpression.getMetaData();

                return result;
            }
            default : {
                Object value = getValue(session);

                return Result.newPSMResult(value);
            }
        }
    }

    public boolean testCondition(Session session) {
        return Boolean.TRUE.equals(getValue(session));
    }

    static int countNulls(Object[] a) {

        int nulls = 0;

        for (int i = 0; i < a.length; i++) {
            if (a[i] == null) {
                nulls++;
            }
        }

        return nulls;
    }

    public boolean isTrue() {
        return opType == OpTypes.VALUE && valueData instanceof Boolean
               && ((Boolean) valueData).booleanValue();
    }

    public boolean isFalse() {
        return opType == OpTypes.VALUE && valueData instanceof Boolean
               && !((Boolean) valueData).booleanValue();
    }

    public boolean isIndexable(RangeVariable range) {
        return false;
    }

    static void convertToType(Session session, Object[] data, Type[] dataType,
                              Type[] newType) {

        for (int i = 0; i < data.length; i++) {
            if (!dataType[i].canConvertFrom(newType[i])) {
                data[i] = newType[i].convertToType(session, data[i],
                                                   dataType[i]);
            }
        }
    }

    /**
     * Returns a Select object that can be used for checking the contents
     * of an existing table against the given CHECK search condition.
     */
    static QuerySpecification getCheckSelect(Session session, Table t,
            Expression e) {

        CompileContext compileContext = new CompileContext(session);

        compileContext.setNextRangeVarIndex(0);

        QuerySpecification s = new QuerySpecification(compileContext);
        RangeVariable range = new RangeVariable(t, null, null, null,
            compileContext);
        RangeVariable[] ranges     = new RangeVariable[]{ range };
        RangeGroup      rangeGroup = new RangeGroupSimple(ranges, false);

        e.resolveCheckOrGenExpression(session, rangeGroup, true);

        if (Type.SQL_BOOLEAN != e.getDataType()) {
            throw Error.error(ErrorCode.X_42568);
        }

        Expression condition = new ExpressionLogical(OpTypes.NOT, e);

        s.addSelectColumnExpression(EXPR_TRUE);
        s.addRangeVariable(session, range);
        s.addQueryCondition(condition);
        s.resolve(session);

        return s;
    }

    public void resolveCheckOrGenExpression(Session session,
            RangeGroup rangeGroup, boolean isCheck) {

        boolean        nonDeterministic = false;
        OrderedHashSet set              = new OrderedHashSet();
        HsqlList unresolved = resolveColumnReferences(session, rangeGroup,
            RangeGroup.emptyArray, null);

        ExpressionColumn.checkColumnsResolved(unresolved);
        resolveTypes(session, null);
        collectAllExpressions(set, Expression.subqueryAggregateExpressionSet,
                              Expression.emptyExpressionSet);

        if (!set.isEmpty()) {
            throw Error.error(ErrorCode.X_42512);
        }

        collectAllExpressions(set, Expression.functionExpressionSet,
                              Expression.emptyExpressionSet);

        for (int i = 0; i < set.size(); i++) {
            Expression current = (Expression) set.get(i);

            if (current.opType == OpTypes.FUNCTION) {
                if (!((FunctionSQLInvoked) current).isDeterministic()) {
                    throw Error.error(ErrorCode.X_42512);
                }
            }

            if (current.opType == OpTypes.SQL_FUNCTION) {
                if (!((FunctionSQL) current).isDeterministic()) {
                    if (isCheck) {
                        nonDeterministic = true;

                        continue;
                    }

                    throw Error.error(ErrorCode.X_42512);
                }
            }
        }

        if (isCheck && nonDeterministic) {
            HsqlArrayList list = new HsqlArrayList();

            RangeVariableResolver.decomposeAndConditions(session, this, list);

            for (int i = 0; i < list.size(); i++) {
                nonDeterministic = true;

                Expression e = (Expression) list.get(i);
                Expression e1;

                if (e instanceof ExpressionLogical) {
                    boolean b = ((ExpressionLogical) e).convertToSmaller();

                    if (!b) {
                        break;
                    }

                    e1 = e.getRightNode();
                    e  = e.getLeftNode();

                    if (!e.dataType.isDateTimeType()) {
                        nonDeterministic = true;

                        break;
                    }

                    if (e.hasNonDeterministicFunction()) {
                        nonDeterministic = true;

                        break;
                    }

                    // both sides are actually consistent regarding timeZone
                    // e.dataType.isDateTimeTypeWithZone();
                    if (e1 instanceof ExpressionArithmetic) {
                        if (opType == OpTypes.ADD) {
                            if (e1.getRightNode()
                                    .hasNonDeterministicFunction()) {
                                e1.swapLeftAndRightNodes();
                            }
                        } else if (opType == OpTypes.SUBTRACT) {}
                        else {
                            break;
                        }

                        if (e1.getRightNode().hasNonDeterministicFunction()) {
                            break;
                        }

                        e1 = e1.getLeftNode();
                    }

                    if (e1.opType == OpTypes.SQL_FUNCTION) {
                        FunctionSQL function = (FunctionSQL) e1;

                        switch (function.funcType) {

                            case FunctionSQL.FUNC_CURRENT_DATE :
                            case FunctionSQL.FUNC_CURRENT_TIMESTAMP :
                            case FunctionSQL.FUNC_LOCALTIMESTAMP :
                                nonDeterministic = false;

                                continue;
                            default :
                                break;
                        }

                        break;
                    }

                    break;
                } else {
                    break;
                }
            }

            if (nonDeterministic) {
                throw Error.error(ErrorCode.X_42512);
            }
        }

        set.clear();
        collectObjectNames(set);

        RangeVariable[] ranges = rangeGroup.getRangeVariables();

        for (int i = 0; i < set.size(); i++) {
            HsqlName name = (HsqlName) set.get(i);

            switch (name.type) {

                case SchemaObject.COLUMN : {
                    if (isCheck) {
                        break;
                    }

                    int colIndex = ranges[0].rangeTable.findColumn(name.name);
                    ColumnSchema column =
                        ranges[0].rangeTable.getColumn(colIndex);

                    if (column.isGenerated()) {
                        throw Error.error(ErrorCode.X_42512);
                    }

                    break;
                }
                case SchemaObject.SEQUENCE : {
                    throw Error.error(ErrorCode.X_42512);
                }
                case SchemaObject.SPECIFIC_ROUTINE : {
                    Routine routine =
                        (Routine) session.database.schemaManager
                            .getSchemaObject(name);

                    if (!routine.isDeterministic()) {
                        throw Error.error(ErrorCode.X_42512);
                    }

                    int impact = routine.getDataImpact();

                    if (impact == Routine.READS_SQL
                            || impact == Routine.MODIFIES_SQL) {
                        throw Error.error(ErrorCode.X_42512);
                    }
                }
            }
        }

        set.clear();
    }

    boolean isUnresolvedParam() {
        return false;
    }

    boolean isDynamicParam() {
        return false;
    }

    boolean hasNonDeterministicFunction() {

        OrderedHashSet list = null;

        list = collectAllExpressions(list, Expression.functionExpressionSet,
                                     Expression.emptyExpressionSet);

        if (list == null) {
            return false;
        }

        for (int j = 0; j < list.size(); j++) {
            Expression current = (Expression) list.get(j);

            if (current.opType == OpTypes.FUNCTION) {
                if (!((FunctionSQLInvoked) current).isDeterministic()) {
                    return true;
                }
            } else if (current.opType == OpTypes.SQL_FUNCTION) {
                if (!((FunctionSQL) current).isDeterministic()) {
                    return true;
                }
            }
        }

        return false;
    }

    void swapLeftAndRightNodes() {

        Expression temp = nodes[LEFT];

        nodes[LEFT]  = nodes[RIGHT];
        nodes[RIGHT] = temp;
    }

    void setAttributesAsColumn(ColumnSchema column, boolean isWritable) {
        throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
    }

    String getValueClassName() {

        Type type = dataType == null ? NullType.getNullType()
                                     : dataType;

        return type.getJDBCClassName();
    }

    /**
     * collect all expressions of a set of expression types appearing anywhere
     * in a select statement and its subselects, etc.
     */
    OrderedHashSet collectAllExpressions(OrderedHashSet set,
                                         OrderedIntHashSet typeSet,
                                         OrderedIntHashSet stopAtTypeSet) {

        if (stopAtTypeSet.contains(opType)) {
            return set;
        }

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] != null) {
                set = nodes[i].collectAllExpressions(set, typeSet,
                                                     stopAtTypeSet);
            }
        }

        boolean added = false;

        if (typeSet.contains(opType)) {
            if (set == null) {
                set = new OrderedHashSet();
            }

            set.add(this);

            added = true;
        }

        if (!added) {
            if (table != null && table.queryExpression != null) {
                set = table.queryExpression.collectAllExpressions(set,
                        typeSet, stopAtTypeSet);
            }
        }

        return set;
    }

    public OrderedHashSet getSubqueries() {
        return collectAllSubqueries(null);
    }

    OrderedHashSet collectAllSubqueries(OrderedHashSet set) {

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] != null) {
                set = nodes[i].collectAllSubqueries(set);
            }
        }

        if (table != null) {
            OrderedHashSet tempSet = null;

            if (table.queryExpression != null) {
                tempSet = table.queryExpression.getSubqueries();
                set     = OrderedHashSet.addAll(set, tempSet);
            }

            if (set == null) {
                set = new OrderedHashSet();
            }

            set.add(table);
        }

        return set;
    }

    /**
     * isCorrelated
     */
    public boolean isCorrelated() {

        if (table == null) {
            return false;
        }

        return table.isCorrelated();
    }

    /**
     * checkValidCheckConstraint
     */
    public void checkValidCheckConstraint() {

        OrderedHashSet set = null;

        set = collectAllExpressions(set,
                                    Expression.subqueryAggregateExpressionSet,
                                    Expression.emptyExpressionSet);

        if (set != null && !set.isEmpty()) {
            throw Error.error(ErrorCode.X_0A000,
                              "subquery in check constraint");
        }
    }

    static HsqlList resolveColumnSet(Session session,
                                     RangeVariable[] rangeVars,
                                     RangeGroup[] rangeGroups,
                                     HsqlList sourceSet) {
        return resolveColumnSet(session, rangeVars, rangeVars.length,
                                rangeGroups, sourceSet, null);
    }

    static HsqlList resolveColumnSet(Session session,
                                     RangeVariable[] rangeVars,
                                     int rangeCount, RangeGroup[] rangeGroups,
                                     HsqlList sourceSet, HsqlList targetSet) {

        if (sourceSet == null) {
            return targetSet;
        }

        RangeGroup rangeGroup = new RangeGroupSimple(rangeVars, false);

        for (int i = 0; i < sourceSet.size(); i++) {
            Expression e = (Expression) sourceSet.get(i);

            targetSet = e.resolveColumnReferences(session, rangeGroup,
                                                  rangeCount, rangeGroups,
                                                  targetSet, false);
        }

        return targetSet;
    }

    boolean isConditionRangeVariable(RangeVariable range) {
        return false;
    }

    void getJoinRangeVariables(RangeVariable[] ranges, HsqlList list) {}

    double costFactor(Session session, RangeVariable range, int operation) {
        return Index.minimumSelectivity;
    }

    Expression getIndexableExpression(RangeVariable rangeVar) {
        return null;
    }

    public Expression duplicate() {

        Expression e = null;

        try {
            e       = (Expression) super.clone();
            e.nodes = (Expression[]) nodes.clone();

            for (int i = 0; i < nodes.length; i++) {
                if (nodes[i] != null) {
                    e.nodes[i] = nodes[i].duplicate();
                }
            }
        } catch (CloneNotSupportedException ex) {
            throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
        }

        return e;
    }

    void replaceNode(Expression existing, Expression replacement) {

        for (int i = 0; i < nodes.length; i++) {
            if (nodes[i] == existing) {
                replacement.alias = nodes[i].alias;
                nodes[i]          = replacement;

                return;
            }
        }

        throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
    }

    public SetFunction updateAggregatingValue(Session session,
            SetFunction currValue) {
        throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
    }

    public Object getAggregatedValue(Session session,
                                     SetFunction currValue) {
        throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
    }

    public Expression getCondition() {
        return null;
    }

    public boolean hasCondition() {
        return false;
    }

    public void setCondition(Expression e) {
        throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
    }

    public void setCollation(Collation collation) {
        this.collation = collation;
    }
}
