HHH-5946 - Wrong SQL generated when composites are compared using not-equal operator

This commit is contained in:
Emanuele Gesuato 2011-02-22 17:24:00 +02:00 committed by Steve Ebersole
parent 4132a4293a
commit cd2b031b6b
8 changed files with 585 additions and 12 deletions

View File

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

View File

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

View File

@ -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<CompositeDateTime> 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 );
}
}

View File

@ -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();
}
}

View File

@ -26,6 +26,14 @@
<column name="amount" not-null="true"/>
<column name="currency" not-null="true"/>
</property>
<property name="timestamp" type="userDateTime">
<column name="year"/>
<column name="month"/>
<column name="day"/>
<column name="hour"/>
<column name="minute"/>
<column name="second"/>
</property>
</class>
<class name="MutualFund" table="MutualFund">

View File

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

View File

@ -11,4 +11,5 @@
<hibernate-mapping package="org.hibernate.test.cut">
<typedef name="money" class="org.hibernate.test.cut.MonetoryAmountUserType"/>
</hibernate-mapping>
<typedef name="userDateTime" class="org.hibernate.test.cut.CompositeDateTimeUserType"/>
</hibernate-mapping>

View File

@ -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();