/*

   Derby - Class org.apache.derby.impl.sql.compile.OrNode

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to you under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */

package	org.apache.derby.impl.sql.compile;

import java.util.List;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.services.context.ContextManager;
import org.apache.derby.shared.common.sanity.SanityManager;

class OrNode extends BinaryLogicalOperatorNode
{
	/* Is this the 1st OR in the OR chain? */
	private boolean firstOr;

	/**
     * Constructor for an OrNode
	 *
	 * @param leftOperand	The left operand of the OR
	 * @param rightOperand	The right operand of the OR
     * @param cm            The context manager
	 */

    OrNode(ValueNode leftOperand, ValueNode rightOperand, ContextManager cm)
	{
        super(leftOperand, rightOperand, "or", cm);
		this.shortCircuitValue = true;
	}

	/**
	 * Mark this OrNode as the 1st OR in the OR chain.
	 * We will consider converting the chain to an IN list
	 * during preprocess() if all entries are of the form:
	 *		ColumnReference = expression
	 */
	void setFirstOr()
	{
		firstOr = true;
	}

	/**
	 * Bind this logical operator.  All that has to be done for binding
	 * a logical operator is to bind the operands, check that both operands
	 * are BooleanDataValue, and set the result type to BooleanDataValue.
	 *
	 * @param fromList			The query's FROM list
	 * @param subqueryList		The subquery list being built as we find SubqueryNodes
     * @param aggregates        The aggregate list being built as we find AggregateNodes
	 *
	 * @return	The new top of the expression tree.
	 *
	 * @exception StandardException		Thrown on error
	 */
    @Override
    ValueNode bindExpression(
        FromList fromList, SubqueryList subqueryList, List<AggregateNode> aggregates)
			throws StandardException
	{
        super.bindExpression(fromList, subqueryList, aggregates);
		postBindFixup();
		return this;
	}
	
	/**
	 * Preprocess an expression tree.  We do a number of transformations
	 * here (including subqueries, IN lists, LIKE and BETWEEN) plus
	 * subquery flattening.
	 * NOTE: This is done before the outer ResultSetNode is preprocessed.
	 *
	 * @param	numTables			Number of tables in the DML Statement
	 * @param	outerFromList		FromList from outer query block
	 * @param	outerSubqueryList	SubqueryList from outer query block
	 * @param	outerPredicateList	PredicateList from outer query block
	 *
	 * @return		The modified expression
	 *
	 * @exception StandardException		Thrown on error
	 */
    @Override
    ValueNode preprocess(int numTables,
								FromList outerFromList,
								SubqueryList outerSubqueryList,
								PredicateList outerPredicateList) 
					throws StandardException
	{
		super.preprocess(numTables,
						 outerFromList, outerSubqueryList, 
						 outerPredicateList);

		/* If this is the first OR in the OR chain then we will
		 * consider converting it to an IN list and then performing
		 * whatever IN list conversions/optimizations are available.
		 * An OR can be converted to an IN list if all of the entries
		 * in the chain are of the form:
		 *		ColumnReference = x
		 *	or:
		 *		x = ColumnReference
		 * where all ColumnReferences are from the same table.
         *
         * We only convert the OR chain to an IN list if it has been
         * normalized to conjunctive normal form (CNF) first. That is, the
         * shape of the chain must be something like this:
         *
         *               OR
         *              /  \
         *             =    OR
         *                 /  \
         *                =   OR
         *                    / \
         *                   =   FALSE
         *
         * Predicates in WHERE, HAVING and ON clauses will have been
         * normalized by the time we get here. Boolean expressions other
         * places in the query are not necessarily normalized, but they
         * won't benefit from IN list conversion anyway, since they cannot
         * be used as qualifiers in a multi-probe scan, so simply skip the
         * conversion in those cases.
		 */
		if (firstOr)
		{
			boolean			convert = true;
			ColumnReference	cr = null;
			int				columnNumber = -1;
			int				tableNumber = -1;
            ValueNode       vn;

            for (vn = this;
                    vn instanceof OrNode;
                    vn = ((OrNode) vn).getRightOperand())
			{
				OrNode on = (OrNode) vn;
				ValueNode left = on.getLeftOperand();

				// Is the operator an =
				if (!left.isRelationalOperator())
				{
					/* If the operator is an IN-list disguised as a relational
					 * operator then we can still convert it--we'll just
					 * combine the existing IN-list ("left") with the new IN-
					 * list values.  So check for that case now.
					 */ 

					if (SanityManager.DEBUG)
					{
						/* At the time of writing the only way a call to
						 * left.isRelationalOperator() would return false for
						 * a BinaryRelationalOperatorNode was if that node
						 * was for an IN-list probe predicate.  That's why we
						 * we can get by with the simple "instanceof" check
						 * below.  But if we're running in SANE mode, do a
						 * quick check to make sure that's still valid.
					 	 */
						if (left instanceof BinaryRelationalOperatorNode)
						{
                            BinaryRelationalOperatorNode bron =
                                    (BinaryRelationalOperatorNode)left;
							if (!bron.isInListProbeNode())
							{
								SanityManager.THROWASSERT(
								"isRelationalOperator() unexpectedly returned "
								+ "false for a BinaryRelationalOperatorNode.");
							}
						}
					}

					convert = (left instanceof BinaryRelationalOperatorNode);
					if (!convert)
						break;
				}

				if (!(((RelationalOperator)left).getOperator() == RelationalOperator.EQUALS_RELOP))
				{
					convert = false;
					break;
				}

				BinaryRelationalOperatorNode bron = (BinaryRelationalOperatorNode)left;

				if (bron.getLeftOperand() instanceof ColumnReference)
				{
					cr = (ColumnReference) bron.getLeftOperand();
					if (tableNumber == -1)
					{
						tableNumber = cr.getTableNumber();
						columnNumber = cr.getColumnNumber();
					}
					else if (tableNumber != cr.getTableNumber() ||
							 columnNumber != cr.getColumnNumber())
					{
						convert = false;
						break;
					}
				}
				else if (bron.getRightOperand() instanceof ColumnReference)
				{
					cr = (ColumnReference) bron.getRightOperand();
					if (tableNumber == -1)
					{
						tableNumber = cr.getTableNumber();
						columnNumber = cr.getColumnNumber();
					}
					else if (tableNumber != cr.getTableNumber() ||
							 columnNumber != cr.getColumnNumber())
					{
						convert = false;
						break;
					}
				}
				else
				{
					convert = false;
					break;
				}
			}

            // DERBY-6363: An OR chain on conjunctive normal form should be
            // terminated by a false BooleanConstantNode. If it is terminated
            // by some other kind of node, it is not on CNF, and it should
            // not be converted to an IN list.
            convert = convert && vn.isBooleanFalse();

			/* So, can we convert the OR chain? */
			if (convert)
			{
                ValueNodeList vnl = new ValueNodeList(getContextManager());
				// Build the IN list 
                for (vn = this;
                        vn instanceof OrNode;
                        vn = ((OrNode) vn).getRightOperand())
				{
					OrNode on = (OrNode) vn;
					BinaryRelationalOperatorNode bron =
						(BinaryRelationalOperatorNode) on.getLeftOperand();
					if (bron.isInListProbeNode())
					{
						/* If we have an OR between multiple IN-lists on the same
						 * column then just combine them into a single IN-list.
						 * Ex.
						 *
						 *   select ... from T1 where i in (2, 3) or i in (7, 10)
						 *
						 * effectively becomes:
						 *
						 *   select ... from T1 where i in (2, 3, 7, 10).
						 */
						vnl.destructiveAppend(
							bron.getInListOp().getRightOperandList());
					}
					else if (bron.getLeftOperand() instanceof ColumnReference)
					{
						vnl.addValueNode(bron.getRightOperand());
					}
					else
					{
						vnl.addValueNode(bron.getLeftOperand());
					}
				}

                InListOperatorNode ilon =
                        new InListOperatorNode(cr, vnl, getContextManager());

				// Transfer the result type info to the IN list
				ilon.setType(getTypeServices());

				/* We return the result of preprocess() on the
				 * IN list so that any compilation time transformations
				 * will be done.
				 */
				return ilon.preprocess(numTables,
						 outerFromList, outerSubqueryList, 
						 outerPredicateList);
			}
		}

		return this;
	}

	/**
	 * Eliminate NotNodes in the current query block.  We traverse the tree, 
	 * inverting ANDs and ORs and eliminating NOTs as we go.  We stop at 
	 * ComparisonOperators and boolean expressions.  We invert 
	 * ComparisonOperators and replace boolean expressions with 
	 * boolean expression = false.
	 * NOTE: Since we do not recurse under ComparisonOperators, there
	 * still could be NotNodes left in the tree.
	 *
	 * @param	underNotNode		Whether or not we are under a NotNode.
	 *							
	 *
	 * @return		The modified expression
	 *
	 * @exception StandardException		Thrown on error
	 */
    @Override
	ValueNode eliminateNots(boolean underNotNode) 
					throws StandardException
	{
		leftOperand = leftOperand.eliminateNots(underNotNode);
		rightOperand = rightOperand.eliminateNots(underNotNode);
		if (! underNotNode)
		{
			return this;
		}

		/* Convert the OrNode to an AndNode */
       AndNode andNode =
                new AndNode(leftOperand, rightOperand, getContextManager());
		andNode.setType(getTypeServices());
		return andNode;
	}

	/**
	 * Finish putting an expression into conjunctive normal
	 * form.  An expression tree in conjunctive normal form meets
	 * the following criteria:
	 *		o  If the expression tree is not null,
	 *		   the top level will be a chain of AndNodes terminating
	 *		   in a true BooleanConstantNode.
	 *		o  The left child of an AndNode will never be an AndNode.
	 *		o  Any right-linked chain that includes an AndNode will
	 *		   be entirely composed of AndNodes terminated by a true BooleanConstantNode.
	 *		o  The left child of an OrNode will never be an OrNode.
	 *		o  Any right-linked chain that includes an OrNode will
	 *		   be entirely composed of OrNodes terminated by a false BooleanConstantNode.
	 *		o  ValueNodes other than AndNodes and OrNodes are considered
	 *		   leaf nodes for purposes of expression normalization.
	 *		   In other words, we won't do any normalization under
	 *		   those nodes.
	 *
	 * In addition, we track whether or not we are under a top level AndNode.  
	 * SubqueryNodes need to know this for subquery flattening.
	 *
	 * @param	underTopAndNode		Whether or not we are under a top level AndNode.
	 *							
	 *
	 * @return		The modified expression
	 *
	 * @exception StandardException		Thrown on error
	 */
    @Override
    ValueNode changeToCNF(boolean underTopAndNode)
					throws StandardException
	{
		OrNode curOr = this;

		/* If rightOperand is an AndNode, then we must generate an 
		 * OrNode above it.
		 */
		if (rightOperand instanceof AndNode)
		{
           BooleanConstantNode falseNode =
                    new BooleanConstantNode(false, getContextManager());
            rightOperand =
                    new OrNode(rightOperand, falseNode, getContextManager());
			((OrNode) rightOperand).postBindFixup();
		}

		/* We need to ensure that the right chain is terminated by
		 * a false BooleanConstantNode.
		 */
		while (curOr.getRightOperand() instanceof OrNode)
		{
			curOr = (OrNode) curOr.getRightOperand();
		}

		/* Add the false BooleanConstantNode if not there yet */
		if (!(curOr.getRightOperand().isBooleanFalse()))
		{
           BooleanConstantNode falseNode =
                    new BooleanConstantNode(false, getContextManager());
            curOr.setRightOperand(new OrNode(
                    curOr.getRightOperand(), falseNode, getContextManager()));
			((OrNode) curOr.getRightOperand()).postBindFixup();
		}

		/* If leftOperand is an OrNode, then we modify the tree from:
		 *
		 *				this
		 *			   /	\
		 *			Or2		Nodex
		 *		   /	\		...
		 *		left2	right2
		 *
		 *	to:
		 *
		 *						this
		 *					   /	\
		 *	left2.changeToCNF()		 Or2
		 *							/	\
		 *		right2.changeToCNF()	 Nodex.changeToCNF()
		 *
		 *	NOTE: We could easily switch places between left2.changeToCNF() and 
		 *  right2.changeToCNF().
		 */

		while (leftOperand instanceof OrNode)
		{
			ValueNode newLeft;
			OrNode	  oldLeft;
			OrNode	  newRight;
			ValueNode oldRight;

			/* For "clarity", we first get the new and old operands */
			newLeft = ((OrNode) leftOperand).getLeftOperand();
			oldLeft = (OrNode) leftOperand;
			newRight = (OrNode) leftOperand;
			oldRight = rightOperand;

			/* We then twiddle the tree to match the above diagram */
			leftOperand = newLeft;
			rightOperand = newRight;
			newRight.setLeftOperand(oldLeft.getRightOperand());
			newRight.setRightOperand(oldRight);
		}

		/* Finally, we continue to normalize the left and right subtrees. */
		leftOperand = leftOperand.changeToCNF(false);
		rightOperand = rightOperand.changeToCNF(false);

		return this;
	}

	/**
	 * Verify that changeToCNF() did its job correctly.  Verify that:
	 *		o  AndNode  - rightOperand is not instanceof OrNode
	 *				      leftOperand is not instanceof AndNode
	 *		o  OrNode	- rightOperand is not instanceof AndNode
	 *					  leftOperand is not instanceof OrNode
	 *
	 * @return		Boolean which reflects validity of the tree.
	 */
    @Override
    boolean verifyChangeToCNF()
	{
		boolean isValid = true;

		if (SanityManager.ASSERT)
		{
			isValid = ((rightOperand instanceof OrNode) ||
					   (rightOperand.isBooleanFalse()));
			if (rightOperand instanceof OrNode)
			{
				isValid = rightOperand.verifyChangeToCNF();
			}
			if (leftOperand instanceof OrNode)
			{
				isValid = false;
			}
			else
			{
				isValid = isValid && leftOperand.verifyChangeToCNF();
			}
		}

		return isValid;
	}

	/**
	 * Do bind() by hand for an AndNode that was generated after bind(),
	 * eg by putAndsOnTop(). (Set the data type and nullability info.)
	 *
	 * @exception StandardException		Thrown on error
	 */
	void postBindFixup()
					throws StandardException
	{
		setType(resolveLogicalBinaryOperator(
							leftOperand.getTypeServices(),
							rightOperand.getTypeServices()
											)
				);
	}
}
