-
- Notifications
You must be signed in to change notification settings - Fork 3.7k
HHH-19974 Use columnDefinition for null casting #11460
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| /* | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| * Copyright Red Hat Inc. and Hibernate Authors | ||
| */ | ||
| package org.hibernate.community.dialect; | ||
| | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| | ||
| import org.hibernate.cfg.AvailableSettings; | ||
| import org.hibernate.resource.jdbc.spi.StatementInspector; | ||
| | ||
| import org.hibernate.testing.orm.junit.DomainModel; | ||
| import org.hibernate.testing.orm.junit.JiraKey; | ||
| 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.Assertions; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
| | ||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Inheritance; | ||
| import jakarta.persistence.InheritanceType; | ||
| | ||
| // Even though we are testing PostgreSQLLegacyDialect, the test environment runs a standard PostgreSQL instance, | ||
| // which is detected as PostgreSQLDialect. To prevent the test from being skipped, we require PostgreSQLDialect. | ||
| // The actual PostgreSQLLegacyDialect is enforced internally via the @Setting annotation. | ||
| @RequiresDialect(org.hibernate.dialect.PostgreSQLDialect.class) | ||
| @DomainModel(annotatedClasses = { | ||
| PostgreSQLLegacyDialectTest.BaseEntity.class, | ||
| PostgreSQLLegacyDialectTest.InetEntity.class, | ||
| PostgreSQLLegacyDialectTest.EmptyEntity.class | ||
| }) | ||
| @SessionFactory | ||
| @ServiceRegistry( | ||
| settings = { | ||
| @Setting( | ||
| name = AvailableSettings.DIALECT, | ||
| value = "org.hibernate.community.dialect.PostgreSQLLegacyDialect" | ||
| ), | ||
| @Setting( | ||
| name = AvailableSettings.STATEMENT_INSPECTOR, | ||
| value = "org.hibernate.community.dialect.PostgreSQLLegacyDialectTest$SqlSpy" | ||
| ) | ||
| } | ||
| | ||
| ) | ||
| public class PostgreSQLLegacyDialectTest { | ||
| | ||
| public static final List<String> SQL_LOG = new ArrayList<>(); | ||
| | ||
| @BeforeEach | ||
| protected void setupTest(SessionFactoryScope scope) { | ||
| SQL_LOG.clear(); | ||
| scope.inTransaction( | ||
| (session) -> { | ||
| session.createNativeQuery( | ||
| "insert " + | ||
| "into inet_entity (id, ipAddress) " + | ||
| "values (1, '192.168.0.1'::inet)" | ||
| ) | ||
| .executeUpdate(); | ||
| session.persist( new EmptyEntity() ); | ||
| } | ||
| ); | ||
| SQL_LOG.clear(); | ||
| } | ||
| | ||
| @Test | ||
| @JiraKey(value = "HHH-19974") | ||
| public void testCastNullString(SessionFactoryScope scope) { | ||
| scope.inTransaction( | ||
| (session) -> { | ||
| String entityName = BaseEntity.class.getName(); | ||
| | ||
| List<BaseEntity> results = session.createQuery( | ||
| "select r from " + entityName + " r", BaseEntity.class | ||
| ).list(); | ||
| | ||
| boolean foundCast = false; | ||
| for ( String sql : SQL_LOG ) { | ||
| if ( sql.contains( "cast(null as inet)" ) ) { | ||
| foundCast = true; | ||
| break; | ||
| } | ||
| } | ||
| | ||
| Assertions.assertTrue( foundCast, "must contains 'cast(null as inet)' clause." ); | ||
| } | ||
| ); | ||
| } | ||
| | ||
| | ||
| @Entity(name = "root_entity") | ||
| @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) | ||
| public static abstract class BaseEntity { | ||
| @Id | ||
| @GeneratedValue | ||
| private Long id; | ||
| } | ||
| | ||
| @Entity(name = "inet_entity") | ||
| public static class InetEntity extends BaseEntity { | ||
| | ||
| @Column(columnDefinition = "inet") | ||
| private String ipAddress; | ||
| | ||
| public InetEntity() { | ||
| } | ||
| } | ||
| | ||
| @Entity(name = "empty_entity") | ||
| public static class EmptyEntity extends BaseEntity { | ||
| } | ||
| | ||
| public static class SqlSpy implements StatementInspector { | ||
| @Override | ||
| public String inspect(String sql) { | ||
| SQL_LOG.add( sql.toLowerCase() ); | ||
| return sql; | ||
| } | ||
| } | ||
| | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -944,12 +944,16 @@ public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfi | |
| | ||
| @Override | ||
| public String getSelectClauseNullString(SqlTypedMapping sqlType, TypeConfiguration typeConfiguration) { | ||
| final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); | ||
| final String castTypeName = ddlTypeRegistry | ||
| .getDescriptor( sqlType.getJdbcMapping().getJdbcType().getDdlTypeCode() ) | ||
| .getCastTypeName( sqlType.toSize(), (SqlExpressible) sqlType.getJdbcMapping(), ddlTypeRegistry ); | ||
| // PostgreSQL assumes a plain null literal in the select statement to be of type text, | ||
| // which can lead to issues in e.g. the union subclass strategy, so do a cast | ||
| String castTypeName = sqlType.getColumnDefinition(); | ||
| Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess that this is the substance of the proposed change? But this doesn't look right to me. If this Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are absolutely right. If The underlying issue is: The field is mapped as Since we can't safely use Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can probably do it by registering a Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean it looks like we already have this stuff built in: You just write: public InetAddress addr;Or if you want to use a string: @JdbcTypeCode(SqlTypes.INET) public String addrStr; Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have verified your suggestions. You are absolutely right regarding the
In both cases, the test passes and Hibernate correctly generates Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, regarding I noticed that Even if we drop the @Override public String getSelectClauseNullString(SqlTypedMapping sqlType, TypeConfiguration typeConfiguration) { final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); final String castTypeName = ddlTypeRegistry .getDescriptor( sqlType.getJdbcMapping().getJdbcType().getDdlTypeCode() ) .getCastTypeName( sqlType.toSize(), (SqlExpressible) sqlType.getJdbcMapping(), ddlTypeRegistry ); return "cast(null as " + castTypeName + ")"; }This would align the dialect with the modern API and make future type handling improvements easier. | ||
| | ||
| if ( castTypeName == null ) { | ||
| final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); | ||
| castTypeName = ddlTypeRegistry | ||
| .getDescriptor( sqlType.getJdbcMapping().getJdbcType().getDdlTypeCode() ) | ||
| .getCastTypeName( sqlType.toSize(), (SqlExpressible) sqlType.getJdbcMapping(), ddlTypeRegistry ); | ||
| // PostgreSQL assumes a plain null literal in the select statement to be of type text, | ||
| // which can lead to issues in e.g. the union subclass strategy, so do a cast | ||
| } | ||
| return "cast(null as " + castTypeName + ")"; | ||
| } | ||
| | ||
| | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| /* | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| * Copyright Red Hat Inc. and Hibernate Authors | ||
| */ | ||
| package org.hibernate.dialect; | ||
| | ||
| | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| | ||
| import org.hibernate.cfg.AvailableSettings; | ||
| import org.hibernate.resource.jdbc.spi.StatementInspector; | ||
| | ||
| import org.hibernate.testing.orm.junit.DomainModel; | ||
| import org.hibernate.testing.orm.junit.JiraKey; | ||
| 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.Assertions; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
| | ||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Inheritance; | ||
| import jakarta.persistence.InheritanceType; | ||
| | ||
| @RequiresDialect(PostgreSQLDialect.class) | ||
| @DomainModel(annotatedClasses = { | ||
| PostgreSQLDialectTest.BaseEntity.class, | ||
| PostgreSQLDialectTest.InetEntity.class, | ||
| PostgreSQLDialectTest.EmptyEntity.class | ||
| }) | ||
| @SessionFactory | ||
| @ServiceRegistry( | ||
| settings = @Setting( | ||
| name = AvailableSettings.STATEMENT_INSPECTOR, | ||
| value = "org.hibernate.dialect.PostgreSQLDialectTest$SqlSpy" | ||
| ) | ||
| ) | ||
| public class PostgreSQLDialectTest { | ||
| | ||
| public static final List<String> SQL_LOG = new ArrayList<>(); | ||
| | ||
| @BeforeEach | ||
| protected void setupTest(SessionFactoryScope scope) { | ||
| SQL_LOG.clear(); | ||
| scope.inTransaction( | ||
| (session) -> { | ||
| session.createNativeQuery( | ||
| "insert " + | ||
| "into inet_entity (id, ipAddress) " + | ||
| "values (1, '192.168.0.1'::inet)" | ||
| ) | ||
| .executeUpdate(); | ||
| session.persist( new EmptyEntity() ); | ||
| } | ||
| ); | ||
| SQL_LOG.clear(); | ||
| } | ||
| | ||
| @Test | ||
| @JiraKey(value = "HHH-19974") | ||
| public void testCastNullString(SessionFactoryScope scope) { | ||
| scope.inTransaction( | ||
| (session) -> { | ||
| String entityName = BaseEntity.class.getName(); | ||
| | ||
| List<BaseEntity> results = session.createQuery( | ||
| "select r from " + entityName + " r", BaseEntity.class | ||
| ).list(); | ||
| | ||
| boolean foundCast = false; | ||
| for ( String sql : SQL_LOG ) { | ||
| if ( sql.contains( "cast(null as inet)" ) ) { | ||
| foundCast = true; | ||
| break; | ||
| } | ||
| } | ||
| | ||
| Assertions.assertTrue( foundCast, "must contains 'cast(null as inet)' clause." ); | ||
| } | ||
| ); | ||
| } | ||
| | ||
| | ||
| @Entity(name = "root_entity") | ||
| @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) | ||
| public static abstract class BaseEntity { | ||
| @Id | ||
| @GeneratedValue | ||
| private Long id; | ||
| } | ||
| | ||
| @Entity(name = "inet_entity") | ||
| public static class InetEntity extends BaseEntity { | ||
| | ||
| @Column(columnDefinition = "inet") | ||
| private String ipAddress; | ||
| | ||
| public InetEntity() { | ||
| } | ||
| } | ||
| | ||
| @Entity(name = "empty_entity") | ||
| public static class EmptyEntity extends BaseEntity { | ||
| } | ||
| | ||
| public static class SqlSpy implements StatementInspector { | ||
| @Override | ||
| public String inspect(String sql) { | ||
| SQL_LOG.add( sql.toLowerCase() ); | ||
| return sql; | ||
| } | ||
| } | ||
| | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use the standard statement collector instead. Same for the other PostgreSQL test