Skip to content

Commit da91380

Browse files
committed
HHH-19974 Use columnDefinition for null casting
Prioritize SqlTypedMapping#getColumnDefinition in getSelectClauseNullString to prevent errors with special types (e.g. inet) during set operations in PostgreSQL and Informix. Signed-off-by: namucy <wkdcjfdud13@gmail.com>
1 parent 82d913d commit da91380

File tree

5 files changed

+286
-9
lines changed

5 files changed

+286
-9
lines changed

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.hibernate.exception.LockAcquisitionException;
4141
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
4242
import org.hibernate.mapping.CheckConstraint;
43+
import org.hibernate.metamodel.mapping.SqlTypedMapping;
4344
import org.hibernate.query.sqm.CastType;
4445
import org.hibernate.query.sqm.IntervalType;
4546
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
@@ -1097,6 +1098,18 @@ public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfi
10971098
return "cast(null as " + castType + ")";
10981099
}
10991100

1101+
// Add override for the newer signature to access columnDefinition
1102+
@Override
1103+
public String getSelectClauseNullString(SqlTypedMapping sqlType, TypeConfiguration typeConfiguration) {
1104+
final String castTypeName = sqlType.getColumnDefinition();
1105+
if ( castTypeName != null ) {
1106+
return "cast(null as " + castTypeName + ")";
1107+
}
1108+
1109+
return getSelectClauseNullString( sqlType.getJdbcMapping().getJdbcType().getDdlTypeCode(), typeConfiguration );
1110+
}
1111+
1112+
11001113
private static String castType(DdlType descriptor) {
11011114
final String typeName = descriptor.getTypeName( Size.length( Size.DEFAULT_LENGTH ) );
11021115
//trim off the length/precision/scale

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -962,9 +962,18 @@ public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfi
962962

963963
@Override
964964
public String getSelectClauseNullString(SqlTypedMapping sqlType, TypeConfiguration typeConfiguration) {
965-
final String castTypeName = typeConfiguration.getDdlTypeRegistry()
966-
.getDescriptor( sqlType.getJdbcMapping().getJdbcType().getDdlTypeCode() )
967-
.getCastTypeName( sqlType.toSize(), (SqlExpressible) sqlType.getJdbcMapping(), typeConfiguration.getDdlTypeRegistry() );
965+
String castTypeName = sqlType.getColumnDefinition();
966+
967+
if ( castTypeName == null ) {
968+
castTypeName = typeConfiguration.getDdlTypeRegistry()
969+
.getDescriptor( sqlType.getJdbcMapping().getJdbcType().getDdlTypeCode() )
970+
.getCastTypeName(
971+
sqlType.toSize(),
972+
(SqlExpressible) sqlType.getJdbcMapping(),
973+
typeConfiguration.getDdlTypeRegistry()
974+
);
975+
}
976+
968977
return "cast(null as " + castTypeName + ")";
969978
}
970979

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.community.dialect;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
10+
import org.hibernate.cfg.AvailableSettings;
11+
import org.hibernate.resource.jdbc.spi.StatementInspector;
12+
13+
import org.hibernate.testing.orm.junit.DomainModel;
14+
import org.hibernate.testing.orm.junit.JiraKey;
15+
import org.hibernate.testing.orm.junit.RequiresDialect;
16+
import org.hibernate.testing.orm.junit.ServiceRegistry;
17+
import org.hibernate.testing.orm.junit.SessionFactory;
18+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
19+
import org.hibernate.testing.orm.junit.Setting;
20+
import org.junit.jupiter.api.Assertions;
21+
import org.junit.jupiter.api.BeforeEach;
22+
import org.junit.jupiter.api.Test;
23+
24+
import jakarta.persistence.Column;
25+
import jakarta.persistence.Entity;
26+
import jakarta.persistence.GeneratedValue;
27+
import jakarta.persistence.Id;
28+
import jakarta.persistence.Inheritance;
29+
import jakarta.persistence.InheritanceType;
30+
31+
// Even though we are testing PostgreSQLLegacyDialect, the test environment runs a standard PostgreSQL instance,
32+
// which is detected as PostgreSQLDialect. To prevent the test from being skipped, we require PostgreSQLDialect.
33+
// The actual PostgreSQLLegacyDialect is enforced internally via the @Setting annotation.
34+
@RequiresDialect(org.hibernate.dialect.PostgreSQLDialect.class)
35+
@DomainModel(annotatedClasses = {
36+
PostgreSQLLegacyDialectTest.BaseEntity.class,
37+
PostgreSQLLegacyDialectTest.InetEntity.class,
38+
PostgreSQLLegacyDialectTest.EmptyEntity.class
39+
})
40+
@SessionFactory
41+
@ServiceRegistry(
42+
settings = {
43+
@Setting(
44+
name = AvailableSettings.DIALECT,
45+
value = "org.hibernate.community.dialect.PostgreSQLLegacyDialect"
46+
),
47+
@Setting(
48+
name = AvailableSettings.STATEMENT_INSPECTOR,
49+
value = "org.hibernate.community.dialect.PostgreSQLLegacyDialectTest$SqlSpy"
50+
)
51+
}
52+
53+
)
54+
public class PostgreSQLLegacyDialectTest {
55+
56+
public static final List<String> SQL_LOG = new ArrayList<>();
57+
58+
@BeforeEach
59+
protected void setupTest(SessionFactoryScope scope) {
60+
SQL_LOG.clear();
61+
scope.inTransaction(
62+
(session) -> {
63+
session.createNativeQuery(
64+
"insert " +
65+
"into inet_entity (id, ipAddress) " +
66+
"values (1, '192.168.0.1'::inet)"
67+
)
68+
.executeUpdate();
69+
session.persist( new EmptyEntity() );
70+
}
71+
);
72+
SQL_LOG.clear();
73+
}
74+
75+
@Test
76+
@JiraKey(value = "HHH-19974")
77+
public void testCastNullString(SessionFactoryScope scope) {
78+
scope.inTransaction(
79+
(session) -> {
80+
String entityName = BaseEntity.class.getName();
81+
82+
List<BaseEntity> results = session.createQuery(
83+
"select r from " + entityName + " r", BaseEntity.class
84+
).list();
85+
86+
boolean foundCast = false;
87+
for ( String sql : SQL_LOG ) {
88+
if ( sql.contains( "cast(null as inet)" ) ) {
89+
foundCast = true;
90+
break;
91+
}
92+
}
93+
94+
Assertions.assertTrue( foundCast, "must contains 'cast(null as inet)' clause." );
95+
}
96+
);
97+
}
98+
99+
100+
@Entity(name = "root_entity")
101+
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
102+
public static abstract class BaseEntity {
103+
@Id
104+
@GeneratedValue
105+
private Long id;
106+
}
107+
108+
@Entity(name = "inet_entity")
109+
public static class InetEntity extends BaseEntity {
110+
111+
@Column(columnDefinition = "inet")
112+
private String ipAddress;
113+
114+
public InetEntity() {
115+
}
116+
}
117+
118+
@Entity(name = "empty_entity")
119+
public static class EmptyEntity extends BaseEntity {
120+
}
121+
122+
public static class SqlSpy implements StatementInspector {
123+
@Override
124+
public String inspect(String sql) {
125+
SQL_LOG.add( sql.toLowerCase() );
126+
return sql;
127+
}
128+
}
129+
130+
}

hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -944,12 +944,16 @@ public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfi
944944

945945
@Override
946946
public String getSelectClauseNullString(SqlTypedMapping sqlType, TypeConfiguration typeConfiguration) {
947-
final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry();
948-
final String castTypeName = ddlTypeRegistry
949-
.getDescriptor( sqlType.getJdbcMapping().getJdbcType().getDdlTypeCode() )
950-
.getCastTypeName( sqlType.toSize(), (SqlExpressible) sqlType.getJdbcMapping(), ddlTypeRegistry );
951-
// PostgreSQL assumes a plain null literal in the select statement to be of type text,
952-
// which can lead to issues in e.g. the union subclass strategy, so do a cast
947+
String castTypeName = sqlType.getColumnDefinition();
948+
949+
if ( castTypeName == null ) {
950+
final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry();
951+
castTypeName = ddlTypeRegistry
952+
.getDescriptor( sqlType.getJdbcMapping().getJdbcType().getDdlTypeCode() )
953+
.getCastTypeName( sqlType.toSize(), (SqlExpressible) sqlType.getJdbcMapping(), ddlTypeRegistry );
954+
// PostgreSQL assumes a plain null literal in the select statement to be of type text,
955+
// which can lead to issues in e.g. the union subclass strategy, so do a cast
956+
}
953957
return "cast(null as " + castTypeName + ")";
954958
}
955959

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.dialect;
6+
7+
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
11+
import org.hibernate.cfg.AvailableSettings;
12+
import org.hibernate.resource.jdbc.spi.StatementInspector;
13+
14+
import org.hibernate.testing.orm.junit.DomainModel;
15+
import org.hibernate.testing.orm.junit.JiraKey;
16+
import org.hibernate.testing.orm.junit.RequiresDialect;
17+
import org.hibernate.testing.orm.junit.ServiceRegistry;
18+
import org.hibernate.testing.orm.junit.SessionFactory;
19+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
20+
import org.hibernate.testing.orm.junit.Setting;
21+
import org.junit.jupiter.api.Assertions;
22+
import org.junit.jupiter.api.BeforeEach;
23+
import org.junit.jupiter.api.Test;
24+
25+
import jakarta.persistence.Column;
26+
import jakarta.persistence.Entity;
27+
import jakarta.persistence.GeneratedValue;
28+
import jakarta.persistence.Id;
29+
import jakarta.persistence.Inheritance;
30+
import jakarta.persistence.InheritanceType;
31+
32+
@RequiresDialect(PostgreSQLDialect.class)
33+
@DomainModel(annotatedClasses = {
34+
PostgreSQLDialectTest.BaseEntity.class,
35+
PostgreSQLDialectTest.InetEntity.class,
36+
PostgreSQLDialectTest.EmptyEntity.class
37+
})
38+
@SessionFactory
39+
@ServiceRegistry(
40+
settings = @Setting(
41+
name = AvailableSettings.STATEMENT_INSPECTOR,
42+
value = "org.hibernate.dialect.PostgreSQLDialectTest$SqlSpy"
43+
)
44+
)
45+
public class PostgreSQLDialectTest {
46+
47+
public static final List<String> SQL_LOG = new ArrayList<>();
48+
49+
@BeforeEach
50+
protected void setupTest(SessionFactoryScope scope) {
51+
SQL_LOG.clear();
52+
scope.inTransaction(
53+
(session) -> {
54+
session.createNativeQuery(
55+
"insert " +
56+
"into inet_entity (id, ipAddress) " +
57+
"values (1, '192.168.0.1'::inet)"
58+
)
59+
.executeUpdate();
60+
session.persist( new EmptyEntity() );
61+
}
62+
);
63+
SQL_LOG.clear();
64+
}
65+
66+
@Test
67+
@JiraKey(value = "HHH-19974")
68+
public void testCastNullString(SessionFactoryScope scope) {
69+
scope.inTransaction(
70+
(session) -> {
71+
String entityName = BaseEntity.class.getName();
72+
73+
List<BaseEntity> results = session.createQuery(
74+
"select r from " + entityName + " r", BaseEntity.class
75+
).list();
76+
77+
boolean foundCast = false;
78+
for ( String sql : SQL_LOG ) {
79+
if ( sql.contains( "cast(null as inet)" ) ) {
80+
foundCast = true;
81+
break;
82+
}
83+
}
84+
85+
Assertions.assertTrue( foundCast, "must contains 'cast(null as inet)' clause." );
86+
}
87+
);
88+
}
89+
90+
91+
@Entity(name = "root_entity")
92+
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
93+
public static abstract class BaseEntity {
94+
@Id
95+
@GeneratedValue
96+
private Long id;
97+
}
98+
99+
@Entity(name = "inet_entity")
100+
public static class InetEntity extends BaseEntity {
101+
102+
@Column(columnDefinition = "inet")
103+
private String ipAddress;
104+
105+
public InetEntity() {
106+
}
107+
}
108+
109+
@Entity(name = "empty_entity")
110+
public static class EmptyEntity extends BaseEntity {
111+
}
112+
113+
public static class SqlSpy implements StatementInspector {
114+
@Override
115+
public String inspect(String sql) {
116+
SQL_LOG.add( sql.toLowerCase() );
117+
return sql;
118+
}
119+
}
120+
121+
}

0 commit comments

Comments
 (0)