diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java index de0370224946..f1d85e6c31d2 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java @@ -359,28 +359,11 @@ protected void renderOffsetExpression(Expression offsetExpression) { protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) { // In Sybase ASE, XMLTYPE is not "comparable", so we have to cast the two parts to varchar for this purpose final boolean isLob = isLob( lhs.getExpressionType() ); + final boolean ansiNullOn = ((SybaseASEDialect) getDialect()).isAnsiNullOn(); if ( isLob ) { switch ( operator ) { - case EQUAL: - lhs.accept( this ); - appendSql( " like " ); - rhs.accept( this ); - return; - case NOT_EQUAL: - lhs.accept( this ); - appendSql( " not like " ); - rhs.accept( this ); - return; - default: - // Fall through - break; - } - } - // I think intersect is only supported in 16.0 SP3 - if ( ( (SybaseASEDialect) getDialect() ).isAnsiNullOn() ) { - if ( isLob ) { - switch ( operator ) { - case DISTINCT_FROM: + case DISTINCT_FROM: + if ( ansiNullOn ) { appendSql( "case when " ); lhs.accept( this ); appendSql( " like " ); @@ -390,8 +373,20 @@ protected void renderComparison(Expression lhs, ComparisonOperator operator, Exp appendSql( " is null and " ); rhs.accept( this ); appendSql( " is null then 0 else 1 end=1" ); - return; - case NOT_DISTINCT_FROM: + } + else { + lhs.accept( this ); + appendSql( " not like " ); + rhs.accept( this ); + appendSql( " and (" ); + lhs.accept( this ); + appendSql( " is not null or " ); + rhs.accept( this ); + appendSql( " is not null)" ); + } + return; + case NOT_DISTINCT_FROM: + if ( ansiNullOn ) { appendSql( "case when " ); lhs.accept( this ); appendSql( " like " ); @@ -401,12 +396,42 @@ protected void renderComparison(Expression lhs, ComparisonOperator operator, Exp appendSql( " is null and " ); rhs.accept( this ); appendSql( " is null then 0 else 1 end=0" ); - return; - default: - // Fall through - break; - } + } + else { + lhs.accept( this ); + appendSql( " like " ); + rhs.accept( this ); + appendSql( " or " ); + lhs.accept( this ); + appendSql( " is null and " ); + rhs.accept( this ); + appendSql( " is null" ); + } + return; + case EQUAL: + lhs.accept( this ); + appendSql( " like " ); + rhs.accept( this ); + return; + case NOT_EQUAL: + lhs.accept( this ); + appendSql( " not like " ); + rhs.accept( this ); + if ( !ansiNullOn ) { + appendSql( " and " ); + lhs.accept( this ); + appendSql( " is not null and " ); + rhs.accept( this ); + appendSql( " is not null" ); + } + return; + default: + // Fall through + break; } + } + // I think intersect is only supported in 16.0 SP3 + if ( ansiNullOn ) { if ( supportsDistinctFromPredicate() ) { renderComparisonEmulateIntersect( lhs, operator, rhs ); } @@ -417,50 +442,28 @@ protected void renderComparison(Expression lhs, ComparisonOperator operator, Exp else { // The ansinull setting only matters if using a parameter or literal and the eq operator according to the docs // http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc32300.1570/html/sqlug/sqlug89.htm - boolean rhsNotNullPredicate = - lhs instanceof Literal - || isParameter( lhs ); - boolean lhsNotNullPredicate = - rhs instanceof Literal - || isParameter( rhs ); - if ( rhsNotNullPredicate || lhsNotNullPredicate ) { + boolean lhsAffectedByAnsiNullOff = lhs instanceof Literal || isParameter( lhs ); + boolean rhsAffectedByAnsiNullOff = rhs instanceof Literal || isParameter( rhs ); + if ( lhsAffectedByAnsiNullOff || rhsAffectedByAnsiNullOff ) { lhs.accept( this ); switch ( operator ) { case DISTINCT_FROM: - if ( isLob ) { - appendSql( " not like " ); - } - else { - appendSql( "<>" ); - } + // Since this is the ansinull=off case, this comparison is enough + appendSql( "<>" ); break; case NOT_DISTINCT_FROM: - if ( isLob ) { - appendSql( " like " ); - } - else { - appendSql( '=' ); - } + // Since this is the ansinull=off case, this comparison is enough + appendSql( '=' ); break; - case LESS_THAN: - case GREATER_THAN: - case LESS_THAN_OR_EQUAL: - case GREATER_THAN_OR_EQUAL: - // These operators are not affected by ansinull=off - lhsNotNullPredicate = false; - rhsNotNullPredicate = false; default: appendSql( operator.sqlText() ); break; } rhs.accept( this ); - if ( lhsNotNullPredicate ) { + if ( operator == ComparisonOperator.EQUAL || operator == ComparisonOperator.NOT_EQUAL ) { appendSql( " and " ); lhs.accept( this ); - appendSql( " is not null" ); - } - if ( rhsNotNullPredicate ) { - appendSql( " and " ); + appendSql( " is not null and " ); rhs.accept( this ); appendSql( " is not null" ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/AnsiNullTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/AnsiNullTest.java new file mode 100644 index 000000000000..759e18c48fd6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/AnsiNullTest.java @@ -0,0 +1,256 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.dialect; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.dialect.SybaseASEDialect; +import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl; +import org.hibernate.orm.test.length.WithLongStrings; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +/** + * @author Jan Schatteman + */ +@Jira( value = "https://hibernate.atlassian.net/browse/HHH-16216" ) +public class AnsiNullTest { + + private int lob1; + private int lob2; + + @BeforeEach + void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.persist( new Book( 1L, "LoremIpsum" ) ); + session.persist( new Book( 2L, null ) ); + } + ); + lob1 = scope.fromTransaction( session -> { + WithLongStrings wls = new WithLongStrings(); + wls.longish = "Short String"; + wls.long32 = "Some long String".repeat( 100 ); + session.persist( wls ); + return wls.id; + } + ); + lob2 = scope.fromTransaction( session -> { + WithLongStrings wls = new WithLongStrings(); + wls.longish = "Short String"; + session.persist( wls ); + return wls.id; + } + ); + } + + @AfterEach + void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createMutationQuery( "delete from Book" ).executeUpdate(); + session.createMutationQuery( "delete from WithLongStrings" ).executeUpdate(); + } + ); + } + + @Test + @RequiresDialect(value = SybaseASEDialect.class) + @DomainModel(annotatedClasses = { AnsiNullTest.Book.class, WithLongStrings.class }) + @SessionFactory + @ServiceRegistry( settings = {@Setting(name = DriverManagerConnectionProviderImpl.INIT_SQL, value = "set ansinull on")} ) + public void testWithAnsiNullOn(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Book b = session.createQuery( "SELECT b FROM Book b WHERE b.title IS DISTINCT FROM null", Book.class ).getSingleResult(); + Assertions.assertEquals( 1L, b.id); + + Assertions.assertEquals( 2, session.createQuery( "SELECT b FROM Book b WHERE b.title IS DISTINCT FROM null OR b.id > 1", Book.class ).list().size() ); + Assertions.assertEquals( 2, session.createQuery( "SELECT b FROM Book b WHERE b.title IS DISTINCT FROM null OR b.id >= 1", Book.class ).list().size() ); + + b = session.createQuery( "SELECT b FROM Book b WHERE b.title IS NOT DISTINCT FROM null", Book.class ).getSingleResult(); + Assertions.assertEquals( 2L, b.id); + + Assertions.assertEquals( 2, session.createQuery( "SELECT b FROM Book b WHERE b.title IS NOT DISTINCT FROM null OR b.id < 2", Book.class ).list().size() ); + Assertions.assertEquals( 2, session.createQuery( "SELECT b FROM Book b WHERE b.title IS NOT DISTINCT FROM null OR b.id <= 2", Book.class ).list().size() ); + + Assertions.assertEquals( 0, session.createQuery( "SELECT b FROM Book b WHERE b.title = null", Book.class ).list().size()); + Assertions.assertEquals( 0, session.createQuery( "SELECT b FROM Book b WHERE b.title = null AND b.id <= 2", Book.class ).list().size()); + b = session.createQuery( "SELECT b FROM Book b WHERE b.title = null OR b.id > 1", Book.class ).getSingleResult(); + Assertions.assertEquals( 2L, b.id); + + Assertions.assertEquals( 0, session.createQuery( "SELECT b FROM Book b WHERE b.title != null", Book.class ).list().size()); + Assertions.assertEquals( 0, session.createQuery( "SELECT b FROM Book b WHERE b.title != null AND b.id < 2", Book.class ).list().size()); + b = session.createQuery( "SELECT b FROM Book b WHERE b.title != null OR b.id < 2", Book.class ).getSingleResult(); + Assertions.assertEquals( 1L, b.id); + + b = session.createQuery( "SELECT b FROM Book b WHERE b.title != null or id = 2", Book.class ).getSingleResult(); + Assertions.assertEquals( 2L, b.id); + b = session.createQuery( "SELECT b FROM Book b WHERE id = 2 or b.title != null", Book.class ).getSingleResult(); + Assertions.assertEquals( 2L, b.id); + + Assertions.assertEquals( 0, session.createQuery( "SELECT b FROM Book b WHERE id = 1 and b.title != null", Book.class ).list().size() ); + Assertions.assertEquals( 0, session.createQuery( "SELECT b FROM Book b WHERE b.title != null and id = 1", Book.class ).list().size() ); + + List books = session.createQuery( "SELECT b FROM Book b WHERE 1 = 1", Book.class ).list(); + Assertions.assertEquals( 2, books.size()); + } + ); + } + + @Test + @RequiresDialect(value = SybaseASEDialect.class) + @DomainModel(annotatedClasses = { AnsiNullTest.Book.class, WithLongStrings.class }) + @SessionFactory + @ServiceRegistry( settings = {@Setting(name = DriverManagerConnectionProviderImpl.INIT_SQL, value = "set ansinull on")} ) + public void testLOBWithAnsiNullOn(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + WithLongStrings w = session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 IS DISTINCT FROM null", WithLongStrings.class ).getSingleResult(); + Assertions.assertEquals( lob1, w.id); + + Assertions.assertEquals( 2, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 IS DISTINCT FROM null OR w.id > 1", WithLongStrings.class ).list().size() ); + Assertions.assertEquals( 2, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 IS DISTINCT FROM null OR w.id >= 1", WithLongStrings.class ).list().size() ); + + w = session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 IS NOT DISTINCT FROM null", WithLongStrings.class ).getSingleResult(); + Assertions.assertEquals( lob2, w.id); + + Assertions.assertEquals( 2, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 IS NOT DISTINCT FROM null OR w.id < 2", WithLongStrings.class ).list().size() ); + Assertions.assertEquals( 2, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 IS NOT DISTINCT FROM null OR w.id <= 2", WithLongStrings.class ).list().size() ); + + Assertions.assertEquals( 0, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 = null", WithLongStrings.class ).list().size()); + Assertions.assertEquals( 0, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 = null AND w.id <= " + lob2, WithLongStrings.class ).list().size()); + w = session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 = null OR w.id > 1", WithLongStrings.class ).getSingleResult(); + Assertions.assertEquals( lob2, w.id); + + Assertions.assertEquals( 0, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 != null", WithLongStrings.class ).list().size()); + Assertions.assertEquals( 0, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 != null AND w.id < " + lob2, WithLongStrings.class ).list().size()); + w = session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 != null OR w.id > 1", WithLongStrings.class ).getSingleResult(); + Assertions.assertEquals( lob2, w.id); + + w = session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 != null or id = " + lob2, WithLongStrings.class ).getSingleResult(); + Assertions.assertEquals( 2L, w.id); + w = session.createQuery( "SELECT w FROM WithLongStrings w WHERE id = " + lob2 + " or w.long32 != null", WithLongStrings.class ).getSingleResult(); + Assertions.assertEquals( 2L, w.id); + Assertions.assertEquals( 0, session.createQuery( "SELECT w FROM WithLongStrings w WHERE id = " + lob1 + " and w.long32 != null", WithLongStrings.class ).list().size() ); + Assertions.assertEquals( 0, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 != null and id = " + lob1, WithLongStrings.class ).list().size() ); + + List ws = session.createQuery( "SELECT w FROM WithLongStrings w WHERE 1 = 1", WithLongStrings.class ).list(); + Assertions.assertEquals( 2, ws.size()); + } + ); + } + + @Test + @RequiresDialect(value = SybaseASEDialect.class) + @DomainModel(annotatedClasses = { AnsiNullTest.Book.class, WithLongStrings.class }) + @SessionFactory + @ServiceRegistry( settings = {@Setting(name = DriverManagerConnectionProviderImpl.INIT_SQL, value = "set ansinull off")} ) + public void testWithAnsiNullOff(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Book b = session.createQuery( "SELECT b FROM Book b WHERE b.title IS DISTINCT FROM null", Book.class ).getSingleResult(); + Assertions.assertEquals( 1L, b.id); + + Assertions.assertEquals( 2, session.createQuery( "SELECT b FROM Book b WHERE b.title IS DISTINCT FROM null OR b.id > 1", Book.class ).list().size() ); + Assertions.assertEquals( 2, session.createQuery( "SELECT b FROM Book b WHERE b.title IS DISTINCT FROM null OR b.id >= 1", Book.class ).list().size() ); + + b = session.createQuery( "SELECT b FROM Book b WHERE b.title IS NOT DISTINCT FROM null", Book.class ).getSingleResult(); + Assertions.assertEquals( 2L, b.id); + + Assertions.assertEquals( 2, session.createQuery( "SELECT b FROM Book b WHERE b.title IS NOT DISTINCT FROM null OR b.id < 2", Book.class ).list().size() ); + Assertions.assertEquals( 2, session.createQuery( "SELECT b FROM Book b WHERE b.title IS NOT DISTINCT FROM null OR b.id <= 2", Book.class ).list().size() ); + + Assertions.assertEquals( 0, session.createQuery( "SELECT b FROM Book b WHERE b.title = null", Book.class ).list().size() ); + Assertions.assertEquals( 0, session.createQuery( "SELECT b FROM Book b WHERE b.title = null AND b.id <= 2", Book.class ).list().size()); + b = session.createQuery( "SELECT b FROM Book b WHERE b.title = null OR b.id > 1", Book.class ).getSingleResult(); + Assertions.assertEquals( 2L, b.id); + + Assertions.assertEquals( 0, session.createQuery( "SELECT b FROM Book b WHERE b.title != null", Book.class ).list().size() ); + Assertions.assertEquals( 0, session.createQuery( "SELECT b FROM Book b WHERE b.title != null AND b.id < 2", Book.class ).list().size()); + b = session.createQuery( "SELECT b FROM Book b WHERE b.title != null OR b.id < 2", Book.class ).getSingleResult(); + Assertions.assertEquals( 1L, b.id); + + b = session.createQuery( "SELECT b FROM Book b WHERE b.title != null or id = 2", Book.class ).getSingleResult(); + Assertions.assertEquals( 2L, b.id); + b = session.createQuery( "SELECT b FROM Book b WHERE id = 2 or b.title != null", Book.class ).getSingleResult(); + Assertions.assertEquals( 2L, b.id); + Assertions.assertEquals( 0, session.createQuery( "SELECT b FROM Book b WHERE id = 1 and b.title != null", Book.class ).list().size() ); + Assertions.assertEquals( 0, session.createQuery( "SELECT b FROM Book b WHERE b.title != null and id = 1", Book.class ).list().size() ); + + List books = session.createQuery( "SELECT b FROM Book b WHERE 1 = 1", Book.class ).list(); + Assertions.assertEquals( 2, books.size()); + } + ); + } + + @Test + @RequiresDialect(value = SybaseASEDialect.class) + @DomainModel(annotatedClasses = { AnsiNullTest.Book.class, WithLongStrings.class }) + @SessionFactory + @ServiceRegistry( settings = {@Setting(name = DriverManagerConnectionProviderImpl.INIT_SQL, value = "set ansinull off")} ) + public void testLOBWithAnsiNullOff(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + WithLongStrings w = session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 IS DISTINCT FROM null", WithLongStrings.class ).getSingleResult(); + Assertions.assertEquals( lob1, w.id); + + Assertions.assertEquals( 2, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 IS DISTINCT FROM null OR w.id > 1", WithLongStrings.class ).list().size() ); + Assertions.assertEquals( 2, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 IS DISTINCT FROM null OR w.id >= 1", WithLongStrings.class ).list().size() ); + + w = session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 IS NOT DISTINCT FROM null", WithLongStrings.class ).getSingleResult(); + Assertions.assertEquals( lob2, w.id); + Assertions.assertEquals( 2, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 IS NOT DISTINCT FROM null OR w.id < 2", WithLongStrings.class ).list().size() ); + Assertions.assertEquals( 2, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 IS NOT DISTINCT FROM null OR w.id <= 2", WithLongStrings.class ).list().size() ); + + Assertions.assertEquals( 0, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 = null", WithLongStrings.class ).list().size()); + Assertions.assertEquals( 0, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 = null AND w.id <= " + lob2, WithLongStrings.class ).list().size()); + w = session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 = null OR w.id > 1", WithLongStrings.class ).getSingleResult(); + Assertions.assertEquals( lob2, w.id); + + Assertions.assertEquals( 0, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 != null", WithLongStrings.class ).list().size()); + Assertions.assertEquals( 0, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 != null AND w.id < " + lob2, WithLongStrings.class ).list().size()); + w = session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 != null OR w.id > 1", WithLongStrings.class ).getSingleResult(); + Assertions.assertEquals( lob2, w.id); + + w = session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 != null or id = " + lob2, WithLongStrings.class ).getSingleResult(); + Assertions.assertEquals( 2L, w.id); + w = session.createQuery( "SELECT w FROM WithLongStrings w WHERE id = " + lob2 + " or w.long32 != null", WithLongStrings.class ).getSingleResult(); + Assertions.assertEquals( 2L, w.id); + Assertions.assertEquals( 0, session.createQuery( "SELECT w FROM WithLongStrings w WHERE id = " + lob1 + " and w.long32 != null", WithLongStrings.class ).list().size() ); + Assertions.assertEquals( 0, session.createQuery( "SELECT w FROM WithLongStrings w WHERE w.long32 != null and id = " + lob1, WithLongStrings.class ).list().size() ); + + List ws = session.createQuery( "SELECT w FROM WithLongStrings w WHERE 1 = 1", WithLongStrings.class ).list(); + Assertions.assertEquals( 2, ws.size()); + } + ); + } + + @Entity(name = "Book") + static class Book { + @Id + Long id; + String title; + + public Book() { + } + + public Book(Long id, String title) { + this.id = id; + this.title = title; + } + } + +}