/* 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.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.index.Index;
import org.hsqldb.lib.HsqlList;
import org.hsqldb.lib.OrderedIntHashSet;
import org.hsqldb.navigator.RowIterator;
import org.hsqldb.persist.PersistentStore;
import org.hsqldb.types.ArrayType;
import org.hsqldb.types.DTIType;
import org.hsqldb.types.DateTimeType;
import org.hsqldb.types.NumberType;
import org.hsqldb.types.Type;
import org.hsqldb.types.Types;

/**
 * @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 ExpressionLogical extends Expression {

    boolean noOptimisation;
    boolean isQuantified;
    boolean isTerminal;

    /**
     * For LIKE
     */
    ExpressionLogical(int type) {

        super(type);

        dataType = Type.SQL_BOOLEAN;
    }

    /**
     * For boolean constants
     */
    ExpressionLogical(boolean b) {

        super(OpTypes.VALUE);

        dataType  = Type.SQL_BOOLEAN;
        valueData = b ? Boolean.TRUE
                      : Boolean.FALSE;
    }

    /*
     * Create an equality expressions using existing columns and
     * range variables. The expression is fully resolved in constructor.
     */
    ExpressionLogical(RangeVariable leftRangeVar, int colIndexLeft,
                      RangeVariable rightRangeVar, int colIndexRight) {

        super(OpTypes.EQUAL);

        ExpressionColumn leftExpression = new ExpressionColumn(leftRangeVar,
            colIndexLeft);
        ExpressionColumn rightExpression = new ExpressionColumn(rightRangeVar,
            colIndexRight);

        nodes        = new Expression[BINARY];
        nodes[LEFT]  = leftExpression;
        nodes[RIGHT] = rightExpression;

        setEqualityMode();

        dataType = Type.SQL_BOOLEAN;
    }

    /**
     * Creates an equality expression
     */
    ExpressionLogical(Expression left, Expression right) {

        super(OpTypes.EQUAL);

        nodes        = new Expression[BINARY];
        nodes[LEFT]  = left;
        nodes[RIGHT] = right;

        setEqualityMode();

        dataType = Type.SQL_BOOLEAN;
    }

    /**
     * Creates a binary operation expression
     */
    ExpressionLogical(int type, Expression left, Expression right) {

        super(type);

        nodes        = new Expression[BINARY];
        nodes[LEFT]  = left;
        nodes[RIGHT] = right;

        switch (opType) {

            case OpTypes.EQUAL :
            case OpTypes.GREATER_EQUAL :
            case OpTypes.GREATER_EQUAL_PRE :
            case OpTypes.GREATER :
            case OpTypes.SMALLER :
            case OpTypes.SMALLER_EQUAL :
                setEqualityMode();

            // fall through
            case OpTypes.NOT_EQUAL :
            case OpTypes.RANGE_CONTAINS :
            case OpTypes.RANGE_EQUALS :
            case OpTypes.RANGE_OVERLAPS :
            case OpTypes.RANGE_IMMEDIATELY_PRECEDES :
            case OpTypes.RANGE_IMMEDIATELY_SUCCEEDS :
            case OpTypes.RANGE_PRECEDES :
            case OpTypes.RANGE_SUCCEEDS :
            case OpTypes.NOT_DISTINCT :
            case OpTypes.IN :
            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.AND :
            case OpTypes.OR :
                dataType = Type.SQL_BOOLEAN;
                break;

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

    /**
     * Creates a modified LIKE comparison
     */
    ExpressionLogical(int type, Expression left, Expression right,
                      Expression end) {

        super(type);

        nodes        = new Expression[TERNARY];
        nodes[LEFT]  = left;
        nodes[RIGHT] = right;
        nodes[2]     = end;
    }

    /**
     * Creates a unary operation expression
     */
    ExpressionLogical(int type, Expression e) {

        super(type);

        nodes       = new Expression[UNARY];
        nodes[LEFT] = e;

        switch (opType) {

            case OpTypes.UNIQUE :
            case OpTypes.EXISTS :
            case OpTypes.IS_NULL :
            case OpTypes.IS_NOT_NULL :
            case OpTypes.NOT :
                dataType = Type.SQL_BOOLEAN;
                break;

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

        if (opType == OpTypes.IS_NULL
                && nodes[LEFT].opType == OpTypes.COLUMN) {
            isSingleColumnNull = true;
        }

        if (opType == OpTypes.NOT && nodes[LEFT].isSingleColumnNull) {
            isSingleColumnNotNull = true;
        }
    }

    /**
     * Creates a column not null expression for check constraints
     */
    ExpressionLogical(ColumnSchema column) {

        super(OpTypes.NOT);

        nodes    = new Expression[UNARY];
        dataType = Type.SQL_BOOLEAN;

        Expression e = new ExpressionColumn(column);

        e           = new ExpressionLogical(OpTypes.IS_NULL, e);
        nodes[LEFT] = e;
    }

    void setEqualityMode() {

        if (nodes[LEFT].opType == OpTypes.COLUMN) {
            nodes[LEFT].nullability = SchemaObject.Nullability.NO_NULLS;

            switch (nodes[RIGHT].opType) {

                case OpTypes.COLUMN :
                    isColumnCondition = true;

                    if (opType == OpTypes.EQUAL) {
                        isColumnEqual = true;
                    }

                    nodes[RIGHT].nullability =
                        SchemaObject.Nullability.NO_NULLS;
                    break;

                case OpTypes.VALUE :
                case OpTypes.DYNAMIC_PARAM :
                case OpTypes.PARAMETER :
                case OpTypes.VARIABLE :
                    isSingleColumnCondition = true;

                    if (opType == OpTypes.EQUAL) {
                        isSingleColumnEqual = true;
                    }
                    break;

                default :
            }
        } else if (nodes[RIGHT].opType == OpTypes.COLUMN) {
            nodes[RIGHT].nullability = SchemaObject.Nullability.NO_NULLS;

            switch (nodes[LEFT].opType) {

                case OpTypes.VALUE :
                case OpTypes.DYNAMIC_PARAM :
                case OpTypes.PARAMETER :
                case OpTypes.VARIABLE :
                    isSingleColumnCondition = true;

                    if (opType == OpTypes.EQUAL) {
                        isSingleColumnEqual = true;
                    }
                    break;

                default :
            }
        }
    }

    /**
     * Creates a NOT NULL condition
     */
    static ExpressionLogical newNotNullCondition(Expression e) {

        e = new ExpressionLogical(OpTypes.IS_NULL, e);

        return new ExpressionLogical(OpTypes.NOT, e);
    }

    // logical ops
    static Expression andExpressions(Expression e1, Expression e2) {

        if (e1 == null) {
            return e2;
        }

        if (e2 == null) {
            return e1;
        }

        if (ExpressionLogical.EXPR_FALSE.equals(e1)
                || ExpressionLogical.EXPR_FALSE.equals(e2)) {
            return ExpressionLogical.EXPR_FALSE;
        }

        if (e1 == e2) {
            return e1;
        }

        return new ExpressionLogical(OpTypes.AND, e1, e2);
    }

    static Expression orExpressions(Expression e1, Expression e2) {

        if (e1 == null) {
            return e2;
        }

        if (e2 == null) {
            return e1;
        }

        if (e1 == e2) {
            return e1;
        }

        return new ExpressionLogical(OpTypes.OR, e1, e2);
    }

    public void addLeftColumnsForAllAny(RangeVariable range,
                                        OrderedIntHashSet set) {

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

        for (int j = 0; j < nodes[LEFT].nodes.length; j++) {
            int index = nodes[LEFT].nodes[j].getColumnIndex();

            if (index < 0
                    || nodes[LEFT].nodes[j].getRangeVariable() != range) {
                set.clear();

                return;
            }

            set.add(index);
        }
    }

    public void setSubType(int type) {

        exprSubType = type;

        if (exprSubType == OpTypes.ALL_QUANTIFIED
                || exprSubType == OpTypes.ANY_QUANTIFIED) {
            isQuantified = true;
        }
    }

    public String getSQL() {

        StringBuffer sb = new StringBuffer(64);

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

        String left  = getContextSQL(nodes[LEFT]);
        String right = getContextSQL(nodes.length > 1 ? nodes[RIGHT]
                                                      : null);

        switch (opType) {

            case OpTypes.NOT :
                if (nodes[LEFT].opType == OpTypes.IS_NULL) {
                    sb.append(getContextSQL(nodes[LEFT].nodes[LEFT])).append(
                        ' ').append(Tokens.T_IS).append(' ').append(
                        Tokens.T_NOT).append(' ').append(Tokens.T_NULL);

                    return sb.toString();
                }

                if (nodes[LEFT].opType == OpTypes.NOT_DISTINCT) {
                    sb.append(getContextSQL(nodes[LEFT].nodes[LEFT])).append(
                        ' ').append(Tokens.T_IS).append(' ').append(
                        Tokens.T_DISTINCT).append(' ').append(
                        Tokens.T_FROM).append(' ').append(
                        getContextSQL(nodes[LEFT].nodes[RIGHT]));

                    return sb.toString();
                }

                sb.append(Tokens.T_NOT).append(' ').append(left);

                return sb.toString();

            case OpTypes.NOT_DISTINCT :
                sb.append(left).append(' ').append(Tokens.T_IS).append(
                    ' ').append(Tokens.T_NOT).append(' ').append(
                    Tokens.T_DISTINCT).append(' ').append(
                    Tokens.T_FROM).append(' ').append(right);

                return sb.toString();

            case OpTypes.IS_NULL :
                sb.append(left).append(' ').append(Tokens.T_IS).append(
                    ' ').append(Tokens.T_NULL);

                return sb.toString();

            case OpTypes.UNIQUE :
                sb.append(' ').append(Tokens.T_UNIQUE).append(' ');
                break;

            case OpTypes.EXISTS :
                sb.append(' ').append(Tokens.T_EXISTS).append(' ');
                break;

            case OpTypes.EQUAL :
                sb.append(left).append('=').append(right);

                return sb.toString();

            case OpTypes.GREATER_EQUAL :
            case OpTypes.GREATER_EQUAL_PRE :
                sb.append(left).append(">=").append(right);

                return sb.toString();

            case OpTypes.GREATER :
                sb.append(left).append('>').append(right);

                return sb.toString();

            case OpTypes.SMALLER :
                sb.append(left).append('<').append(right);

                return sb.toString();

            case OpTypes.SMALLER_EQUAL :
                sb.append(left).append("<=").append(right);

                return sb.toString();

            case OpTypes.NOT_EQUAL :
                if (Tokens.T_NULL.equals(right)) {
                    sb.append(left).append(" IS NOT ").append(right);
                } else {
                    sb.append(left).append("!=").append(right);
                }

                return sb.toString();

            case OpTypes.AND :
                sb.append(left).append(' ').append(Tokens.T_AND).append(
                    ' ').append(right);

                return sb.toString();

            case OpTypes.OR :
                sb.append(left).append(' ').append(Tokens.T_OR).append(
                    ' ').append(right);

                return sb.toString();

            case OpTypes.IN :
                sb.append(left).append(' ').append(Tokens.T_IN).append(
                    ' ').append(right);

                return sb.toString();

            case OpTypes.MATCH_SIMPLE :
                sb.append(left).append(' ').append(Tokens.T_MATCH).append(
                    ' ').append(right);

                return sb.toString();

            case OpTypes.MATCH_PARTIAL :
                sb.append(left).append(' ').append(Tokens.T_MATCH).append(
                    ' ').append(Tokens.PARTIAL).append(right);

                return sb.toString();

            case OpTypes.MATCH_FULL :
                sb.append(left).append(' ').append(Tokens.T_MATCH).append(
                    ' ').append(Tokens.FULL).append(right);

                return sb.toString();

            case OpTypes.MATCH_UNIQUE_SIMPLE :
                sb.append(left).append(' ').append(Tokens.T_MATCH).append(
                    ' ').append(Tokens.UNIQUE).append(right);

                return sb.toString();

            case OpTypes.MATCH_UNIQUE_PARTIAL :
                sb.append(left).append(' ').append(Tokens.T_MATCH).append(
                    ' ').append(Tokens.UNIQUE).append(' ').append(
                    Tokens.PARTIAL).append(right);

                return sb.toString();

            case OpTypes.MATCH_UNIQUE_FULL :
                sb.append(left).append(' ').append(Tokens.T_MATCH).append(
                    ' ').append(Tokens.UNIQUE).append(' ').append(
                    Tokens.FULL).append(right);

                return sb.toString();

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

        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.NOT :
                if (nodes[LEFT].opType == OpTypes.NOT_DISTINCT) {
                    sb.append(Tokens.T_DISTINCT);

                    return sb.toString();
                }

                sb.append(Tokens.T_NOT);
                break;

            case OpTypes.NOT_DISTINCT :
                sb.append(Tokens.T_NOT).append(' ').append(Tokens.T_DISTINCT);
                break;

            case OpTypes.EQUAL :
                sb.append("EQUAL");
                break;

            case OpTypes.GREATER_EQUAL :
            case OpTypes.GREATER_EQUAL_PRE :
                sb.append("GREATER_EQUAL");
                break;

            case OpTypes.GREATER :
                sb.append("GREATER");
                break;

            case OpTypes.SMALLER :
                sb.append("SMALLER");
                break;

            case OpTypes.SMALLER_EQUAL :
                sb.append("SMALLER_EQUAL");
                break;

            case OpTypes.NOT_EQUAL :
                sb.append("NOT_EQUAL");
                break;

            case OpTypes.AND :
                sb.append(Tokens.T_AND);
                break;

            case OpTypes.OR :
                sb.append(Tokens.T_OR);
                break;

            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 :
                sb.append(Tokens.T_MATCH);
                break;

            case OpTypes.IS_NULL :
                sb.append(Tokens.T_IS).append(' ').append(Tokens.T_NULL);
                break;

            case OpTypes.UNIQUE :
                sb.append(Tokens.T_UNIQUE);
                break;

            case OpTypes.EXISTS :
                sb.append(Tokens.T_EXISTS);
                break;

            case OpTypes.RANGE_CONTAINS :
                sb.append(Tokens.T_CONTAINS);
                break;

            case OpTypes.RANGE_EQUALS :
                sb.append(Tokens.T_EQUALS);
                break;

            case OpTypes.RANGE_OVERLAPS :
                sb.append(Tokens.T_OVERLAPS);
                break;

            case OpTypes.RANGE_PRECEDES :
                sb.append(Tokens.T_PRECEDES);
                break;

            case OpTypes.RANGE_SUCCEEDS :
                sb.append(Tokens.T_SUCCEEDS);
                break;

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

        if (getLeftNode() != null) {
            sb.append(" arg_left=[");
            sb.append(nodes[LEFT].describe(session, blanks + 1));
            sb.append(']');
        }

        if (getRightNode() != null) {
            sb.append(" arg_right=[");
            sb.append(nodes[RIGHT].describe(session, blanks + 1));
            sb.append(']');
        }

        return sb.toString();
    }

    public void resolveTypes(Session session, Expression parent) {

        // parametric ALL / ANY
        if (isQuantified) {
            if (nodes[RIGHT].opType == OpTypes.TABLE) {
                if (nodes[RIGHT] instanceof ExpressionTable) {
                    if (nodes[RIGHT].nodes[LEFT].opType
                            == OpTypes.DYNAMIC_PARAM) {
                        nodes[LEFT].resolveTypes(session, this);

                        nodes[RIGHT].nodes[LEFT].dataType = new ArrayType(
                            nodes[LEFT].dataType,
                            ArrayType.defaultLargeArrayCardinality);
                    }
                }
            }
        }

        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.NOT_DISTINCT :
                changeToRowExpression(LEFT);
                changeToRowExpression(RIGHT);
                resolveRowTypes();
                checkRowComparison();
                break;

            case OpTypes.EQUAL :
            case OpTypes.GREATER_EQUAL :
            case OpTypes.GREATER_EQUAL_PRE :
            case OpTypes.GREATER :
            case OpTypes.SMALLER :
            case OpTypes.SMALLER_EQUAL :
            case OpTypes.NOT_EQUAL :
                resolveTypesForComparison(session, parent);
                break;

            case OpTypes.AND : {
                resolveTypesForLogicalOp();

                if (nodes[LEFT].opType == OpTypes.VALUE) {
                    if (nodes[RIGHT].opType == OpTypes.VALUE) {
                        setAsConstantValue(session, parent);
                    } else {
                        Object value = nodes[LEFT].getValue(session);

                        if (value == null || Boolean.FALSE.equals(value)) {
                            setAsConstantValue(Boolean.FALSE, parent);
                        }
                    }
                } else if (nodes[RIGHT].opType == OpTypes.VALUE) {
                    Object value = nodes[RIGHT].getValue(session);

                    if (value == null || Boolean.FALSE.equals(value)) {
                        setAsConstantValue(Boolean.FALSE, parent);
                    }
                }

                break;
            }
            case OpTypes.OR : {
                resolveTypesForLogicalOp();

                if (nodes[LEFT].opType == OpTypes.VALUE) {
                    if (nodes[RIGHT].opType == OpTypes.VALUE) {
                        setAsConstantValue(session, parent);
                    } else {
                        Object value = nodes[LEFT].getValue(session);

                        if (Boolean.TRUE.equals(value)) {
                            setAsConstantValue(Boolean.TRUE, parent);
                        }
                    }
                } else if (nodes[RIGHT].opType == OpTypes.VALUE) {
                    Object value = nodes[RIGHT].getValue(session);

                    if (Boolean.TRUE.equals(value)) {
                        setAsConstantValue(Boolean.TRUE, parent);
                    }
                }

                break;
            }
            case OpTypes.IS_NOT_NULL :
            case OpTypes.IS_NULL : {
                switch (nodes[LEFT].opType) {

                    case OpTypes.ROW : {
                        Expression[] sourceNodes = nodes[LEFT].nodes;
                        Expression   result      = null;

                        for (int i = 0; i < sourceNodes.length; i++) {
                            Expression node;

                            node = new ExpressionLogical(OpTypes.IS_NULL,
                                                         sourceNodes[i]);

                            if (opType == OpTypes.IS_NOT_NULL) {
                                node = new ExpressionLogical(OpTypes.NOT,
                                                             node);
                            }

                            result = andExpressions(result, node);
                        }

                        opType = OpTypes.AND;
                        nodes  = result.nodes;

                        resolveTypes(session, parent);

                        break;
                    }
                    case OpTypes.ROW_SUBQUERY :
                    case OpTypes.TABLE_SUBQUERY : {
                        break;
                    }
                    default : {
                        if (nodes[LEFT].isUnresolvedParam()) {
                            if (session.database.sqlEnforceTypes) {
                                throw Error.error(ErrorCode.X_42563);
                            }

                            nodes[LEFT].dataType = Type.SQL_VARCHAR_DEFAULT;
                        }

                        if (opType == OpTypes.IS_NOT_NULL) {
                            Expression node;

                            node = new ExpressionLogical(OpTypes.IS_NULL,
                                                         nodes[LEFT]);
                            nodes[LEFT] = node;
                            opType      = OpTypes.NOT;

                            resolveTypes(session, parent);

                            break;
                        }

                        if (nodes[LEFT].opType == OpTypes.VALUE) {
                            setAsConstantValue(session, parent);
                        }

                        break;
                    }
                }

                break;
            }
            case OpTypes.NOT : {
                if (nodes[LEFT].isUnresolvedParam()) {
                    nodes[LEFT].dataType = Type.SQL_BOOLEAN;

                    break;
                }

                if (nodes[LEFT].opType == OpTypes.VALUE) {
                    if (nodes[LEFT].dataType.isBooleanType()) {
                        setAsConstantValue(session, parent);

                        break;
                    } else {
                        throw Error.error(ErrorCode.X_42563);
                    }
                }

                if (nodes[LEFT].dataType == null
                        || !nodes[LEFT].dataType.isBooleanType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                dataType = Type.SQL_BOOLEAN;

                break;
            }
            case OpTypes.RANGE_CONTAINS :
            case OpTypes.RANGE_EQUALS :
            case OpTypes.RANGE_OVERLAPS :
            case OpTypes.RANGE_IMMEDIATELY_PRECEDES :
            case OpTypes.RANGE_IMMEDIATELY_SUCCEEDS :
            case OpTypes.RANGE_PRECEDES :
            case OpTypes.RANGE_SUCCEEDS :
                resolveTypesForPeriodPredicates(session);
                break;

            case OpTypes.IN :
                resolveTypesForIn(session);
                break;

            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 :
                resolveTypesForAllAny(session);
                break;

            case OpTypes.UNIQUE :

                // create the full index before meterialization
                // needed for VALUES expression
                nodes[LEFT].table.getFullIndex(session);
                break;

            case OpTypes.EXISTS :
                break;

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

    private void resolveTypesForLogicalOp() {

        if (nodes[LEFT].isUnresolvedParam()) {
            nodes[LEFT].dataType = Type.SQL_BOOLEAN;
        }

        if (nodes[RIGHT].isUnresolvedParam()) {
            nodes[RIGHT].dataType = Type.SQL_BOOLEAN;
        }

        if (nodes[LEFT].dataType == null || nodes[RIGHT].dataType == null) {
            throw Error.error(ErrorCode.X_42571);
        }

        if (nodes[LEFT].opType == OpTypes.ROW
                || nodes[RIGHT].opType == OpTypes.ROW) {
            throw Error.error(ErrorCode.X_42565);
        }

        if (Type.SQL_BOOLEAN != nodes[LEFT].dataType
                || Type.SQL_BOOLEAN != nodes[RIGHT].dataType) {
            throw Error.error(ErrorCode.X_42568);
        }
    }

    private void resolveTypesForComparison(Session session,
                                           Expression parent) {

        if (exprSubType == OpTypes.ALL_QUANTIFIED
                || exprSubType == OpTypes.ANY_QUANTIFIED) {
            resolveTypesForAllAny(session);
            checkRowComparison();

            return;
        }

        int leftDegree  = nodes[LEFT].getDegree();
        int rightDegree = nodes[RIGHT].getDegree();

        if (leftDegree > 1 || rightDegree > 1) {
            if (leftDegree != rightDegree) {
                throw Error.error(ErrorCode.X_42564);
            }

            resolveRowTypes();
            checkRowComparison();

            return;
        } else {
            if (nodes[LEFT].isUnresolvedParam()) {
                nodes[LEFT].dataType = nodes[RIGHT].dataType;
            } else if (nodes[RIGHT].isUnresolvedParam()) {
                nodes[RIGHT].dataType = nodes[LEFT].dataType;
            }

            if (nodes[LEFT].dataType == null) {
                nodes[LEFT].dataType = nodes[RIGHT].dataType;
            } else if (nodes[RIGHT].dataType == null) {
                nodes[RIGHT].dataType = nodes[LEFT].dataType;
            }

            if (nodes[LEFT].dataType == null
                    || nodes[RIGHT].dataType == null) {
                throw Error.error(ErrorCode.X_42567);
            }

            if (!nodes[LEFT].dataType.canCompareDirect(
                    nodes[RIGHT].dataType)) {
                if (convertDateTime(session)) {

                    // compatibility for BIT with number and BOOLEAN - convert bit to other type
                } else if (nodes[LEFT].dataType.isBitType()
                           || nodes[LEFT].dataType.isBooleanType()) {
                    if (session.database.sqlEnforceTypes) {
                        throw Error.error(ErrorCode.X_42562);
                    }

                    if (nodes[LEFT].dataType.canConvertFrom(
                            nodes[RIGHT].dataType)) {
                        nodes[RIGHT] = ExpressionOp.getCastExpression(session,
                                nodes[RIGHT], nodes[LEFT].dataType);
                    }
                } else if (nodes[RIGHT].dataType.isBitType()
                           || nodes[RIGHT].dataType.isBooleanType()) {
                    if (session.database.sqlEnforceTypes) {
                        throw Error.error(ErrorCode.X_42562);
                    }

                    if (nodes[RIGHT].dataType.canConvertFrom(
                            nodes[LEFT].dataType)) {
                        nodes[LEFT] = ExpressionOp.getCastExpression(session,
                                nodes[LEFT], nodes[RIGHT].dataType);
                    }
                } else if (nodes[LEFT].dataType.isNumberType()) {
                    if (session.database.sqlEnforceTypes) {
                        throw Error.error(ErrorCode.X_42562);
                    }

                    if (nodes[LEFT].dataType.canConvertFrom(
                            nodes[RIGHT].dataType)) {
                        nodes[RIGHT] = ExpressionOp.getCastExpression(session,
                                nodes[RIGHT], nodes[LEFT].dataType);
                    }
                } else if (nodes[RIGHT].dataType.isNumberType()) {
                    if (session.database.sqlEnforceTypes) {
                        throw Error.error(ErrorCode.X_42562);
                    }

                    if (nodes[RIGHT].dataType.canConvertFrom(
                            nodes[LEFT].dataType)) {
                        nodes[LEFT] = ExpressionOp.getCastExpression(session,
                                nodes[LEFT], nodes[RIGHT].dataType);
                    }
                } else if (nodes[LEFT].dataType.isDateTimeType()) {
                    if (nodes[LEFT].dataType.isDateTimeTypeWithZone()
                            ^ nodes[RIGHT].dataType.isDateTimeTypeWithZone()) {
                        nodes[LEFT] = new ExpressionOp(nodes[LEFT]);
                    }
                } else if (nodes[LEFT].dataType.canConvertFrom(
                        nodes[RIGHT].dataType)) {
                    nodes[RIGHT] = ExpressionOp.getCastExpression(session,
                            nodes[RIGHT], nodes[LEFT].dataType);
                } else {
                    throw Error.error(ErrorCode.X_42562);
                }
            }

            if (opType == OpTypes.EQUAL || opType == OpTypes.NOT_EQUAL) {

                //
            } else {
                if (nodes[LEFT].dataType.isArrayType()
                        || nodes[LEFT].dataType.isLobType()
                        || nodes[RIGHT].dataType.isLobType()) {
                    throw Error.error(ErrorCode.X_42534);
                }
            }

            if (nodes[LEFT].opType == OpTypes.ROWNUM
                    && nodes[RIGHT].opType == OpTypes.VALUE) {
                isTerminal = true;
            }

            if (nodes[LEFT].dataType.typeComparisonGroup
                    != nodes[RIGHT].dataType.typeComparisonGroup) {
                throw Error.error(ErrorCode.X_42562);
            }

            if (nodes[LEFT].opType == OpTypes.VALUE
                    && nodes[RIGHT].opType == OpTypes.VALUE) {
                setAsConstantValue(session, parent);
            } else if (session.database.sqlSyntaxDb2) {
                if (nodes[LEFT].dataType.typeComparisonGroup
                        == Types.SQL_VARCHAR) {
                    if (nodes[LEFT].opType == OpTypes.VALUE) {
                        nodes[RIGHT].dataType.convertToTypeLimits(session,
                                nodes[LEFT].valueData);
                    }

                    if (nodes[RIGHT].opType == OpTypes.VALUE) {
                        nodes[LEFT].dataType.convertToTypeLimits(session,
                                nodes[RIGHT].valueData);
                    }
                }
            }
        }
    }

    private void changeToRowExpression(int nodeIndex) {

        if (nodes[nodeIndex].opType != OpTypes.ROW) {
            nodes[nodeIndex] = new Expression(OpTypes.ROW,
                                              new Expression[]{
                                                  nodes[nodeIndex] });
            nodes[nodeIndex].nodeDataTypes = new Type[]{
                nodes[nodeIndex].nodes[0].dataType };
        }
    }

    private void resolveRowTypes() {

        for (int i = 0; i < nodes[LEFT].nodeDataTypes.length; i++) {
            Type leftType  = nodes[LEFT].nodeDataTypes[i];
            Type rightType = nodes[RIGHT].nodeDataTypes[i];

            if (leftType == null) {
                leftType = nodes[LEFT].nodeDataTypes[i] = rightType;
            } else if (nodes[RIGHT].dataType == null) {
                rightType = nodes[RIGHT].nodeDataTypes[i] = leftType;
            }

            if (leftType == null || rightType == null) {
                throw Error.error(ErrorCode.X_42567);
            }

            if (leftType.typeComparisonGroup
                    != rightType.typeComparisonGroup) {
                throw Error.error(ErrorCode.X_42562);
            } else if (leftType.isDateTimeType()) {
                if (leftType.isDateTimeTypeWithZone()
                        ^ rightType.isDateTimeTypeWithZone()) {
                    nodes[LEFT].nodes[i] =
                        new ExpressionOp(nodes[LEFT].nodes[i]);
                    nodes[LEFT].nodeDataTypes[i] =
                        nodes[LEFT].nodes[i].dataType;
                }
            }
        }
    }

    void checkRowComparison() {

        if (opType == OpTypes.EQUAL || opType == OpTypes.NOT_EQUAL) {
            return;
        }

        for (int i = 0; i < nodes[LEFT].nodeDataTypes.length; i++) {
            Type leftType  = nodes[LEFT].nodeDataTypes[i];
            Type rightType = nodes[RIGHT].nodeDataTypes[i];

            if (leftType.isArrayType() || leftType.isLobType()
                    || rightType.isLobType()) {
                throw Error.error(ErrorCode.X_42534);
            }
        }
    }

    /**
     * for compatibility, convert a datetime character string to a datetime
     * value for comparison
     */
    private boolean convertDateTime(Session session) {

        int a = LEFT;
        int b = RIGHT;

        if (nodes[a].dataType.isDateTimeType()) {

            //
        } else if (nodes[b].dataType.isDateTimeType()) {
            a = RIGHT;
            b = LEFT;
        } else {
            return false;
        }

        if (nodes[a].dataType.isDateTimeTypeWithZone()) {
            return false;
        }

        if (nodes[b].dataType.isCharacterType()) {
            if (nodes[b].opType == OpTypes.VALUE) {
                try {
                    nodes[b].valueData = nodes[a].dataType.castToType(session,
                            nodes[b].valueData, nodes[b].dataType);
                    nodes[b].dataType = nodes[a].dataType;
                } catch (HsqlException e) {
                    if (nodes[a].dataType == Type.SQL_DATE) {
                        nodes[b].valueData =
                            Type.SQL_TIMESTAMP.castToType(session,
                                                          nodes[b].valueData,
                                                          nodes[b].dataType);
                        nodes[b].dataType = Type.SQL_TIMESTAMP;
                    }
                }

                return true;
            } else {
                nodes[b] = new ExpressionOp(nodes[b], nodes[a].dataType);

                nodes[b].resolveTypes(session, this);

                return true;
            }
        }

        return false;
    }

    void resolveTypesForPeriodPredicates(Session session) {

        // convert CONTAINS right part if necessary
        if (nodes[RIGHT].nodes.length == 0) {
            Expression[] newNodes = new Expression[] {
                nodes[RIGHT], new ExpressionValue(null, nodes[RIGHT].dataType)
            };

            nodes[RIGHT] = new Expression(OpTypes.ROW, newNodes);

            nodes[RIGHT].resolveTypes(session, null);
        }

        // end convert
        if (nodes[LEFT].nodes[0].isUnresolvedParam()) {
            nodes[LEFT].nodes[0].dataType = nodes[RIGHT].nodes[0].dataType;
        }

        if (nodes[RIGHT].nodes[0].isUnresolvedParam()) {
            nodes[RIGHT].nodes[0].dataType = nodes[LEFT].nodes[0].dataType;
        }

        if (nodes[LEFT].nodes[0].dataType == null) {
            nodes[LEFT].nodes[0].dataType  = Type.SQL_TIMESTAMP;
            nodes[RIGHT].nodes[0].dataType = Type.SQL_TIMESTAMP;
        }

        if (nodes[LEFT].nodes[1].isUnresolvedParam()) {
            nodes[LEFT].nodes[1].dataType = nodes[RIGHT].nodes[0].dataType;
        }

        if (nodes[RIGHT].nodes[1].isUnresolvedParam()) {
            nodes[RIGHT].nodes[1].dataType = nodes[LEFT].nodes[0].dataType;
        }

        if (!DTIType.isValidDatetimeRange(nodes[LEFT].nodes[0].dataType,
                                          nodes[LEFT].nodes[1].dataType)) {
            throw Error.error(ErrorCode.X_42563);
        }

        if (!DTIType.isValidDatetimeRange(nodes[RIGHT].nodes[0].dataType,
                                          nodes[RIGHT].nodes[1].dataType)) {
            throw Error.error(ErrorCode.X_42563);
        }

        nodes[LEFT].nodeDataTypes[0]  = nodes[LEFT].nodes[0].dataType;
        nodes[LEFT].nodeDataTypes[1]  = nodes[LEFT].nodes[1].dataType;
        nodes[RIGHT].nodeDataTypes[0] = nodes[RIGHT].nodes[0].dataType;
        nodes[RIGHT].nodeDataTypes[1] = nodes[RIGHT].nodes[1].dataType;
    }

    void resolveTypesForAllAny(Session session) {

        int degree = nodes[LEFT].getDegree();

        if (degree == 1 && nodes[LEFT].opType != OpTypes.ROW) {
            nodes[LEFT] = new Expression(OpTypes.ROW,
                                         new Expression[]{ nodes[LEFT] });
        }

        if (nodes[RIGHT].opType == OpTypes.VALUELIST) {
            nodes[RIGHT].prepareTable(session, nodes[LEFT], degree);
            nodes[RIGHT].table.prepareTable(session);
        }

        // encounterd in system generated MATCH predicates
        if (nodes[RIGHT].nodeDataTypes == null) {
            nodes[RIGHT].prepareTable(session, nodes[LEFT], degree);
        }

        if (degree != nodes[RIGHT].nodeDataTypes.length) {
            throw Error.error(ErrorCode.X_42564);
        }

        if (nodes[RIGHT].opType == OpTypes.VALUELIST) {}

        if (nodes[LEFT].nodeDataTypes == null) {
            nodes[LEFT].nodeDataTypes = new Type[nodes[LEFT].nodes.length];
        }

        for (int i = 0; i < nodes[LEFT].nodeDataTypes.length; i++) {
            Type type = nodes[LEFT].nodes[i].dataType;

            if (type == null) {
                type = nodes[RIGHT].nodeDataTypes[i];
            }

            if (type == null) {
                throw Error.error(ErrorCode.X_42567);
            }

            if (type.typeComparisonGroup
                    != nodes[RIGHT].nodeDataTypes[i].typeComparisonGroup) {
                throw Error.error(ErrorCode.X_42563);
            }

            nodes[LEFT].nodeDataTypes[i]  = type;
            nodes[LEFT].nodes[i].dataType = type;
        }
    }

    void resolveTypesForIn(Session session) {
        resolveTypesForAllAny(session);
    }

    public Object getValue(Session session) {

        switch (opType) {

            case OpTypes.VALUE :
                return valueData;

            case OpTypes.NEGATE :
                return ((NumberType) dataType).negate(
                    nodes[LEFT].getValue(session, nodes[LEFT].dataType));

            case OpTypes.IS_NOT_NULL :
            case OpTypes.IS_NULL : {
                switch (nodes[LEFT].opType) {

                    case OpTypes.ROW :
                    case OpTypes.ROW_SUBQUERY :
                    case OpTypes.TABLE_SUBQUERY : {
                        Object[] values = nodes[LEFT].getRowValue(session);

                        for (int i = 0; i < values.length; i++) {
                            if (values[i] == null) {
                                if (opType == OpTypes.IS_NOT_NULL) {
                                    return Boolean.FALSE;
                                }
                            } else {
                                if (opType == OpTypes.IS_NULL) {
                                    return Boolean.FALSE;
                                }
                            }
                        }

                        return Boolean.TRUE;
                    }
                }

                return nodes[LEFT].getValue(session) == null ? Boolean.TRUE
                                                             : Boolean.FALSE;
            }
            case OpTypes.RANGE_CONTAINS : {
                Object[] left  = nodes[LEFT].getRowValue(session);
                Object[] right = nodes[RIGHT].getRowValue(session);
                boolean pointOfTime = nodes[RIGHT].exprSubType
                                      != OpTypes.RANGE_EQUALS;

                return DateTimeType.contains(session, left,
                                             nodes[LEFT].nodeDataTypes, right,
                                             nodes[RIGHT].nodeDataTypes,
                                             pointOfTime);
            }
            case OpTypes.RANGE_EQUALS : {
                Object[] left  = nodes[LEFT].getRowValue(session);
                Object[] right = nodes[RIGHT].getRowValue(session);

                return DateTimeType.equals(session, left,
                                           nodes[LEFT].nodeDataTypes, right,
                                           nodes[RIGHT].nodeDataTypes);
            }
            case OpTypes.RANGE_OVERLAPS : {
                Object[] left  = nodes[LEFT].getRowValue(session);
                Object[] right = nodes[RIGHT].getRowValue(session);

                if (nodes[LEFT].exprSubType == OpTypes.RANGE_EQUALS) {
                    return DateTimeType.overlaps(session, left,
                                                 nodes[LEFT].nodeDataTypes,
                                                 right,
                                                 nodes[RIGHT].nodeDataTypes);
                } else {
                    return DateTimeType.overlapsRelaxed(
                        session, left, nodes[LEFT].nodeDataTypes, right,
                        nodes[RIGHT].nodeDataTypes);
                }
            }
            case OpTypes.RANGE_PRECEDES : {
                Object[] left  = nodes[LEFT].getRowValue(session);
                Object[] right = nodes[RIGHT].getRowValue(session);

                return DateTimeType.precedes(session, left,
                                             nodes[LEFT].nodeDataTypes, right,
                                             nodes[RIGHT].nodeDataTypes);
            }
            case OpTypes.RANGE_IMMEDIATELY_PRECEDES : {
                Object[] left  = nodes[LEFT].getRowValue(session);
                Object[] right = nodes[RIGHT].getRowValue(session);

                return DateTimeType.immediatelyPrecedes(session, left,
                        nodes[LEFT].nodeDataTypes, right,
                        nodes[RIGHT].nodeDataTypes);
            }
            case OpTypes.RANGE_IMMEDIATELY_SUCCEEDS : {
                Object[] left  = nodes[LEFT].getRowValue(session);
                Object[] right = nodes[RIGHT].getRowValue(session);

                return DateTimeType.immediatelySucceeds(session, left,
                        nodes[LEFT].nodeDataTypes, right,
                        nodes[RIGHT].nodeDataTypes);
            }
            case OpTypes.RANGE_SUCCEEDS : {
                Object[] left  = nodes[LEFT].getRowValue(session);
                Object[] right = nodes[RIGHT].getRowValue(session);

                return DateTimeType.succeeds(session, left,
                                             nodes[LEFT].nodeDataTypes, right,
                                             nodes[RIGHT].nodeDataTypes);
            }
            case OpTypes.IN : {
                return testInCondition(session);
            }
            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 : {
                return testMatchCondition(session);
            }
            case OpTypes.NOT_DISTINCT :
                return testNotDistinctCondition(session);

            case OpTypes.UNIQUE : {
                nodes[LEFT].materialise(session);

                return nodes[LEFT].table.hasUniqueNotNullRows(session)
                       ? Boolean.TRUE
                       : Boolean.FALSE;
            }
            case OpTypes.EXISTS : {
                return testExistsCondition(session);
            }
            case OpTypes.NOT : {
                Boolean result = (Boolean) nodes[LEFT].getValue(session);

                return result == null ? null
                                      : result.booleanValue() ? Boolean.FALSE
                                                              : Boolean.TRUE;
            }
            case OpTypes.AND : {
                Boolean r1 = (Boolean) nodes[LEFT].getValue(session);

                if (Boolean.FALSE.equals(r1)) {
                    return Boolean.FALSE;
                }

                Boolean r2 = (Boolean) nodes[RIGHT].getValue(session);

                if (Boolean.FALSE.equals(r2)) {
                    return Boolean.FALSE;
                }

                if (r1 == null || r2 == null) {
                    return null;
                }

                return Boolean.TRUE;
            }
            case OpTypes.OR : {
                Boolean r1 = (Boolean) nodes[LEFT].getValue(session);

                if (Boolean.TRUE.equals(r1)) {
                    return Boolean.TRUE;
                }

                Boolean r2 = (Boolean) nodes[RIGHT].getValue(session);

                if (Boolean.TRUE.equals(r2)) {
                    return Boolean.TRUE;
                }

                if (r1 == null || r2 == null) {
                    return null;
                }

                return Boolean.FALSE;
            }
            case OpTypes.EQUAL :
            case OpTypes.GREATER :
            case OpTypes.GREATER_EQUAL :
            case OpTypes.SMALLER_EQUAL :
            case OpTypes.GREATER_EQUAL_PRE :
            case OpTypes.SMALLER :
            case OpTypes.NOT_EQUAL : {
                if (exprSubType == OpTypes.ANY_QUANTIFIED
                        || exprSubType == OpTypes.ALL_QUANTIFIED) {
                    return testAllAnyCondition(session);
                }

                Object o1 = nodes[LEFT].getValue(session);
                Object o2 = nodes[RIGHT].getValue(session);

                if (nodes[LEFT].dataType != null
                        && nodes[LEFT].dataType.isArrayType()) {
                    return compareValues(session, o1, o2);
                }

                if (o1 instanceof Object[]) {
                    if (o2 != null && !(o2 instanceof Object[])) {
                        throw Error.runtimeError(ErrorCode.U_S0500,
                                                 "ExpressionLogical");
                    }

                    return compareValues(session, (Object[]) o1,
                                         (Object[]) o2);
                } else {
                    if (o2 instanceof Object[]) {
                        o2 = ((Object[]) o2)[0];
                    }

                    return compareValues(session, o1, o2);
                }
            }
            default :
                throw Error.runtimeError(ErrorCode.U_S0500,
                                         "ExpressionLogical");
        }
    }

    /**
     * For MATCH SIMPLE and FULL expressions, nulls in left are handled
     * prior to calling this method
     */
    private Boolean compareValues(Session session, Object left, Object right) {

        int result = 0;

        if (left == null || right == null) {
            return null;
        }

        result = nodes[LEFT].dataType.compare(session, left, right, opType);

        switch (opType) {

            case OpTypes.EQUAL :
                return result == 0 ? Boolean.TRUE
                                   : Boolean.FALSE;

            case OpTypes.NOT_EQUAL :
                return result != 0 ? Boolean.TRUE
                                   : Boolean.FALSE;

            case OpTypes.GREATER :
                return result > 0 ? Boolean.TRUE
                                  : Boolean.FALSE;

            case OpTypes.GREATER_EQUAL :
            case OpTypes.GREATER_EQUAL_PRE :
                return result >= 0 ? Boolean.TRUE
                                   : Boolean.FALSE;

            case OpTypes.SMALLER_EQUAL :
                return result <= 0 ? Boolean.TRUE
                                   : Boolean.FALSE;

            case OpTypes.SMALLER :
                return result < 0 ? Boolean.TRUE
                                  : Boolean.FALSE;

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

    /**
     * For MATCH SIMPLE and FULL expressions, nulls in left are handled
     * prior to calling this method
     */
    private Boolean compareValues(Session session, Object[] leftList,
                                  Object[] rightList) {

        int     result  = 0;
        boolean hasNull = false;

        if (leftList == null || rightList == null) {
            return null;
        }

        for (int i = 0; i < nodes[LEFT].nodes.length; i++) {
            if (leftList[i] == null) {
                if (opType == OpTypes.MATCH_PARTIAL
                        || opType == OpTypes.MATCH_UNIQUE_PARTIAL) {
                    continue;
                }

                hasNull = true;
            }

            if (rightList[i] == null) {
                hasNull = true;
            }

            Object leftValue  = leftList[i];
            Object rightValue = rightList[i];
            Type[] types      = nodes[LEFT].nodeDataTypes;

            result = types[i].compare(session, leftValue, rightValue);

            if (result != 0) {
                break;
            }
        }

        switch (opType) {

            case OpTypes.MATCH_SIMPLE :
            case OpTypes.MATCH_UNIQUE_SIMPLE :
            case OpTypes.MATCH_PARTIAL :
            case OpTypes.MATCH_UNIQUE_PARTIAL :
            case OpTypes.MATCH_FULL :
            case OpTypes.MATCH_UNIQUE_FULL :
            case OpTypes.NOT_DISTINCT :
                return result == 0 ? Boolean.TRUE
                                   : Boolean.FALSE;

            case OpTypes.IN :
            case OpTypes.EQUAL :
                if (hasNull) {
                    return null;
                }

                return result == 0 ? Boolean.TRUE
                                   : Boolean.FALSE;

            case OpTypes.NOT_EQUAL :
                if (hasNull) {
                    return null;
                }

                return result != 0 ? Boolean.TRUE
                                   : Boolean.FALSE;

            case OpTypes.GREATER :
                if (hasNull) {
                    return null;
                }

                return result > 0 ? Boolean.TRUE
                                  : Boolean.FALSE;

            case OpTypes.GREATER_EQUAL :
            case OpTypes.GREATER_EQUAL_PRE :
                if (hasNull) {
                    return null;
                }

                return result >= 0 ? Boolean.TRUE
                                   : Boolean.FALSE;

            case OpTypes.SMALLER_EQUAL :
                if (hasNull) {
                    return null;
                }

                return result <= 0 ? Boolean.TRUE
                                   : Boolean.FALSE;

            case OpTypes.SMALLER :
                if (hasNull) {
                    return null;
                }

                return result < 0 ? Boolean.TRUE
                                  : Boolean.FALSE;

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

    /**
     * Returns the result of testing a VALUE_LIST expression
     */
    private Boolean testInCondition(Session session) {

        Object[] data = nodes[LEFT].getRowValue(session);

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

        if (Expression.countNulls(data) != 0) {
            return null;
        }

        if (nodes[RIGHT].opType == OpTypes.VALUELIST) {
            final int length = nodes[RIGHT].nodes.length;

            for (int i = 0; i < length; i++) {
                Object[] rowData = nodes[RIGHT].nodes[i].getRowValue(session);

                if (Boolean.TRUE.equals(compareValues(session, data,
                                                      rowData))) {
                    return Boolean.TRUE;
                }
            }

            return Boolean.FALSE;
        }

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

    private Boolean testNotDistinctCondition(Session session) {

        Object[] leftData  = nodes[LEFT].getRowValue(session);
        Object[] rightData = nodes[RIGHT].getRowValue(session);

        if (leftData == null || rightData == null) {
            return leftData == rightData;
        }

        return compareValues(session, leftData, rightData);
    }

    private Boolean testMatchCondition(Session session) {

        Object[] data = nodes[LEFT].getRowValue(session);

        if (data == null) {
            return Boolean.TRUE;
        }

        final int nulls = countNulls(data);

        if (nulls != 0) {
            switch (opType) {

                case OpTypes.MATCH_SIMPLE :
                case OpTypes.MATCH_UNIQUE_SIMPLE :
                    return Boolean.TRUE;

                case OpTypes.MATCH_PARTIAL :
                case OpTypes.MATCH_UNIQUE_PARTIAL :
                    if (nulls == data.length) {
                        return Boolean.TRUE;
                    }
                    break;

                case OpTypes.MATCH_FULL :
                case OpTypes.MATCH_UNIQUE_FULL :
                    return nulls == data.length ? Boolean.TRUE
                                                : Boolean.FALSE;
            }
        }

        switch (nodes[RIGHT].opType) {

            case OpTypes.VALUELIST : {
                final int length   = nodes[RIGHT].nodes.length;
                boolean   hasMatch = false;

                for (int i = 0; i < length; i++) {
                    Object[] rowData =
                        nodes[RIGHT].nodes[i].getRowValue(session);
                    Boolean result = compareValues(session, data, rowData);

                    if (result == null || !result.booleanValue()) {
                        continue;
                    }

                    switch (opType) {

                        case OpTypes.MATCH_SIMPLE :
                        case OpTypes.MATCH_PARTIAL :
                        case OpTypes.MATCH_FULL :
                            return Boolean.TRUE;

                        case OpTypes.MATCH_UNIQUE_SIMPLE :
                        case OpTypes.MATCH_UNIQUE_PARTIAL :
                        case OpTypes.MATCH_UNIQUE_FULL :
                            if (hasMatch) {
                                return Boolean.FALSE;
                            }

                            hasMatch = true;
                            break;

                        default :
                    }
                }

                return hasMatch ? Boolean.TRUE
                                : Boolean.FALSE;
            }
            case OpTypes.TABLE_SUBQUERY : {
                PersistentStore store =
                    nodes[RIGHT].getTable().getRowStore(session);

                nodes[RIGHT].materialise(session);
                convertToType(session, data, nodes[LEFT].nodeDataTypes,
                              nodes[RIGHT].nodeDataTypes);

                if (nulls != 0
                        && (opType == OpTypes.MATCH_PARTIAL
                            || opType == OpTypes.MATCH_UNIQUE_PARTIAL)) {
                    boolean hasMatch = false;
                    RowIterator it =
                        nodes[RIGHT].getTable().rowIterator(session);

                    while (it.next()) {
                        Object[] rowData = it.getCurrent();
                        Boolean result = compareValues(session, data, rowData);

                        if (result == null) {
                            continue;
                        }

                        if (result.booleanValue()) {
                            if (opType == OpTypes.MATCH_PARTIAL) {
                                return Boolean.TRUE;
                            }

                            if (hasMatch) {
                                return Boolean.FALSE;
                            }

                            hasMatch = true;
                        }
                    }

                    return hasMatch ? Boolean.TRUE
                                    : Boolean.FALSE;
                }

                // now nulls == 0;
                RowIterator it = nodes[RIGHT].getTable().getFullIndex(
                    session).findFirstRow(session, store, data);
                boolean result = it.next();

                if (!result) {
                    return Boolean.FALSE;
                }

                switch (opType) {

                    case OpTypes.MATCH_SIMPLE :
                    case OpTypes.MATCH_PARTIAL :
                    case OpTypes.MATCH_FULL :
                        return Boolean.TRUE;
                }

                // only one match allowed for MATCH UNIQUE xxxx
                while (true) {
                    result = it.next();

                    if (!result) {
                        break;
                    }

                    Object[] rowData = it.getCurrent();

                    if (Boolean.TRUE.equals(compareValues(session, data,
                                                          rowData))) {
                        return Boolean.FALSE;
                    }
                }

                return Boolean.TRUE;
            }
            default : {
                throw Error.runtimeError(ErrorCode.U_S0500,
                                         "ExpressionLogical");
            }
        }
    }

    private Boolean testExistsCondition(Session session) {

        nodes[LEFT].materialise(session);

        return nodes[LEFT].getTable().isEmpty(session) ? Boolean.FALSE
                                                       : Boolean.TRUE;
    }

    private Boolean testAllAnyCondition(Session session) {

        Object[]     rowData = nodes[LEFT].getRowValue(session);
        TableDerived td      = nodes[RIGHT].table;

        td.materialiseCorrelated(session);

        Boolean result = getAllAnyValue(session, rowData, td);

        return result;
    }

    private Boolean getAllAnyValue(Session session, Object[] data,
                                   TableDerived td) {

        Table           table = td;
        boolean         empty = table.isEmpty(session);
        Index           index = table.getFullIndex(session);
        RowIterator     it;
        PersistentStore store = table.getRowStore(session);
        Object[]        firstdata;
        Object[]        lastdata;
        boolean         hasNullValue = false;

        for (int i = 0; i < table.columnCount; i++) {
            hasNullValue |= store.hasNull(i);
        }

        switch (exprSubType) {

            case OpTypes.ANY_QUANTIFIED : {
                if (empty) {
                    return Boolean.FALSE;
                }

                if (countNulls(data) == data.length) {
                    return null;
                }

                convertToType(session, data, nodes[LEFT].nodeDataTypes,
                              nodes[RIGHT].nodeDataTypes);

                if (opType == OpTypes.EQUAL) {
                    it = index.findFirstRow(session, store, data);

                    if (it.next()) {
                        return Boolean.TRUE;
                    } else {
                        if (hasNullValue) {
                            return null;
                        } else {
                            return Boolean.FALSE;
                        }
                    }
                }

                if (opType == OpTypes.NOT_EQUAL) {
                    it = index.firstRow(session, store, 0, null);
                } else {
                    it = index.findFirstRowNotNull(session, store);
                }

                if (!it.next()) {
                    return null;
                }

                firstdata = it.getCurrent();

                RowIterator lastIt = index.lastRow(session, store, 0, null);

                lastIt.next();

                lastdata = lastIt.getCurrent();

                Boolean comparefirst = compareValues(session, data, firstdata);
                Boolean comparelast  = compareValues(session, data, lastdata);

                switch (opType) {

                    case OpTypes.NOT_EQUAL :
                        if (Boolean.TRUE.equals(comparefirst)
                                || Boolean.TRUE.equals(comparelast)) {
                            return Boolean.TRUE;
                        } else if (Boolean.FALSE.equals(comparefirst)
                                   && Boolean.FALSE.equals(comparelast)) {
                            it = index.findFirstRow(session, store, data);

                            return Boolean.FALSE;
                        } else {
                            return null;
                        }
                    case OpTypes.GREATER :
                        return comparefirst;

                    case OpTypes.GREATER_EQUAL :
                    case OpTypes.GREATER_EQUAL_PRE :
                        return comparefirst;

                    case OpTypes.SMALLER :
                        return comparelast;

                    case OpTypes.SMALLER_EQUAL :
                        return comparelast;
                }

                break;
            }
            case OpTypes.ALL_QUANTIFIED : {
                if (empty) {
                    return Boolean.TRUE;
                }

                if (countNulls(data) == data.length) {
                    return null;
                }

                it = index.firstRow(session, store, 0, null);

                it.next();

                firstdata = it.getCurrent();

                if (countNulls(firstdata) == data.length) {
                    return null;
                }

                convertToType(session, data, nodes[LEFT].nodeDataTypes,
                              nodes[RIGHT].nodeDataTypes);

                it = index.findFirstRow(session, store, data);

                if (opType == OpTypes.EQUAL) {
                    if (it.next()) {
                        return store.elementCount(session) == 1 ? Boolean.TRUE
                                                                : Boolean
                                                                .FALSE;
                    } else {
                        return Boolean.FALSE;
                    }
                }

                if (opType == OpTypes.NOT_EQUAL) {
                    return it.next() ? Boolean.FALSE
                                     : Boolean.TRUE;
                }

                RowIterator lastIt = index.lastRow(session, store, 0, null);

                lastIt.next();

                lastdata = lastIt.getCurrent();

                Boolean comparefirst = compareValues(session, data, firstdata);
                Boolean comparelast  = compareValues(session, data, lastdata);

                switch (opType) {

                    case OpTypes.GREATER :
                        return comparelast;

                    case OpTypes.GREATER_EQUAL :
                    case OpTypes.GREATER_EQUAL_PRE :
                        return comparelast;

                    case OpTypes.SMALLER :
                        return comparefirst;

                    case OpTypes.SMALLER_EQUAL :
                        return comparefirst;
                }

                break;
            }
        }

        return null;
    }

    /**
     * Converts an OR containing an AND to an AND
     */
    void distributeOr() {

        if (opType != OpTypes.OR) {
            return;
        }

        if (nodes[LEFT].opType == OpTypes.AND) {
            opType = OpTypes.AND;

            Expression temp = new ExpressionLogical(OpTypes.OR,
                nodes[LEFT].nodes[RIGHT], nodes[RIGHT]);

            nodes[LEFT].opType       = OpTypes.OR;
            nodes[LEFT].nodes[RIGHT] = nodes[RIGHT];
            nodes[RIGHT]             = temp;
        } else if (nodes[RIGHT].opType == OpTypes.AND) {
            Expression temp = nodes[LEFT];

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

            distributeOr();

            return;
        }

        ((ExpressionLogical) nodes[LEFT]).distributeOr();
        ((ExpressionLogical) nodes[RIGHT]).distributeOr();
    }

    /**
     *
     */
    public boolean isIndexable(RangeVariable rangeVar) {

        boolean result;

        switch (opType) {

            case OpTypes.AND : {
                result = nodes[LEFT].isIndexable(rangeVar)
                         || nodes[RIGHT].isIndexable(rangeVar);

                return result;
            }
            case OpTypes.OR : {
                result = nodes[LEFT].isIndexable(rangeVar)
                         && nodes[RIGHT].isIndexable(rangeVar);

                return result;
            }
            default : {
                Expression temp = getIndexableExpression(rangeVar);

                return temp != null;
            }
        }
    }

    Expression getIndexableExpression(RangeVariable rangeVar) {

        switch (opType) {

            case OpTypes.IS_NULL :
                return nodes[LEFT].opType == OpTypes.COLUMN
                       && nodes[LEFT].isIndexable(rangeVar) ? this
                                                            : null;

            case OpTypes.NOT :
                return nodes[LEFT].opType == OpTypes.IS_NULL
                       && nodes[LEFT].nodes[LEFT].opType == OpTypes.COLUMN
                       && nodes[LEFT].nodes[LEFT].isIndexable(rangeVar) ? this
                                                                        : null;

            case OpTypes.EQUAL :
                if (exprSubType == OpTypes.ANY_QUANTIFIED) {
                    if (nodes[RIGHT].isCorrelated()) {
                        return null;
                    }

                    for (int node = 0; node < nodes[LEFT].nodes.length;
                            node++) {
                        if (nodes[LEFT].nodes[node].opType == OpTypes.COLUMN
                                && nodes[LEFT].nodes[node].isIndexable(
                                    rangeVar)) {
                            return this;
                        }
                    }

                    return null;
                }

            // fall through
            case OpTypes.GREATER :
            case OpTypes.GREATER_EQUAL :
            case OpTypes.GREATER_EQUAL_PRE :
            case OpTypes.SMALLER :
            case OpTypes.SMALLER_EQUAL :
                if (exprSubType != 0) {
                    return null;
                }

                if (nodes[RIGHT].isCorrelated()) {
                    return null;
                }

                if (nodes[LEFT].opType == OpTypes.COLUMN
                        && nodes[LEFT].isIndexable(rangeVar)) {
                    if (nodes[RIGHT].hasReference(rangeVar)) {
                        return null;
                    }

                    return this;
                }

                if (nodes[LEFT].hasReference(rangeVar)) {
                    return null;
                }

                if (nodes[RIGHT].opType == OpTypes.COLUMN
                        && nodes[RIGHT].isIndexable(rangeVar)) {
                    swapCondition();

                    return this;
                }

                return null;

            case OpTypes.OR :
                if (isIndexable(rangeVar)) {
                    return this;
                }

                return null;

            default :
                return null;
        }
    }

    /**
     * Called only on comparison expressions after reordering which have
     * a COLUMN left leaf
     */
    boolean isSimpleBound() {

        if (opType == OpTypes.IS_NULL) {
            return true;
        }

        if (nodes[RIGHT] != null) {
            if (nodes[RIGHT].opType == OpTypes.VALUE) {

                // also true for all parameters
                return true;
            }

            if (nodes[RIGHT].opType == OpTypes.SQL_FUNCTION) {
                if (((FunctionSQL) nodes[RIGHT]).isValueFunction()) {
                    return true;
                }
            }
        }

        return false;
    }

    boolean convertToSmaller() {

        switch (opType) {

            case OpTypes.GREATER_EQUAL :
            case OpTypes.GREATER :
                swapCondition();

                return true;

            case OpTypes.SMALLER_EQUAL :
            case OpTypes.SMALLER :
                return true;

            default :
                return false;
        }
    }

    /**
     * Swap the condition with its complement
     */
    void swapCondition() {

        int i = OpTypes.EQUAL;

        switch (opType) {

            case OpTypes.GREATER_EQUAL :
            case OpTypes.GREATER_EQUAL_PRE :
                i = OpTypes.SMALLER_EQUAL;
                break;

            case OpTypes.SMALLER_EQUAL :
                i = OpTypes.GREATER_EQUAL;
                break;

            case OpTypes.SMALLER :
                i = OpTypes.GREATER;
                break;

            case OpTypes.GREATER :
                i = OpTypes.SMALLER;
                break;

            case OpTypes.NOT_DISTINCT :
                i = OpTypes.NOT_DISTINCT;
                break;

            case OpTypes.EQUAL :
                break;

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

        opType = i;

        Expression e = nodes[LEFT];

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

    boolean reorderComparison(Session session, Expression parent) {

        Expression colExpression    = null;
        Expression nonColExpression = null;
        boolean    left             = false;
        boolean    replaceColumn    = false;
        int        operation        = 0;

        if (nodes[LEFT].opType == OpTypes.ADD) {
            operation = OpTypes.SUBTRACT;
            left      = true;
        } else if (nodes[LEFT].opType == OpTypes.SUBTRACT) {
            operation = OpTypes.ADD;
            left      = true;
        } else if (nodes[RIGHT].opType == OpTypes.ADD) {
            operation = OpTypes.SUBTRACT;
        } else if (nodes[RIGHT].opType == OpTypes.SUBTRACT) {
            operation = OpTypes.ADD;
        }

        if (operation == 0) {
            return false;
        }

        if (left) {
            if (nodes[LEFT].nodes[LEFT].opType == OpTypes.COLUMN) {
                colExpression    = nodes[LEFT].nodes[LEFT];
                nonColExpression = nodes[LEFT].nodes[RIGHT];
            } else if (nodes[LEFT].nodes[RIGHT].opType == OpTypes.COLUMN) {
                replaceColumn    = operation == OpTypes.ADD;
                colExpression    = nodes[LEFT].nodes[RIGHT];
                nonColExpression = nodes[LEFT].nodes[LEFT];
            }
        } else {
            if (nodes[RIGHT].nodes[LEFT].opType == OpTypes.COLUMN) {
                colExpression    = nodes[RIGHT].nodes[LEFT];
                nonColExpression = nodes[RIGHT].nodes[RIGHT];
            } else if (nodes[RIGHT].nodes[RIGHT].opType == OpTypes.COLUMN) {
                replaceColumn    = operation == OpTypes.ADD;
                colExpression    = nodes[RIGHT].nodes[RIGHT];
                nonColExpression = nodes[RIGHT].nodes[LEFT];
            }
        }

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

        Expression           otherExpression = left ? nodes[RIGHT]
                                                    : nodes[LEFT];
        ExpressionArithmetic newArg          = null;

        if (!replaceColumn) {
            newArg = new ExpressionArithmetic(operation, otherExpression,
                                              nonColExpression);

            newArg.resolveTypesForArithmetic(session, parent);
        }

        if (left) {
            if (replaceColumn) {
                nodes[RIGHT]             = colExpression;
                nodes[LEFT].nodes[RIGHT] = otherExpression;

                ((ExpressionArithmetic) nodes[LEFT]).resolveTypesForArithmetic(
                    session, parent);
            } else {
                nodes[LEFT]  = colExpression;
                nodes[RIGHT] = newArg;
            }
        } else {
            if (replaceColumn) {
                nodes[LEFT]               = colExpression;
                nodes[RIGHT].nodes[RIGHT] = otherExpression;

                ((ExpressionArithmetic) nodes[RIGHT])
                    .resolveTypesForArithmetic(session, parent);
            } else {
                nodes[RIGHT] = colExpression;
                nodes[LEFT]  = newArg;
            }
        }

        return true;
    }

    boolean isConditionRangeVariable(RangeVariable range) {

        if (nodes[LEFT].getRangeVariable() == range) {
            return true;
        }

        if (nodes[RIGHT].getRangeVariable() == range) {
            return true;
        }

        return false;
    }

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

        for (int i = 0; i < nodes.length; i++) {
            nodes[i].getJoinRangeVariables(ranges, list);
        }
    }

    double costFactor(Session session, RangeVariable rangeVar, int operation) {

        double cost;

        switch (opType) {

            case OpTypes.OR : {
                return nodes[LEFT].costFactor(session, rangeVar, opType)
                       + nodes[RIGHT].costFactor(session, rangeVar, opType);
            }
            case OpTypes.RANGE_CONTAINS :
            case OpTypes.RANGE_EQUALS :
            case OpTypes.RANGE_OVERLAPS :
            case OpTypes.RANGE_IMMEDIATELY_PRECEDES :
            case OpTypes.RANGE_IMMEDIATELY_SUCCEEDS :
            case OpTypes.RANGE_PRECEDES :
            case OpTypes.RANGE_SUCCEEDS :
            case OpTypes.IN :
            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.NOT_DISTINCT : {
                PersistentStore store =
                    rangeVar.rangeTable.getRowStore(session);

                cost = store.elementCount();

                if (cost < Index.minimumSelectivity) {
                    cost = Index.minimumSelectivity;
                }

                break;
            }
            case OpTypes.IS_NULL :
            case OpTypes.NOT : {
                cost = costFactorUnaryColumn(session, rangeVar);

                break;
            }
            case OpTypes.EQUAL : {
                switch (exprSubType) {

                    case OpTypes.ANY_QUANTIFIED : {
                        if (nodes[LEFT].opType == OpTypes.COLUMN
                                && nodes[LEFT].getRangeVariable()
                                   == rangeVar) {
                            cost = costFactorColumns(session, rangeVar);
                            cost *= 1024;

                            break;
                        }
                    }

                    // fall through
                    case OpTypes.ALL_QUANTIFIED : {
                        PersistentStore store =
                            rangeVar.rangeTable.getRowStore(session);

                        cost = store.elementCount();

                        if (cost < Index.minimumSelectivity) {
                            cost = Index.minimumSelectivity;
                        }

                        cost *= 1024;

                        break;
                    }
                    default :
                        cost = costFactorColumns(session, rangeVar);
                }

                break;
            }
            case OpTypes.GREATER :
            case OpTypes.GREATER_EQUAL :
            case OpTypes.GREATER_EQUAL_PRE :
            case OpTypes.SMALLER :
            case OpTypes.SMALLER_EQUAL : {
                cost = costFactorColumns(session, rangeVar);

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

        return cost;
    }

    double costFactorUnaryColumn(Session session, RangeVariable rangeVar) {

        if (nodes[LEFT].opType == OpTypes.COLUMN
                && nodes[LEFT].getRangeVariable() == rangeVar) {
            return nodes[LEFT].costFactor(session, rangeVar, opType);
        } else {
            PersistentStore store = rangeVar.rangeTable.getRowStore(session);
            double          cost  = store.elementCount();

            return cost < Index.minimumSelectivity ? Index.minimumSelectivity
                                                   : cost;
        }
    }

    double costFactorColumns(Session session, RangeVariable rangeVar) {

        double cost = 0;

        if (nodes[LEFT].opType == OpTypes.COLUMN
                && nodes[LEFT].getRangeVariable() == rangeVar) {
            if (!nodes[RIGHT].hasReference(rangeVar)) {
                cost = nodes[LEFT].costFactor(session, rangeVar, opType);
            }
        } else if (nodes[RIGHT].opType == OpTypes.COLUMN
                   && nodes[RIGHT].getRangeVariable() == rangeVar) {
            if (!nodes[LEFT].hasReference(rangeVar)) {
                cost = nodes[RIGHT].costFactor(session, rangeVar, opType);
            }
        } else {
            PersistentStore store = rangeVar.rangeTable.getRowStore(session);

            cost = store.elementCount();
        }

        if (cost == 0) {
            PersistentStore store = rangeVar.rangeTable.getRowStore(session);

            cost = store.elementCount();
        }

        if (cost < Index.minimumSelectivity) {
            cost = Index.minimumSelectivity;
        }

        return cost;
    }
}
