HHH-2826 : (HQL) <component> is [not] null

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@14096 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Steve Ebersole 2007-10-17 20:59:18 +00:00
parent 2af0d5b70b
commit 0a6a160709
6 changed files with 261 additions and 2 deletions

View File

@ -40,6 +40,8 @@ import org.hibernate.hql.ast.tree.InLogicOperatorNode;
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 SqlASTFactory extends ASTFactory implements HqlSqlTokenTypes {
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:

View File

@ -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.
* <p/>
* 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 );
}
}
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -4,9 +4,11 @@ import org.hibernate.type.Type;
import org.hibernate.Hibernate;
/**
* @author <a href="mailto:steve@hibernate.org">Steve Ebersole </a>
* 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();
}

View File

@ -102,6 +102,41 @@ public class ASTParserLoadingTest extends FunctionalTestCase {
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();