/*
* Copyright (c) 2013, OpenCloudDB/MyCAT and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software;Designed and Developed mainly by many Chinese
* opensource volunteers. you can redistribute it and/or modify it under the
* terms of the GNU General Public License version 2 only, as published by the
* Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Any questions about this component can be directed to it's project Web address
* https://code.google.com/p/opencloudb/.
*
*/
package com.akiban.sql.compiler;
import com.akiban.sql.parser.*;
import com.akiban.sql.StandardException;
import com.akiban.sql.types.DataTypeDescriptor;
import com.akiban.sql.types.TypeId;
/** Perform normalization such as CNF on boolean expressions. */
public class BooleanNormalizer implements Visitor
{
public static final int NOT_IN_AND_LIMIT = 100;
SQLParserContext parserContext;
NodeFactory nodeFactory;
public BooleanNormalizer(SQLParserContext parserContext) {
this.parserContext = parserContext;
this.nodeFactory = parserContext.getNodeFactory();
}
/** Normalize conditions anywhere in this statement. */
public StatementNode normalize(StatementNode stmt) throws StandardException {
return (StatementNode)stmt.accept(this);
}
/** Normalize WHERE clause in this SELECT node. */
public void selectNode(SelectNode node) throws StandardException {
node.setWhereClause(normalizeExpression(node.getWhereClause()));
node.setHavingClause(normalizeExpression(node.getHavingClause()));
}
/** Normalize ON clause in this JOIN node. */
public void joinNode(JoinNode node) throws StandardException {
node.setJoinClause(normalizeExpression(node.getJoinClause()));
}
/** Normalize WHEN clause in this CASE node. */
public void conditionalNode(ConditionalNode node) throws StandardException {
node.setTestCondition(normalizeExpression(node.getTestCondition()));
}
/** Normalize a top-level boolean expression. */
public ValueNode normalizeExpression(ValueNode boolClause) throws StandardException {
/* For each expression tree:
* o Eliminate NOTs (eliminateNots())
* o Ensure that there is an AndNode on top of every
* top level expression. (putAndsOnTop())
* o Finish the job (changeToCNF())
*/
if (boolClause != null) {
boolClause = eliminateNots(boolClause, false);
assert verifyEliminateNots(boolClause);
boolClause = putAndsOnTop(boolClause);
assert verifyPutAndsOnTop(boolClause);
boolClause = changeToCNF(boolClause, true);
assert verifyChangeToCNF(boolClause, true);
}
return boolClause;
}
/**
* 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 node An expression node.
* @param underNotNode Whether or not we are under a NotNode.
*
* @return The modified expression
*
* @exception StandardException Thrown on error
*/
protected ValueNode eliminateNots(ValueNode node, boolean underNotNode)
throws StandardException {
switch (node.getNodeType()) {
case NodeTypes.NOT_NODE:
{
NotNode notNode = (NotNode)node;
return eliminateNots(notNode.getOperand(), !underNotNode);
}
case NodeTypes.AND_NODE:
case NodeTypes.OR_NODE:
{
BinaryLogicalOperatorNode bnode = (BinaryLogicalOperatorNode)node;
ValueNode leftOperand = bnode.getLeftOperand();
ValueNode rightOperand = bnode.getRightOperand();
leftOperand = eliminateNots(leftOperand, underNotNode);
rightOperand = eliminateNots(rightOperand, underNotNode);
if (underNotNode) {
/* Convert AND to OR and vice versa. */
BinaryLogicalOperatorNode cnode = (BinaryLogicalOperatorNode)
nodeFactory.getNode((node.getNodeType() == NodeTypes.AND_NODE) ?
NodeTypes.OR_NODE : NodeTypes.AND_NODE,
leftOperand, rightOperand,
parserContext);
cnode.setType(bnode.getType());
return cnode;
}
else {
bnode.setLeftOperand(leftOperand);
bnode.setRightOperand(rightOperand);
}
}
break;
case NodeTypes.CONDITIONAL_NODE:
{
ConditionalNode conditionalNode = (ConditionalNode)node;
ValueNode thenNode = conditionalNode.getThenNode();
ValueNode elseNode = conditionalNode.getElseNode();
// TODO: Derby does not do this; is there any benefit?
thenNode = eliminateNots(thenNode, false);
elseNode = eliminateNots(elseNode, false);
if (underNotNode) {
ValueNode swap = thenNode;
thenNode = elseNode;
elseNode = swap;
}
conditionalNode.setThenNode(thenNode);
conditionalNode.setElseNode(elseNode);
}
break;
case NodeTypes.IS_NODE:
{
IsNode isNode = (IsNode)node;
ValueNode leftOperand = isNode.getLeftOperand();
leftOperand = eliminateNots(leftOperand, underNotNode);
isNode.setLeftOperand(leftOperand);
if (underNotNode)
isNode.toggleNegated();
}
break;
case NodeTypes.IS_NULL_NODE:
case NodeTypes.IS_NOT_NULL_NODE:
if (underNotNode) {
UnaryOperatorNode unode = (UnaryOperatorNode)node;
ValueNode operand = unode.getOperand();
int newNodeType;
switch (node.getNodeType()) {
case NodeTypes.IS_NULL_NODE:
newNodeType = NodeTypes.IS_NOT_NULL_NODE;
break;
case NodeTypes.IS_NOT_NULL_NODE:
newNodeType = NodeTypes.IS_NULL_NODE;
break;
default:
assert false;
newNodeType = -1;
}
ValueNode newNode = (ValueNode)nodeFactory.getNode(newNodeType,
operand,
parserContext);
newNode.setType(unode.getType());
return newNode;
}
break;
case NodeTypes.BINARY_EQUALS_OPERATOR_NODE:
case NodeTypes.BINARY_GREATER_EQUALS_OPERATOR_NODE:
case NodeTypes.BINARY_GREATER_THAN_OPERATOR_NODE:
case NodeTypes.BINARY_LESS_THAN_OPERATOR_NODE:
case NodeTypes.BINARY_LESS_EQUALS_OPERATOR_NODE:
case NodeTypes.BINARY_NOT_EQUALS_OPERATOR_NODE:
if (underNotNode) {
BinaryOperatorNode onode = (BinaryOperatorNode)node;
ValueNode leftOperand = onode.getLeftOperand();
ValueNode rightOperand = onode.getRightOperand();
int newNodeType;
switch (node.getNodeType()) {
case NodeTypes.BINARY_EQUALS_OPERATOR_NODE:
newNodeType = NodeTypes.BINARY_NOT_EQUALS_OPERATOR_NODE;
break;
case NodeTypes.BINARY_GREATER_EQUALS_OPERATOR_NODE:
newNodeType = NodeTypes.BINARY_LESS_THAN_OPERATOR_NODE;
break;
case NodeTypes.BINARY_GREATER_THAN_OPERATOR_NODE:
newNodeType = NodeTypes.BINARY_LESS_EQUALS_OPERATOR_NODE;
break;
case NodeTypes.BINARY_LESS_THAN_OPERATOR_NODE:
newNodeType = NodeTypes.BINARY_GREATER_EQUALS_OPERATOR_NODE;
break;
case NodeTypes.BINARY_LESS_EQUALS_OPERATOR_NODE:
newNodeType = NodeTypes.BINARY_GREATER_THAN_OPERATOR_NODE;
break;
case NodeTypes.BINARY_NOT_EQUALS_OPERATOR_NODE:
newNodeType = NodeTypes.BINARY_EQUALS_OPERATOR_NODE;
break;
default:
assert false;
newNodeType = -1;
}
ValueNode newNode = (ValueNode)nodeFactory.getNode(newNodeType,
leftOperand, rightOperand,
parserContext);
newNode.setType(onode.getType());
return newNode;
}
break;
case NodeTypes.BETWEEN_OPERATOR_NODE:
if (underNotNode) {
BetweenOperatorNode betweenOperatorNode = (BetweenOperatorNode)node;
ValueNode leftOperand = betweenOperatorNode.getLeftOperand();
ValueNodeList rightOperandList = betweenOperatorNode.getRightOperandList();
/* We want to convert the BETWEEN into < OR > as described below. */
/* Convert:
* leftO between rightOList.elementAt(0) and rightOList.elementAt(1)
* to:
* leftO < rightOList.elementAt(0) or leftO > rightOList.elementAt(1)
* NOTE - We do the conversion here since ORs will eventually be
* optimizable and there's no benefit for the optimizer to see NOT BETWEEN
*/
/* leftO < rightOList.elementAt(0) */
BinaryComparisonOperatorNode leftBCO = (BinaryComparisonOperatorNode)
nodeFactory.getNode(NodeTypes.BINARY_LESS_THAN_OPERATOR_NODE,
leftOperand,
rightOperandList.get(0),
parserContext);
/* leftO > rightOList.elementAt(1) */
BinaryComparisonOperatorNode rightBCO = (BinaryComparisonOperatorNode)
nodeFactory.getNode(NodeTypes.BINARY_GREATER_THAN_OPERATOR_NODE,
leftOperand,
rightOperandList.get(1),
parserContext);
/* Create and return the OR */
OrNode newOr = (OrNode)nodeFactory.getNode(NodeTypes.OR_NODE,
leftBCO,
rightBCO,
parserContext);
/* Work out types (only nullability). */
DataTypeDescriptor leftType = leftOperand.getType();
DataTypeDescriptor right0Type = rightOperandList.get(0).getType();
DataTypeDescriptor right1Type = rightOperandList.get(1).getType();
boolean orNullable = false;
if ((leftType != null) && (right0Type != null)) {
boolean nullable = leftType.isNullable() || right0Type.isNullable();
DataTypeDescriptor leftBCType = new DataTypeDescriptor(TypeId.BOOLEAN_ID,
nullable);
leftBCO.setType(leftBCType);
orNullable = nullable;
}
if ((leftType != null) && (right1Type != null)) {
boolean nullable = leftType.isNullable() || right1Type.isNullable();
DataTypeDescriptor rightBCType = new DataTypeDescriptor(TypeId.BOOLEAN_ID,
nullable);
rightBCO.setType(rightBCType);
orNullable |= nullable;
}
if ((leftType != null) && (right0Type != null) && (right1Type != null))
newOr.setType(new DataTypeDescriptor(TypeId.BOOLEAN_ID, orNullable));
return newOr;
}
break;
case NodeTypes.IN_LIST_OPERATOR_NODE:
/* We want to convert the IN List into = OR = ... as * described below. */
/* Convert:
* leftO IN rightOList.elementAt(0) , rightOList.elementAt(1) ...
* to:
* leftO <> rightOList.elementAt(0) AND leftO <> rightOList.elementAt(1) ...
* NOTE - We do the conversion here since the single table clauses
* can be pushed down and the optimizer may eventually have a filter factor
* for <>.
*/
if (underNotNode)
return inWithNestedTuples((InListOperatorNode)node);
break;
case NodeTypes.SUBQUERY_NODE:
if (underNotNode) {
SubqueryNode snode = (SubqueryNode)node;
SubqueryNode.SubqueryType subqueryType = snode.getSubqueryType();
switch (subqueryType) {
case IN:
subqueryType = SubqueryNode.SubqueryType.NOT_IN;
break;
case NOT_IN:
subqueryType = SubqueryNode.SubqueryType.IN;
break;
case EQ_ANY:
subqueryType = SubqueryNode.SubqueryType.NE_ALL;
break;
case EQ_ALL:
subqueryType = SubqueryNode.SubqueryType.NE_ANY;
break;
case NE_ANY:
subqueryType = SubqueryNode.SubqueryType.EQ_ALL;
break;
case NE_ALL:
subqueryType = SubqueryNode.SubqueryType.EQ_ANY;
break;
case GT_ANY:
subqueryType = SubqueryNode.SubqueryType.LE_ALL;
break;
case GT_ALL:
subqueryType = SubqueryNode.SubqueryType.LE_ANY;
break;
case GE_ANY:
subqueryType = SubqueryNode.SubqueryType.LT_ALL;
break;
case GE_ALL:
subqueryType = SubqueryNode.SubqueryType.LT_ANY;
break;
case LT_ANY:
subqueryType = SubqueryNode.SubqueryType.GE_ALL;
break;
case LT_ALL:
subqueryType = SubqueryNode.SubqueryType.GE_ANY;
break;
case LE_ANY:
subqueryType = SubqueryNode.SubqueryType.GT_ALL;
break;
case LE_ALL:
subqueryType = SubqueryNode.SubqueryType.GT_ANY;
break;
case EXISTS:
subqueryType = SubqueryNode.SubqueryType.NOT_EXISTS;
break;
case NOT_EXISTS:
subqueryType = SubqueryNode.SubqueryType.EXISTS;
break;
case EXPRESSION:
return equalsBooleanConstant(node, Boolean.FALSE);
default:
assert false : "NOT is not supported for this time of subquery";
return equalsBooleanConstant(node, Boolean.FALSE);
}
snode.setSubqueryType(subqueryType);
}
break;
case NodeTypes.BOOLEAN_CONSTANT_NODE:
if (underNotNode) {
BooleanConstantNode bnode = (BooleanConstantNode)node;
bnode.setBooleanValue(!bnode.getBooleanValue());
}
break;
case NodeTypes.SQL_BOOLEAN_CONSTANT_NODE:
if (underNotNode) {
ConstantNode cnode = (ConstantNode)node;
cnode.setValue(cnode.getValue() == Boolean.TRUE ? Boolean.FALSE : Boolean.TRUE);
}
break;
case NodeTypes.COLUMN_REFERENCE:
/* X -> (X = TRUE / FALSE) */
// NOTE: This happened in a different place in the original
// Derby, but that ended up only doing those along the
// right-hand branch, which does not seem consistent.
return equalsBooleanConstant(castToBoolean(node),
underNotNode ? Boolean.FALSE : Boolean.TRUE);
default:
if (underNotNode) {
return equalsBooleanConstant(castToBoolean(node), Boolean.FALSE);
}
else {
return castToBoolean(node);
}
}
return node;
}
protected ValueNode getNotEqual(ValueNode left, ValueNode right) throws StandardException
{
if (left instanceof RowConstructorNode)
{
if (right instanceof RowConstructorNode)
{
ValueNodeList leftList = ((RowConstructorNode)left).getNodeList();
ValueNodeList rightList = ((RowConstructorNode)right).getNodeList();
if (leftList.size() != rightList.size())
throw new IllegalArgumentException("Mismatched column count in IN's operand, left: "
+ leftList.size() + ", right: " + rightList.size());
ValueNode result = null;
for (int n = 0; n < leftList.size(); ++n)
{
ValueNode equalNode = getNotEqual(leftList.get(n), rightList.get(n));
if (result == null)
result = equalNode;
else
{
OrNode orNode = (OrNode)nodeFactory.getNode(NodeTypes.OR_NODE,
result, equalNode,
parserContext);
result = orNode;
}
}
return result;
}
else
throw new IllegalArgumentException("Mismatched column count in IN's operand");
}
else
{
if (right instanceof RowConstructorNode)
throw new IllegalArgumentException("Mismatched column count in IN's operands");
return (ValueNode) nodeFactory.getNode(NodeTypes.BINARY_NOT_EQUALS_OPERATOR_NODE,
left, right,
parserContext);
}
}
protected ValueNode inWithNestedTuples(InListOperatorNode node) throws StandardException
{
RowConstructorNode rightList = node.getRightOperandList();
if (rightList.getNodeList().size() > NOT_IN_AND_LIMIT) {
node.setNegated(true);
return node;
}
RowConstructorNode leftList = node.getLeftOperand();
ValueNode result = null;
boolean nested = leftList.getDepth() > 0;
ValueNode left = leftList.getNodeList().get(0);
for (ValueNode rightNode : rightList.getNodeList())
{
ValueNode equalNode = getNotEqual(nested
? leftList
: left
, rightNode);
if (result == null)
result = equalNode;
else
{
AndNode andNode = (AndNode)nodeFactory.getNode(NodeTypes.AND_NODE,
equalNode, result,
parserContext);
result = andNode;
}
}
return result;
}
protected ValueNode castToBoolean(ValueNode node) throws StandardException {
if ((node.getType() == null) ||
(node.getType().getTypeId().isBooleanTypeId()))
return node;
return (ValueNode)
nodeFactory.getNode(NodeTypes.CAST_NODE,
node,
new DataTypeDescriptor(TypeId.BOOLEAN_ID,
node.getType().isNullable()),
parserContext);
}
protected ValueNode equalsBooleanConstant(ValueNode node, Boolean constant)
throws StandardException {
BooleanConstantNode trueNode = (BooleanConstantNode)
nodeFactory.getNode(NodeTypes.BOOLEAN_CONSTANT_NODE,
constant,
parserContext);
BinaryComparisonOperatorNode equalsNode = (BinaryComparisonOperatorNode)
nodeFactory.getNode(NodeTypes.BINARY_EQUALS_OPERATOR_NODE,
node, trueNode,
parserContext);
if (node.getType() != null) {
boolean nullableResult = node.getType().isNullable();
equalsNode.setType(new DataTypeDescriptor(TypeId.BOOLEAN_ID,
nullableResult));
}
return equalsNode;
}
/**
* Verify that eliminateNots() did its job correctly. Verify that
* there are no NotNodes above the top level comparison operators
* and boolean expressions.
*
* @return Boolean which reflects validity of the tree.
*/
protected boolean verifyEliminateNots(ValueNode node) {
switch (node.getNodeType()) {
case NodeTypes.NOT_NODE:
return false;
case NodeTypes.AND_NODE:
case NodeTypes.OR_NODE:
{
BinaryLogicalOperatorNode bnode = (BinaryLogicalOperatorNode)node;
return verifyEliminateNots(bnode.getLeftOperand()) &&
verifyEliminateNots(bnode.getRightOperand());
}
}
return true;
}
/**
* Do the 1st step in putting an expression into conjunctive normal
* form. This step ensures that the top level of the expression is
* a chain of AndNodes terminated by a true BooleanConstantNode.
*
* @param node An expression node.
*
* @return The modified expression
*
* @exception StandardException Thrown on error
*/
protected AndNode putAndsOnTop(ValueNode node) throws StandardException {
switch (node.getNodeType()) {
case NodeTypes.AND_NODE:
{
AndNode andNode = (AndNode)node;
andNode.setRightOperand(putAndsOnTop(andNode.getRightOperand()));
return andNode;
}
default:
{
/* expr -> expr AND TRUE */
BooleanConstantNode trueNode = (BooleanConstantNode)
nodeFactory.getNode(NodeTypes.BOOLEAN_CONSTANT_NODE,
Boolean.TRUE,
parserContext);
AndNode andNode = (AndNode)nodeFactory.getNode(NodeTypes.AND_NODE,
node, trueNode,
parserContext);
if (node.getType() != null) {
boolean nullableResult = node.getType().isNullable();
andNode.setType(new DataTypeDescriptor(TypeId.BOOLEAN_ID,
nullableResult));
}
return andNode;
}
}
}
/**
* Verify that putAndsOnTop() did its job correctly. Verify that the top level
* of the expression is a chain of AndNodes terminated by a true BooleanConstantNode.
*
* @param node An expression node.
*
* @return Boolean which reflects validity of the tree.
*/
protected boolean verifyPutAndsOnTop(ValueNode node) {
while (true) {
if (!(node instanceof AndNode))
return false;
node = ((AndNode)node).getRightOperand();
if (node.isBooleanTrue())
return true;
// else another AND.
}
}
/**
* 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 node An expression node.
* @param underTopAndNode Whether or not we are under a top level AndNode.
*
* @return The modified expression
*
* @exception StandardException Thrown on error
*/
protected ValueNode changeToCNF(ValueNode node, boolean underTopAndNode)
throws StandardException {
switch (node.getNodeType()) {
case NodeTypes.AND_NODE:
{
AndNode andNode = (AndNode)node;
ValueNode leftOperand = andNode.getLeftOperand();
ValueNode rightOperand = andNode.getRightOperand();
/* Top chain will be a chain of Ands terminated by a non-AndNode.
* (putAndsOnTop() has taken care of this. If the last node in
* the chain is not a true BooleanConstantNode then we need to do the
* transformation to make it so.
*/
/* Add the true BooleanConstantNode if not there yet */
if (!(rightOperand instanceof AndNode) &&
!(rightOperand.isBooleanTrue())) {
BooleanConstantNode trueNode = (BooleanConstantNode)
nodeFactory.getNode(NodeTypes.BOOLEAN_CONSTANT_NODE,
Boolean.TRUE,
parserContext);
AndNode newRight = (AndNode)nodeFactory.getNode(NodeTypes.AND_NODE,
rightOperand, trueNode,
parserContext);
newRight.setType(rightOperand.getType());
rightOperand = newRight;
}
/* If leftOperand is an AndNode, then we modify the tree from:
*
* And1
* / \
* And2 Nodex
* / \ ...
* left2 right2
*
* to:
*
* And1
* / \
* changeToCNF(left2) And2
* / \
* changeToCNF(right2) changeToCNF(Nodex)
*
* NOTE: We could easily switch places between changeToCNF(left2) and
* changeToCNF(right2).
*/
/* Pull up the AndNode chain to our left */
while (leftOperand instanceof AndNode) {
AndNode oldLeft = (AndNode)leftOperand;
ValueNode oldRight = rightOperand;
ValueNode newLeft = oldLeft.getLeftOperand();
AndNode newRight = oldLeft;
/* We then twiddle the tree to match the above diagram */
leftOperand = newLeft;
rightOperand = newRight;
newRight.setLeftOperand(oldLeft.getRightOperand());
newRight.setRightOperand(oldRight);
}
/* We then twiddle the tree to match the above diagram */
leftOperand = changeToCNF(leftOperand, underTopAndNode);
rightOperand = changeToCNF(rightOperand, underTopAndNode);
andNode.setLeftOperand(leftOperand);
andNode.setRightOperand(rightOperand);
}
break;
case NodeTypes.OR_NODE:
{
OrNode orNode = (OrNode)node;
ValueNode leftOperand = orNode.getLeftOperand();
ValueNode rightOperand = orNode.getRightOperand();
/* If rightOperand is an AndNode, then we must generate an
* OrNode above it.
*/
if (rightOperand instanceof AndNode) {
BooleanConstantNode falseNode = (BooleanConstantNode)
nodeFactory.getNode(NodeTypes.BOOLEAN_CONSTANT_NODE,
Boolean.FALSE,
parserContext);
OrNode newRight = (OrNode)nodeFactory.getNode(NodeTypes.OR_NODE,
rightOperand, falseNode,
parserContext);
newRight.setType(rightOperand.getType());
rightOperand = newRight;
orNode.setRightOperand(rightOperand);
}
/* We need to ensure that the right chain is terminated by
* a false BooleanConstantNode.
*/
while (rightOperand instanceof OrNode) {
orNode = (OrNode)orNode.getRightOperand();
rightOperand = orNode.getRightOperand();
}
/* Add the false BooleanConstantNode if not there yet */
if (!rightOperand.isBooleanFalse()) {
BooleanConstantNode falseNode = (BooleanConstantNode)
nodeFactory.getNode(NodeTypes.BOOLEAN_CONSTANT_NODE,
Boolean.FALSE,
parserContext);
OrNode newRight = (OrNode)nodeFactory.getNode(NodeTypes.OR_NODE,
rightOperand,
falseNode,
parserContext);
newRight.setType(rightOperand.getType());
orNode.setRightOperand(newRight);
}
orNode = (OrNode)node;
rightOperand = orNode.getRightOperand();
/* If leftOperand is an OrNode, then we modify the tree from:
*
* Or1
* / \
* Or2 Nodex
* / \ ...
* left2 right2
*
* to:
*
* Or1
* / \
* changeToCNF(left2) Or2
* / \
* changeToCNF(right2) changeToCNF(Nodex)
*
* NOTE: We could easily switch places between changeToCNF(left2) and
* changeToCNF(right2).
*/
while (leftOperand instanceof OrNode) {
OrNode oldLeft = (OrNode)leftOperand;
ValueNode oldRight = rightOperand;
ValueNode newLeft = oldLeft.getLeftOperand();
OrNode newRight = oldLeft;
/* 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 = changeToCNF(leftOperand, false);
rightOperand = changeToCNF(rightOperand, false);
orNode.setLeftOperand(leftOperand);
orNode.setRightOperand(rightOperand);
}
break;
// TODO: subquery node to pick up underTopAndNode for flattening.
// BinaryComparisonOperatorNode for that case.
}
return node;
}
/**
* 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
*
* @param node An expression node.
*
* @return Boolean which reflects validity of the tree.
*/
protected boolean verifyChangeToCNF(ValueNode node, boolean top) {
if (node instanceof AndNode) {
AndNode andNode = (AndNode)node;
ValueNode leftOperand = andNode.getLeftOperand();
ValueNode rightOperand = andNode.getRightOperand();
boolean isValid = ((rightOperand instanceof AndNode) ||
rightOperand.isBooleanTrue());
if (rightOperand instanceof AndNode) {
isValid = verifyChangeToCNF(rightOperand, false);
}
if (leftOperand instanceof AndNode) {
isValid = false;
}
else {
isValid = isValid && verifyChangeToCNF(leftOperand, false);
}
return isValid;
}
if (top)
return false;
if (node instanceof OrNode) {
OrNode orNode = (OrNode)node;
ValueNode leftOperand = orNode.getLeftOperand();
ValueNode rightOperand = orNode.getRightOperand();
boolean isValid = ((rightOperand instanceof OrNode) ||
rightOperand.isBooleanFalse());
if (rightOperand instanceof OrNode) {
isValid = verifyChangeToCNF(rightOperand, false);
}
if (leftOperand instanceof OrNode) {
isValid = false;
}
else {
isValid = verifyChangeToCNF(leftOperand, false);
}
return isValid;
}
return true;
}
/* Visitor interface */
public Visitable visit(Visitable node) throws StandardException {
switch (((QueryTreeNode)node).getNodeType()) {
case NodeTypes.SELECT_NODE:
selectNode((SelectNode)node);
break;
case NodeTypes.JOIN_NODE:
case NodeTypes.HALF_OUTER_JOIN_NODE:
joinNode((JoinNode)node);
break;
case NodeTypes.CONDITIONAL_NODE:
conditionalNode((ConditionalNode)node);
break;
}
return node;
}
public boolean visitChildrenFirst(Visitable node) {
return true;
}
public boolean stopTraversal() {
return false;
}
public boolean skipChildren(Visitable node) throws StandardException {
return false;
}
}