diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/BinaryLogicOperatorNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/BinaryLogicOperatorNode.java index 152eedfb80..f83615af1b 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/BinaryLogicOperatorNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/BinaryLogicOperatorNode.java @@ -23,6 +23,8 @@ */ package org.hibernate.hql.internal.ast.tree; +import java.util.Arrays; + import antlr.SemanticException; import antlr.collections.AST; @@ -191,9 +193,7 @@ public class BinaryLogicOperatorNode extends HqlSqlWalkerNode implements BinaryO protected 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] = "?"; - } + Arrays.fill( rtn, "?" ); return rtn; } else if ( operand.getType() == HqlSqlTokenTypes.VECTOR_EXPR ) { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/InLogicOperatorNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/InLogicOperatorNode.java index 7be415278f..6bb5e12421 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/InLogicOperatorNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/InLogicOperatorNode.java @@ -121,50 +121,70 @@ public class InLogicOperatorNode extends BinaryLogicOperatorNode implements Bina || ( !ParameterNode.class.isInstance( getLeftHandOperand() ) ) ? null : ( (ParameterNode) getLeftHandOperand() ) .getHqlParameterSpecification(); - /** - * only one element in "in" cluster, e.g. - * where (a,b) in ( (1,2) ) this will be mutated to - * where a=1 and b=2 - */ + + final boolean negated = getType() == HqlSqlTokenTypes.NOT_IN; + if ( rhsNode != null && rhsNode.getNextSibling() == null ) { - String[] rhsElementTexts = extractMutationTexts( rhsNode, - rhsColumnSpan ); - setType( HqlSqlTokenTypes.AND ); - setText( "AND" ); - ParameterSpecification rhsEmbeddedCompositeParameterSpecification = rhsNode == null - || ( !ParameterNode.class.isInstance( rhsNode ) ) ? null - : ( (ParameterNode) rhsNode ) - .getHqlParameterSpecification(); - translate( lhsColumnSpan, HqlSqlTokenTypes.EQ, "=", lhsElementTexts, + /** + * only one element in the vector grouping. + * where (a,b) in ( (1,2) ) this will be mutated to + * where a=1 and b=2 + */ + String[] rhsElementTexts = extractMutationTexts( rhsNode, rhsColumnSpan ); + setType( negated ? HqlTokenTypes.OR : HqlSqlTokenTypes.AND ); + setText( negated ? "or" : "and" ); + ParameterSpecification rhsEmbeddedCompositeParameterSpecification = + rhsNode == null || ( !ParameterNode.class.isInstance( rhsNode ) ) + ? null + : ( (ParameterNode) rhsNode ).getHqlParameterSpecification(); + translate( + lhsColumnSpan, + negated ? HqlSqlTokenTypes.NE : HqlSqlTokenTypes.EQ, + negated ? "<>" : "=", + lhsElementTexts, rhsElementTexts, lhsEmbeddedCompositeParameterSpecification, - rhsEmbeddedCompositeParameterSpecification, this ); - } else { + rhsEmbeddedCompositeParameterSpecification, + this + ); + } + else { List andElementsNodeList = new ArrayList(); while ( rhsNode != null ) { - String[] rhsElementTexts = extractMutationTexts( rhsNode, - rhsColumnSpan ); - AST and = getASTFactory().create( HqlSqlTokenTypes.AND, "AND" ); - ParameterSpecification rhsEmbeddedCompositeParameterSpecification = rhsNode == null - || ( !ParameterNode.class.isInstance( rhsNode ) ) ? null - : ( (ParameterNode) rhsNode ) - .getHqlParameterSpecification(); - translate( lhsColumnSpan, HqlSqlTokenTypes.EQ, "=", - lhsElementTexts, rhsElementTexts, + String[] rhsElementTexts = extractMutationTexts( rhsNode, rhsColumnSpan ); + AST group = getASTFactory().create( + negated ? HqlSqlTokenTypes.OR : HqlSqlTokenTypes.AND, + negated ? "or" : "and" + ); + ParameterSpecification rhsEmbeddedCompositeParameterSpecification = + rhsNode == null || ( !ParameterNode.class.isInstance( rhsNode ) ) + ? null + : ( (ParameterNode) rhsNode ).getHqlParameterSpecification(); + translate( + lhsColumnSpan, + negated ? HqlSqlTokenTypes.NE : HqlSqlTokenTypes.EQ, + negated ? "<>" : "=", + lhsElementTexts, + rhsElementTexts, lhsEmbeddedCompositeParameterSpecification, - rhsEmbeddedCompositeParameterSpecification, and ); - andElementsNodeList.add( and ); + rhsEmbeddedCompositeParameterSpecification, + group + ); + andElementsNodeList.add( group ); rhsNode = (Node) rhsNode.getNextSibling(); } - setType( HqlSqlTokenTypes.OR ); - setText( "OR" ); + setType( negated ? HqlSqlTokenTypes.AND : HqlSqlTokenTypes.OR ); + setText( negated ? "and" : "or" ); AST curNode = this; for ( int i = andElementsNodeList.size() - 1; i > 1; i-- ) { - AST or = getASTFactory().create( HqlSqlTokenTypes.OR, "OR" ); - curNode.setFirstChild( or ); - curNode = or; + AST group = getASTFactory().create( + negated ? HqlSqlTokenTypes.AND : HqlSqlTokenTypes.OR, + negated ? "and" : "or" + ); + curNode.setFirstChild( group ); + curNode = group; AST and = (AST) andElementsNodeList.get( i ); - or.setNextSibling( and ); + group.setNextSibling( and ); } AST node0 = (AST) andElementsNodeList.get( 0 ); AST node1 = (AST) andElementsNodeList.get( 1 ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/TupleSupportTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/TupleSupportTest.java new file mode 100644 index 0000000000..5e962a1638 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/TupleSupportTest.java @@ -0,0 +1,130 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * 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, as published by the Free Software Foundation. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.hql; + +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; + +import java.util.Collections; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.query.spi.HQLQueryPlan; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.junit4.BaseUnitTestCase; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-7757" ) +public class TupleSupportTest extends BaseUnitTestCase { + @Entity( name = "TheEntity" ) + public static class TheEntity { + @Id + private Long id; + @Embedded + private TheComposite compositeValue; + } + + @Embeddable + public static class TheComposite { + private String thing1; + private String thing2; + + public TheComposite() { + } + + public TheComposite(String thing1, String thing2) { + this.thing1 = thing1; + this.thing2 = thing2; + } + } + + private SessionFactory sessionFactory; + + @Before + public void buildSessionFactory() { + Configuration cfg = new Configuration() + .addAnnotatedClass( TheEntity.class ); + cfg.getProperties().put( AvailableSettings.DIALECT, NoTupleSupportDialect.class.getName() ); + cfg.getProperties().put( AvailableSettings.HBM2DDL_AUTO, "create-drop" ); + sessionFactory = cfg.buildSessionFactory(); + } + + @After + public void releaseSessionFactory() { + sessionFactory.close(); + } + + @Test + public void testImplicitTupleNotEquals() { + final String hql = "from TheEntity e where e.compositeValue <> :p1"; + HQLQueryPlan queryPlan = ( (SessionFactoryImplementor) sessionFactory ).getQueryPlanCache() + .getHQLQueryPlan( hql, false, Collections.emptyMap() ); + + assertEquals( 1, queryPlan.getSqlStrings().length ); + System.out.println( " SQL : " + queryPlan.getSqlStrings()[0] ); + assertTrue( queryPlan.getSqlStrings()[0].contains( "<>" ) ); + } + + @Test + public void testImplicitTupleNotInList() { + final String hql = "from TheEntity e where e.compositeValue not in (:p1,:p2)"; + HQLQueryPlan queryPlan = ( (SessionFactoryImplementor) sessionFactory ).getQueryPlanCache() + .getHQLQueryPlan( hql, false, Collections.emptyMap() ); + + assertEquals( 1, queryPlan.getSqlStrings().length ); + System.out.println( " SQL : " + queryPlan.getSqlStrings()[0] ); + assertTrue( queryPlan.getSqlStrings()[0].contains( "<>" ) ); + } + + public static class NoTupleSupportDialect extends H2Dialect { + @Override + public boolean supportsRowValueConstructorSyntax() { + return false; + } + + @Override + public boolean supportsRowValueConstructorSyntaxInInList() { + return false; + } + } +}