diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/PropertiesSubqueryExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/PropertiesSubqueryExpression.java new file mode 100644 index 0000000000..69df2e56e9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/criterion/PropertiesSubqueryExpression.java @@ -0,0 +1,28 @@ +package org.hibernate.criterion; + +import org.hibernate.Criteria; +import org.hibernate.internal.util.StringHelper; + +/** + * A comparison between several properties value in the outer query and the result of a multicolumn subquery. + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public class PropertiesSubqueryExpression extends SubqueryExpression { + private final String[] propertyNames; + + protected PropertiesSubqueryExpression(String[] propertyNames, String op, DetachedCriteria dc) { + super( op, null, dc ); + this.propertyNames = propertyNames; + } + + @Override + protected String toLeftSqlString(Criteria criteria, CriteriaQuery outerQuery) { + StringBuilder left = new StringBuilder( "(" ); + final String[] sqlColumnNames = new String[propertyNames.length]; + for ( int i = 0; i < sqlColumnNames.length; ++i ) { + sqlColumnNames[i] = outerQuery.getColumn( criteria, propertyNames[i] ); + } + left.append( StringHelper.join( ", ", sqlColumnNames ) ); + return left.append( ")" ).toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/Subqueries.java b/hibernate-core/src/main/java/org/hibernate/criterion/Subqueries.java index 8cd2696505..3d974c7168 100755 --- a/hibernate-core/src/main/java/org/hibernate/criterion/Subqueries.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/Subqueries.java @@ -33,6 +33,7 @@ package org.hibernate.criterion; * @see Projection * @see org.hibernate.Criteria * @author Gavin King + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ public class Subqueries { @@ -59,6 +60,22 @@ public class Subqueries { public static Criterion propertyEq(String propertyName, DetachedCriteria dc) { return new PropertySubqueryExpression(propertyName, "=", null, dc); } + + public static Criterion propertiesEq(String[] propertyNames, DetachedCriteria dc) { + return new PropertiesSubqueryExpression(propertyNames, "=", dc); + } + + public static Criterion propertiesNotEq(String[] propertyNames, DetachedCriteria dc) { + return new PropertiesSubqueryExpression(propertyNames, "<>", dc); + } + + public static Criterion propertiesIn(String[] propertyNames, DetachedCriteria dc) { + return new PropertiesSubqueryExpression(propertyNames, "in", dc); + } + + public static Criterion propertiesNotIn(String[] propertyNames, DetachedCriteria dc) { + return new PropertiesSubqueryExpression(propertyNames, "not in", dc); + } public static Criterion propertyNe(String propertyName, DetachedCriteria dc) { return new PropertySubqueryExpression(propertyName, "<>", null, dc); diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/criteria/CriteriaQueryTest.java b/hibernate-core/src/matrix/java/org/hibernate/test/criteria/CriteriaQueryTest.java index fa2ba53854..cb02551266 100755 --- a/hibernate-core/src/matrix/java/org/hibernate/test/criteria/CriteriaQueryTest.java +++ b/hibernate-core/src/matrix/java/org/hibernate/test/criteria/CriteriaQueryTest.java @@ -59,6 +59,9 @@ import org.hibernate.test.hql.Animal; import org.hibernate.test.hql.Reptile; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; import org.hibernate.transform.Transformers; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.Type; @@ -72,11 +75,14 @@ import static org.junit.Assert.fail; /** * @author Gavin King + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ public class CriteriaQueryTest extends BaseCoreFunctionalTestCase { @Override public String[] getMappings() { - return new String[] { "criteria/Enrolment.hbm.xml","criteria/Foo.hbm.xml", "hql/Animal.hbm.xml" }; + return new String[] { + "criteria/Enrolment.hbm.xml", "criteria/Foo.hbm.xml", "hql/Animal.hbm.xml", "criteria/Person.hbm.xml" + }; } @Override @@ -1720,6 +1726,85 @@ public class CriteriaQueryTest extends BaseCoreFunctionalTestCase { } + @Test + @TestForIssue( jiraKey = "HHH-6766" ) + public void testMultiplePropertiesSubquery() { + Session session = openSession(); + Transaction tx = session.beginTransaction(); + + Man lukasz = new Man(); + lukasz.setName( "Lukasz" ); + lukasz.setHeight( 170 ); + lukasz.setWeight( 60 ); + session.persist( lukasz ); + + Man robert = new Man(); + robert.setName( "Robert" ); + robert.setHeight( 170 ); + robert.setWeight( 78 ); + session.persist( robert ); + + Woman kinga = new Woman(); + kinga.setName( "Kinga" ); + kinga.setHeight( 170 ); + kinga.setWeight( 60 ); + session.persist( kinga ); + + tx.commit(); + session.close(); + + session = openSession(); + tx = session.beginTransaction(); + + DetachedCriteria sizeQuery = DetachedCriteria.forClass( Man.class ).setProjection( + Projections.projectionList().add( Projections.property( "weight" ) ) + .add( Projections.property( "height" ) ) ) + .add( Restrictions.eq( "name", "Lukasz" ) + ); + + List result = session.createCriteria( Woman.class ) + .add( Subqueries.propertiesEq( new String[] { "weight", "height" }, sizeQuery ) ) + .list(); + assertEquals( 1, result.size() ); + assertEquals( kinga, result.get( 0 ) ); + + if (getDialect().supportsRowValueConstructorSyntaxInInList()) { + result = session.createCriteria( Woman.class ) + .add( Subqueries.propertiesIn( new String[] { "weight", "height" }, sizeQuery ) ) + .list(); + assertEquals( 1, result.size() ); + assertEquals( kinga, result.get( 0 ) ); + } + + tx.commit(); + session.close(); + + session = openSession(); + tx = session.beginTransaction(); + + sizeQuery = DetachedCriteria.forClass( Man.class ).setProjection( + Projections.projectionList().add( Projections.property( "weight" ) ) + .add( Projections.property( "height" ) ) ) + .add( Restrictions.ne( "name", "Lukasz" ) + ); + result = session.createCriteria( Woman.class ) + .add( Subqueries.propertiesNotEq( new String[] { "weight", "height" }, sizeQuery ) ) + .list(); + assertEquals( 1, result.size() ); + assertEquals( kinga, result.get( 0 ) ); + + if (getDialect().supportsRowValueConstructorSyntaxInInList()) { + result = session.createCriteria( Woman.class ) + .add( Subqueries.propertiesNotIn( new String[] { "weight", "height" }, sizeQuery ) ) + .list(); + assertEquals( 1, result.size() ); + assertEquals( kinga, result.get( 0 ) ); + } + + tx.commit(); + session.close(); + } + @Test public void testCriteriaCollectionOfComponent() { Session session = openSession(); diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/criteria/Man.java b/hibernate-core/src/matrix/java/org/hibernate/test/criteria/Man.java new file mode 100644 index 0000000000..5b3cd0cc61 --- /dev/null +++ b/hibernate-core/src/matrix/java/org/hibernate/test/criteria/Man.java @@ -0,0 +1,7 @@ +package org.hibernate.test.criteria; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public class Man extends Person { +} diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/criteria/Person.hbm.xml b/hibernate-core/src/matrix/java/org/hibernate/test/criteria/Person.hbm.xml new file mode 100644 index 0000000000..dec080e975 --- /dev/null +++ b/hibernate-core/src/matrix/java/org/hibernate/test/criteria/Person.hbm.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/criteria/Person.java b/hibernate-core/src/matrix/java/org/hibernate/test/criteria/Person.java new file mode 100644 index 0000000000..486dca5cc4 --- /dev/null +++ b/hibernate-core/src/matrix/java/org/hibernate/test/criteria/Person.java @@ -0,0 +1,70 @@ +package org.hibernate.test.criteria; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public abstract class Person { + private Long id; + + private String name; + + private Integer weight; + + private Integer height; + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( o == null || getClass() != o.getClass() ) return false; + + Person person = (Person) o; + + if ( height != null ? !height.equals( person.height ) : person.height != null ) return false; + if ( id != null ? !id.equals( person.id ) : person.id != null ) return false; + if ( name != null ? !name.equals( person.name ) : person.name != null ) return false; + if ( weight != null ? !weight.equals( person.weight ) : person.weight != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + ( name != null ? name.hashCode() : 0 ); + result = 31 * result + ( weight != null ? weight.hashCode() : 0 ); + result = 31 * result + ( height != null ? height.hashCode() : 0 ); + return result; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getWeight() { + return weight; + } + + public void setWeight(Integer weight) { + this.weight = weight; + } + + public Integer getHeight() { + return height; + } + + public void setHeight(Integer height) { + this.height = height; + } +} diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/criteria/Woman.java b/hibernate-core/src/matrix/java/org/hibernate/test/criteria/Woman.java new file mode 100644 index 0000000000..a8a13daf47 --- /dev/null +++ b/hibernate-core/src/matrix/java/org/hibernate/test/criteria/Woman.java @@ -0,0 +1,7 @@ +package org.hibernate.test.criteria; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public class Woman extends Person { +}