/* * 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; } }