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 24aa7cb992..e02d065ef9 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 @@ -12,6 +12,7 @@ import org.hibernate.HibernateException; import org.hibernate.TypeMismatchException; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; +import org.hibernate.hql.internal.ast.QuerySyntaxException; import org.hibernate.hql.internal.ast.util.ColumnHelper; import org.hibernate.internal.util.StringHelper; import org.hibernate.param.ParameterSpecification; @@ -110,8 +111,22 @@ public class BinaryLogicOperatorNode extends AbstractSelectExpression implements // mutation depends on the types of nodes involved... int comparisonType = getType(); String comparisonText = getText(); - setType( HqlSqlTokenTypes.AND ); - setText( "AND" ); + + switch ( comparisonType ) { + case HqlSqlTokenTypes.EQ: + setType( HqlSqlTokenTypes.AND ); + setText( "AND" ); + break; + + case HqlSqlTokenTypes.NE: + setType( HqlSqlTokenTypes.OR ); + setText( "OR" ); + break; + + default: + throw new QuerySyntaxException( comparisonText + " operator not supported on composite types." ); + } + String[] lhsElementTexts = extractMutationTexts( getLeftHandOperand(), valueElements ); String[] rhsElementTexts = extractMutationTexts( getRightHandOperand(), valueElements ); @@ -175,7 +190,7 @@ public class BinaryLogicOperatorNode extends AbstractSelectExpression implements AST rhs = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, rhsElementTexts[i] ); op.setFirstChild( lhs ); lhs.setNextSibling( rhs ); - AST newContainer = getASTFactory().create( HqlSqlTokenTypes.AND, "AND" ); + AST newContainer = getASTFactory().create( container.getType(), container.getText() ); container.setFirstChild( newContainer ); newContainer.setNextSibling( op ); container = newContainer; diff --git a/hibernate-core/src/test/java/org/hibernate/test/cut/CompositeDateTime.java b/hibernate-core/src/test/java/org/hibernate/test/cut/CompositeDateTime.java new file mode 100644 index 0000000000..a252e68e5e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cut/CompositeDateTime.java @@ -0,0 +1,203 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2014, 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.cut; + +import java.io.Serializable; + +/** + * Class for testing composite user types with more than two fields. + * + * @author Etienne Miret + */ +public class CompositeDateTime implements Serializable { + + private static final long serialVersionUID = 7401750071679578453L; + + private Integer year; + + private Integer month; + + private Integer day; + + private Integer hour; + + private Integer minute; + + private Integer second; + + public CompositeDateTime(final Integer year, final Integer month, final Integer day, final Integer hour, + final Integer minute, final Integer second) { + super(); + this.year = year; + this.month = month; + this.day = day; + this.hour = hour; + this.minute = minute; + this.second = second; + } + + /* + * Constructor for those who hate auto (un)boxing. + */ + public CompositeDateTime(final int year, final int month, final int day, final int hour, + final int minute, final int second) { + this( new Integer( year ), Integer.valueOf( month ), Integer.valueOf( day ), Integer.valueOf( hour ), + Integer.valueOf( minute ), Integer.valueOf( second ) ); + } + + public CompositeDateTime(final CompositeDateTime other) { + super(); + this.year = other.year; + this.month = other.month; + this.day = other.day; + this.hour = other.hour; + this.minute = other.minute; + this.second = other.second; + } + + public Integer getYear() { + return year; + } + + public void setYear(final Integer year) { + this.year = year; + } + + public Integer getMonth() { + return month; + } + + public void setMonth(final Integer month) { + this.month = month; + } + + public Integer getDay() { + return day; + } + + public void setDay(final Integer day) { + this.day = day; + } + + public Integer getHour() { + return hour; + } + + public void setHour(final Integer hour) { + this.hour = hour; + } + + public Integer getMinute() { + return minute; + } + + public void setMinute(final Integer minute) { + this.minute = minute; + } + + public Integer getSecond() { + return second; + } + + public void setSecond(final Integer second) { + this.second = second; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ( ( day == null ) ? 0 : day.hashCode() ); + result = prime * result + ( ( hour == null ) ? 0 : hour.hashCode() ); + result = prime * result + ( ( minute == null ) ? 0 : minute.hashCode() ); + result = prime * result + ( ( month == null ) ? 0 : month.hashCode() ); + result = prime * result + ( ( second == null ) ? 0 : second.hashCode() ); + result = prime * result + ( ( year == null ) ? 0 : year.hashCode() ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( !( obj instanceof CompositeDateTime ) ) { + return false; + } + CompositeDateTime other = (CompositeDateTime) obj; + if ( day == null ) { + if ( other.day != null ) { + return false; + } + } + else if ( !day.equals( other.day ) ) { + return false; + } + if ( hour == null ) { + if ( other.hour != null ) { + return false; + } + } + else if ( !hour.equals( other.hour ) ) { + return false; + } + if ( minute == null ) { + if ( other.minute != null ) { + return false; + } + } + else if ( !minute.equals( other.minute ) ) { + return false; + } + if ( month == null ) { + if ( other.month != null ) { + return false; + } + } + else if ( !month.equals( other.month ) ) { + return false; + } + if ( second == null ) { + if ( other.second != null ) { + return false; + } + } + else if ( !second.equals( other.second ) ) { + return false; + } + if ( year == null ) { + if ( other.year != null ) { + return false; + } + } + else if ( !year.equals( other.year ) ) { + return false; + } + return true; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cut/CompositeDateTimeUserType.java b/hibernate-core/src/test/java/org/hibernate/test/cut/CompositeDateTimeUserType.java new file mode 100644 index 0000000000..b951a15e33 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cut/CompositeDateTimeUserType.java @@ -0,0 +1,158 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2014, 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.cut; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.Type; +import org.hibernate.usertype.CompositeUserType; + +/** + * Class for testing composite user types with more than two fields. + * + * @author Etienne Miret + */ +public class CompositeDateTimeUserType implements CompositeUserType { + + @Override + public String[] getPropertyNames() { + return new String[] { "year", "month", "day", "hour", "minute", "second" }; + } + + @Override + public Type[] getPropertyTypes() { + return new Type[] { StandardBasicTypes.INTEGER, StandardBasicTypes.INTEGER, StandardBasicTypes.INTEGER, + StandardBasicTypes.INTEGER, StandardBasicTypes.INTEGER, StandardBasicTypes.INTEGER }; + } + + @Override + public Object getPropertyValue(Object component, int property) throws HibernateException { + final CompositeDateTime dateTime = (CompositeDateTime) component; + switch ( property ) { + case 0: + return dateTime.getYear(); + + case 1: + return dateTime.getMonth(); + + case 2: + return dateTime.getDay(); + + case 3: + return dateTime.getHour(); + + case 4: + return dateTime.getMinute(); + + case 5: + return dateTime.getSecond(); + + default: + throw new HibernateException( "This type has only 6 fields." ); + } + } + + @Override + public void setPropertyValue(Object component, int property, Object value) throws HibernateException { + } + + @Override + public Class returnedClass() { + return CompositeDateTime.class; + } + + @Override + public boolean equals(Object x, Object y) throws HibernateException { + return x == null ? y == null : x.equals( y ); + } + + @Override + public int hashCode(Object x) throws HibernateException { + return x == null ? 0 : x.hashCode(); + } + + @Override + public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException { + final Integer year = rs.getObject( names[0], Integer.class ); + final Integer month = rs.getObject( names[1], Integer.class ); + final Integer day = rs.getObject( names[2], Integer.class ); + final Integer hour = rs.getObject( names[3], Integer.class ); + final Integer minute = rs.getObject( names[4], Integer.class ); + final Integer second = rs.getObject( names[5], Integer.class ); + if ( year == null && month == null && day == null && hour == null && minute == null && second == null ) { + return null; + } else { + return new CompositeDateTime( year, month, day, hour, minute, second ); + } + } + + @Override + public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException { + if ( value == null ) { + for (int i = 0; i < 6; i++ ) { + st.setObject( index + i, null ); + } + } else { + final CompositeDateTime dateTime = (CompositeDateTime) value; + st.setObject( index, dateTime.getYear() ); + st.setObject( index + 1, dateTime.getMonth() ); + st.setObject( index + 2, dateTime.getDay() ); + st.setObject( index + 3, dateTime.getHour() ); + st.setObject( index + 4, dateTime.getMinute() ); + st.setObject( index + 5, dateTime.getSecond() ); + } + } + + @Override + public CompositeDateTime deepCopy(Object value) throws HibernateException { + return value == null ? null : new CompositeDateTime( (CompositeDateTime) value ); + } + + @Override + public boolean isMutable() { + return true; + } + + @Override + public Serializable disassemble(Object value, SessionImplementor session) throws HibernateException { + return deepCopy( value ); + } + + @Override + public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException { + return deepCopy( cached ); + } + + @Override + public Object replace(Object original, Object target, SessionImplementor session, Object owner) throws HibernateException { + return deepCopy( original ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cut/CompositeUserTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/cut/CompositeUserTypeTest.java index 8967756963..e4569f966c 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/cut/CompositeUserTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/cut/CompositeUserTypeTest.java @@ -11,13 +11,16 @@ import java.math.BigDecimal; import java.util.Currency; import java.util.List; +import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.criterion.Restrictions; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.SybaseASE15Dialect; +import org.hibernate.hql.internal.ast.QuerySyntaxException; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.SkipForDialects; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -52,7 +55,8 @@ public class CompositeUserTypeTest extends BaseCoreFunctionalTestCase { assertEquals( result.size(), 1 ); result = s.createQuery("from Transaction where value = (1.5, 'AUD')").list(); assertEquals( result.size(), 1 ); - + result = s.createQuery( "from Transaction where value != (1.4, 'AUD')" ).list(); + assertEquals( result.size(), 1 ); } s.delete(tran); @@ -102,5 +106,185 @@ public class CompositeUserTypeTest extends BaseCoreFunctionalTestCase { } -} + /** + * Tests the {@code =} operator on composite types. + */ + public void testEqualOperator() { + final Session s = openSession(); + s.getTransaction().begin(); + final Transaction txn = new Transaction(); + txn.setDescription( "foo" ); + txn.setValue( new MonetoryAmount( new BigDecimal( 42 ), Currency.getInstance( "AUD" ) ) ); + txn.setTimestamp( new CompositeDateTime( 2014, 8, 23, 14, 35, 0 ) ); + s.persist( txn ); + + final Query q = s.createQuery( "from Transaction where value = :amount" ); + + /* Both amount and currency match. */ + q.setParameter( "amount", new MonetoryAmount( new BigDecimal( 42 ), Currency.getInstance( "AUD" ) ) ); + assertEquals( 1, q.list().size() ); + + /* Only currency matches. */ + q.setParameter( "amount", new MonetoryAmount( new BigDecimal( 36 ), Currency.getInstance( "AUD" ) ) ); + assertEquals( 0, q.list().size() ); + + /* Only amount matches. */ + q.setParameter( "amount", new MonetoryAmount( new BigDecimal( 42 ), Currency.getInstance( "EUR" ) ) ); + assertEquals( 0, q.list().size() ); + + /* None match. */ + q.setParameter( "amount", new MonetoryAmount( new BigDecimal( 76 ), Currency.getInstance( "USD" ) ) ); + assertEquals( 0, q.list().size() ); + + final Query qTimestamp = s.createQuery( "from Transaction where timestamp = :timestamp" ); + + /* All matches. */ + qTimestamp.setParameter( "timestamp", new CompositeDateTime( 2014, 8, 23, 14, 35, 0 ) ); + assertEquals( 1, qTimestamp.list().size() ); + + /* None matches. */ + qTimestamp.setParameter( "timestamp", new CompositeDateTime( 2013, 9, 25, 12, 31, 25 ) ); + assertEquals( 0, qTimestamp.list().size() ); + + /* Year doesn't match. */ + qTimestamp.setParameter( "timestamp", new CompositeDateTime( 2013, 8, 23, 14, 35, 0 ) ); + assertEquals( 0, qTimestamp.list().size() ); + + /* Month doesn't match. */ + qTimestamp.setParameter( "timestamp", new CompositeDateTime( 2014, 9, 23, 14, 35, 0 ) ); + assertEquals( 0, qTimestamp.list().size() ); + + /* Minute doesn't match. */ + qTimestamp.setParameter( "timestamp", new CompositeDateTime( 2014, 8, 23, 14, 41, 0 ) ); + assertEquals( 0, qTimestamp.list().size() ); + + /* Second doesn't match. */ + qTimestamp.setParameter( "timestamp", new CompositeDateTime( 2014, 8, 23, 14, 35, 28 ) ); + assertEquals( 0, qTimestamp.list().size() ); + + s.delete( txn ); + s.getTransaction().commit(); + s.close(); + } + + /** + * Tests the {@code <>} operator on composite types. + */ + @Test + @TestForIssue( jiraKey = "HHH-5946" ) + public void testNotEqualOperator() { + final Session s = openSession(); + s.getTransaction().begin(); + + final Transaction t1 = new Transaction(); + t1.setDescription( "foo" ); + t1.setValue( new MonetoryAmount( new BigDecimal( 178 ), Currency.getInstance( "EUR" ) ) ); + t1.setTimestamp( new CompositeDateTime( 2014, 8, 23, 14, 23, 0 ) ); + s.persist( t1 ); + + final Transaction t2 = new Transaction(); + t2.setDescription( "bar" ); + t2.setValue( new MonetoryAmount( new BigDecimal( 1000000 ), Currency.getInstance( "USD" ) ) ); + t1.setTimestamp( new CompositeDateTime( 2014, 8, 22, 14, 23, 0 ) ); + s.persist( t2 ); + + final Transaction t3 = new Transaction(); + t3.setDescription( "bar" ); + t3.setValue( new MonetoryAmount( new BigDecimal( 1000000 ), Currency.getInstance( "EUR" ) ) ); + t3.setTimestamp( new CompositeDateTime( 2014, 8, 22, 14, 23, 01 ) ); + s.persist( t3 ); + + final Query q1 = s.createQuery( "from Transaction where value <> :amount" ); + q1.setParameter( "amount", new MonetoryAmount( new BigDecimal( 178 ), Currency.getInstance( "EUR" ) ) ); + assertEquals( 2, q1.list().size() ); + + final Query q2 = s.createQuery( "from Transaction where value <> :amount and description = :str" ); + q2.setParameter( "amount", new MonetoryAmount( new BigDecimal( 1000000 ), Currency.getInstance( "USD" ) ) ); + q2.setParameter( "str", "bar" ); + assertEquals( 1, q2.list().size() ); + + final Query q3 = s.createQuery( "from Transaction where timestamp <> :timestamp" ); + q3.setParameter( "timestamp", new CompositeDateTime( 2014, 8, 23, 14, 23, 0 ) ); + assertEquals( 2, q3.list().size() ); + + s.delete( t3 ); + s.delete( t2 ); + s.delete( t1 ); + s.getTransaction().commit(); + s.close(); + } + + /** + * Tests the {@code <} operator on composite types. As long as we don't support it, we need to throw an exception + * rather than create a random query. + */ + @Test( expected = QuerySyntaxException.class ) + @TestForIssue( jiraKey = "HHH-5946" ) + public void testLessThanOperator() { + final Session s = openSession(); + s.getTransaction().begin(); + + final Query q = s.createQuery( "from Transaction where value < :amount" ); + q.setParameter( "amount", new MonetoryAmount( BigDecimal.ZERO, Currency.getInstance( "EUR" ) ) ); + q.list(); + + s.getTransaction().commit(); + s.close(); + } + + /** + * Tests the {@code <=} operator on composite types. As long as we don't support it, we need to throw an exception + * rather than create a random query. + */ + @Test( expected = QuerySyntaxException.class ) + @TestForIssue( jiraKey = "HHH-5946" ) + public void testLessOrEqualOperator() { + final Session s = openSession(); + s.getTransaction().begin(); + + final Query q = s.createQuery( "from Transaction where value <= :amount" ); + q.setParameter( "amount", new MonetoryAmount( BigDecimal.ZERO, Currency.getInstance( "USD" ) ) ); + q.list(); + + s.getTransaction().commit(); + s.close(); + } + + /** + * Tests the {@code >} operator on composite types. As long as we don't support it, we need to throw an exception + * rather than create a random query. + */ + @Test( expected = QuerySyntaxException.class ) + @TestForIssue( jiraKey = "HHH-5946" ) + public void testGreaterThanOperator() { + final Session s = openSession(); + s.getTransaction().begin(); + + final Query q = s.createQuery( "from Transaction where value > :amount" ); + q.setParameter( "amount", new MonetoryAmount( BigDecimal.ZERO, Currency.getInstance( "EUR" ) ) ); + q.list(); + + s.getTransaction().commit(); + s.close(); + } + + /** + * Tests the {@code >=} operator on composite types. As long as we don't support it, we need to throw an exception + * rather than create a random query. + */ + @Test( expected = QuerySyntaxException.class ) + @TestForIssue( jiraKey = "HHH-5946" ) + public void testGreaterOrEqualOperator() { + final Session s = openSession(); + s.getTransaction().begin(); + + final Query q = s.createQuery( "from Transaction where value >= :amount" ); + q.setParameter( "amount", new MonetoryAmount( BigDecimal.ZERO, Currency.getInstance( "USD" ) ) ); + q.list(); + + s.getTransaction().commit(); + s.close(); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cut/Transaction.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/cut/Transaction.hbm.xml index 2fb0879ab5..343822b8b0 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/cut/Transaction.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/cut/Transaction.hbm.xml @@ -26,6 +26,14 @@ + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/cut/Transaction.java b/hibernate-core/src/test/java/org/hibernate/test/cut/Transaction.java index 891a854ec8..c9c175d1de 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/cut/Transaction.java +++ b/hibernate-core/src/test/java/org/hibernate/test/cut/Transaction.java @@ -17,7 +17,8 @@ public class Transaction { private Long id; private String description; private MonetoryAmount value; - + private CompositeDateTime timestamp; + public String getDescription() { return description; } @@ -42,4 +43,12 @@ public class Transaction { this.value = value; } + public CompositeDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(CompositeDateTime timestamp) { + this.timestamp = timestamp; + } + } diff --git a/hibernate-core/src/test/java/org/hibernate/test/cut/types.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/cut/types.hbm.xml index 5f5811b5f4..fdbe7ffb7e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/cut/types.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/cut/types.hbm.xml @@ -11,4 +11,5 @@ - \ No newline at end of file + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java index abb35ba1eb..5a09758a66 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java @@ -1441,11 +1441,6 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { s.createQuery( "from Human h where ('John', 'X', 'Doe') = h.name" ).list(); s.createQuery( "from Human h where ('John', 'X', 'Doe') <> h.name" ).list(); - // HANA only allows '=' and '<>'/'!=' - if ( ! ( getDialect() instanceof AbstractHANADialect ) ) { - s.createQuery( "from Human h where ('John', 'X', 'Doe') >= h.name" ).list(); - } - s.createQuery( "from Human h order by h.name" ).list(); s.getTransaction().commit();