diff --git a/core/src/main/java/org/hibernate/hql/ast/SqlASTFactory.java b/core/src/main/java/org/hibernate/hql/ast/SqlASTFactory.java index 73ff4e9a5f..a42cfe2155 100644 --- a/core/src/main/java/org/hibernate/hql/ast/SqlASTFactory.java +++ b/core/src/main/java/org/hibernate/hql/ast/SqlASTFactory.java @@ -40,6 +40,8 @@ import org.hibernate.hql.ast.tree.JavaConstantNode; import org.hibernate.hql.ast.tree.SessionFactoryAwareNode; import org.hibernate.hql.ast.tree.BooleanLiteralNode; +import org.hibernate.hql.ast.tree.IsNullLogicOperatorNode; +import org.hibernate.hql.ast.tree.IsNotNullLogicOperatorNode; import java.lang.reflect.Constructor; @@ -157,7 +159,9 @@ public Class getASTNodeType(int tokenType) { case NOT_BETWEEN: return BetweenOperatorNode.class; case IS_NULL: + return IsNullLogicOperatorNode.class; case IS_NOT_NULL: + return IsNotNullLogicOperatorNode.class; case EXISTS: return UnaryLogicOperatorNode.class; default: diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/AbstractNullnessCheckNode.java b/core/src/main/java/org/hibernate/hql/ast/tree/AbstractNullnessCheckNode.java new file mode 100644 index 0000000000..e795fc49f4 --- /dev/null +++ b/core/src/main/java/org/hibernate/hql/ast/tree/AbstractNullnessCheckNode.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, v. 2.1. This program is distributed in the + * hope that it will be useful, but WITHOUT A WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. You should have received a + * copy of the GNU Lesser General Public License, v.2.1 along with this + * distribution; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Red Hat Author(s): Steve Ebersole + */ +package org.hibernate.hql.ast.tree; + +import antlr.collections.AST; + +import org.hibernate.type.Type; +import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.hql.antlr.HqlSqlTokenTypes; +import org.hibernate.util.StringHelper; +import org.hibernate.HibernateException; + +/** + * Base class for nodes dealing 'is null' and 'is not null' operators. + *

+ * todo : a good deal of this is copied from BinaryLogicOperatorNode; look at consolidating these code fragments + * + * @author Steve Ebersole + */ +public abstract class AbstractNullnessCheckNode extends UnaryLogicOperatorNode { + + /** + * {@inheritDoc} + */ + public void initialize() { + // TODO : this really needs to be delayed unitl after we definitively know the operand node type; + // where this is currently a problem is parameters for which where we cannot unequivocally + // resolve an expected type + Type operandType = extractDataType( getOperand() ); + if ( operandType == null ) { + return; + } + SessionFactoryImplementor sessionFactory = getSessionFactoryHelper().getFactory(); + int operandColumnSpan = operandType.getColumnSpan( sessionFactory ); + if ( operandColumnSpan > 1 ) { + mutateRowValueConstructorSyntax( operandColumnSpan ); + } + } + + /** + * When (if) we need to expand a row value constructor, what is the type of connector to use between the + * expansion fragments. + * + * @return The expansion connector type. + */ + protected abstract int getExpansionConnectorType(); + + /** + * When (if) we need to expand a row value constructor, what is the text of the connector to use between the + * expansion fragments. + * + * @return The expansion connector text. + */ + protected abstract String getExpansionConnectorText(); + + private void mutateRowValueConstructorSyntax(int operandColumnSpan) { + final int comparisonType = getType(); + final String comparisonText = getText(); + + final int expansionConnectorType = getExpansionConnectorType(); + final String expansionConnectorText = getExpansionConnectorText(); + + setType( expansionConnectorType ); + setText( expansionConnectorText ); + + String[] mutationTexts = extractMutationTexts( getOperand(), operandColumnSpan ); + + AST container = this; + for ( int i = operandColumnSpan - 1; i > 0; i-- ) { + if ( i == 1 ) { + AST op1 = getASTFactory().create( comparisonType, comparisonText ); + AST operand1 = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, mutationTexts[0] ); + op1.setFirstChild( operand1 ); + container.setFirstChild( op1 ); + AST op2 = getASTFactory().create( comparisonType, comparisonText ); + AST operand2 = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, mutationTexts[1] ); + op2.setFirstChild( operand2 ); + op1.setNextSibling( op2 ); + } + else { + AST op = getASTFactory().create( comparisonType, comparisonText ); + AST operand = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, mutationTexts[i] ); + op.setFirstChild( operand ); + AST newContainer = getASTFactory().create( expansionConnectorType, expansionConnectorText ); + container.setFirstChild( newContainer ); + newContainer.setNextSibling( op ); + container = newContainer; + } + } + } + + private static Type extractDataType(Node operand) { + Type type = null; + if ( operand instanceof SqlNode ) { + type = ( ( SqlNode ) operand ).getDataType(); + } + if ( type == null && operand instanceof ExpectedTypeAwareNode ) { + type = ( ( ExpectedTypeAwareNode ) operand ).getExpectedType(); + } + return type; + } + + private static String[] extractMutationTexts(Node operand, int count) { + if ( operand instanceof ParameterNode ) { + String[] rtn = new String[count]; + for ( int i = 0; i < count; i++ ) { + rtn[i] = "?"; + } + return rtn; + } + else if ( operand.getType() == HqlSqlTokenTypes.VECTOR_EXPR ) { + String[] rtn = new String[ operand.getNumberOfChildren() ]; + int x = 0; + AST node = operand.getFirstChild(); + while ( node != null ) { + rtn[ x++ ] = node.getText(); + node = node.getNextSibling(); + } + return rtn; + } + else if ( operand instanceof SqlNode ) { + String nodeText = operand.getText(); + if ( nodeText.startsWith( "(" ) ) { + nodeText = nodeText.substring( 1 ); + } + if ( nodeText.endsWith( ")" ) ) { + nodeText = nodeText.substring( 0, nodeText.length() - 1 ); + } + String[] splits = StringHelper.split( ", ", nodeText ); + if ( count != splits.length ) { + throw new HibernateException( "SqlNode's text did not reference expected number of columns" ); + } + return splits; + } + else { + throw new HibernateException( "dont know how to extract row value elements from node : " + operand ); + } + } +} diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/IsNotNullLogicOperatorNode.java b/core/src/main/java/org/hibernate/hql/ast/tree/IsNotNullLogicOperatorNode.java new file mode 100644 index 0000000000..30a816fe51 --- /dev/null +++ b/core/src/main/java/org/hibernate/hql/ast/tree/IsNotNullLogicOperatorNode.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, v. 2.1. This program is distributed in the + * hope that it will be useful, but WITHOUT A WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. You should have received a + * copy of the GNU Lesser General Public License, v.2.1 along with this + * distribution; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Red Hat Author(s): Steve Ebersole + */ +package org.hibernate.hql.ast.tree; + +import org.hibernate.hql.antlr.HqlSqlTokenTypes; + +/** + * IsNotNullLogicOperatorNode implementation + * + * @author Steve Ebersole + */ +public class IsNotNullLogicOperatorNode extends AbstractNullnessCheckNode { + protected int getExpansionConnectorType() { + return HqlSqlTokenTypes.OR; + } + + protected String getExpansionConnectorText() { + return "OR"; + } +} diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/IsNullLogicOperatorNode.java b/core/src/main/java/org/hibernate/hql/ast/tree/IsNullLogicOperatorNode.java new file mode 100644 index 0000000000..9b7b154d40 --- /dev/null +++ b/core/src/main/java/org/hibernate/hql/ast/tree/IsNullLogicOperatorNode.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, v. 2.1. This program is distributed in the + * hope that it will be useful, but WITHOUT A WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. You should have received a + * copy of the GNU Lesser General Public License, v.2.1 along with this + * distribution; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Red Hat Author(s): Steve Ebersole + */ +package org.hibernate.hql.ast.tree; + +import org.hibernate.hql.antlr.HqlSqlTokenTypes; + +/** + * Represents a 'is null' check. + * + * @author Steve Ebersole + */ +public class IsNullLogicOperatorNode extends AbstractNullnessCheckNode { + protected int getExpansionConnectorType() { + return HqlSqlTokenTypes.AND; + } + + protected String getExpansionConnectorText() { + return "AND"; + } +} diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/UnaryLogicOperatorNode.java b/core/src/main/java/org/hibernate/hql/ast/tree/UnaryLogicOperatorNode.java index 0064e9adf7..842edfecbd 100644 --- a/core/src/main/java/org/hibernate/hql/ast/tree/UnaryLogicOperatorNode.java +++ b/core/src/main/java/org/hibernate/hql/ast/tree/UnaryLogicOperatorNode.java @@ -4,9 +4,11 @@ import org.hibernate.Hibernate; /** - * @author Steve Ebersole + * Represents a unary operator node. + * + * @author Steve Ebersole */ -public class UnaryLogicOperatorNode extends SqlNode implements UnaryOperatorNode { +public class UnaryLogicOperatorNode extends HqlSqlWalkerNode implements UnaryOperatorNode { public Node getOperand() { return ( Node ) getFirstChild(); } diff --git a/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java b/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java index d20a94a5de..60730d213f 100644 --- a/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java +++ b/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java @@ -102,6 +102,41 @@ public static Test suite() { return new FunctionalTestClassTestSuite( ASTParserLoadingTest.class ); } + public void testComponentNullnessChecks() { + Session s = openSession(); + s.beginTransaction(); + Human h = new Human(); + h.setName( new Name( "Johnny", 'B', "Goode" ) ); + s.save( h ); + h = new Human(); + h.setName( new Name( "Steve", null, "Ebersole" ) ); + s.save( h ); + h = new Human(); + h.setName( new Name( "Bono", null, null ) ); + s.save( h ); + h = new Human(); + h.setName( new Name( null, null, null ) ); + s.save( h ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.beginTransaction(); + List results = s.createQuery( "from Human where name is null" ).list(); + assertEquals( 1, results.size() ); + results = s.createQuery( "from Human where name is not null" ).list(); + assertEquals( 3, results.size() ); + s.createQuery( "from Human where ? is null" ).setParameter( 0, null ).list(); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.beginTransaction(); + s.createQuery( "delete Human" ).executeUpdate(); + s.getTransaction().commit(); + s.close(); + } + public void testInvalidCollectionDereferencesFail() { Session s = openSession(); s.beginTransaction();