Skip to content

Commit ae5e5b2

Browse files
committed
HHH-18780 Use column type information to generate union subclass null casts
1 parent cab048b commit ae5e5b2

File tree

8 files changed

+121
-9
lines changed

8 files changed

+121
-9
lines changed

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

+10
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
import org.hibernate.mapping.AggregateColumn;
5757
import org.hibernate.mapping.Table;
5858
import org.hibernate.metamodel.mapping.EntityMappingType;
59+
import org.hibernate.metamodel.mapping.SqlExpressible;
60+
import org.hibernate.metamodel.mapping.SqlTypedMapping;
5961
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
6062
import org.hibernate.procedure.internal.PostgreSQLCallableStatementSupport;
6163
import org.hibernate.procedure.spi.CallableStatementSupport;
@@ -950,6 +952,14 @@ public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfi
950952
return "cast(null as " + typeConfiguration.getDdlTypeRegistry().getDescriptor( sqlType ).getRawTypeName() + ")";
951953
}
952954

955+
@Override
956+
public String getSelectClauseNullString(SqlTypedMapping sqlType, TypeConfiguration typeConfiguration) {
957+
final String castTypeName = typeConfiguration.getDdlTypeRegistry()
958+
.getDescriptor( sqlType.getJdbcMapping().getJdbcType().getDdlTypeCode() )
959+
.getCastTypeName( sqlType.toSize(), (SqlExpressible) sqlType.getJdbcMapping(), typeConfiguration.getDdlTypeRegistry() );
960+
return "cast(null as " + castTypeName + ")";
961+
}
962+
953963
@Override
954964
public boolean supportsCommentOn() {
955965
return true;

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

+21
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
import org.hibernate.mapping.UniqueKey;
8383
import org.hibernate.mapping.UserDefinedType;
8484
import org.hibernate.metamodel.mapping.EntityMappingType;
85+
import org.hibernate.metamodel.mapping.SqlTypedMapping;
8586
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
8687
import org.hibernate.persister.entity.EntityPersister;
8788
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
@@ -3086,11 +3087,31 @@ public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
30863087
* @param sqlType The {@link Types} type code.
30873088
* @param typeConfiguration The type configuration
30883089
* @return The appropriate select clause value fragment.
3090+
* @deprecated Use {@link #getSelectClauseNullString(SqlTypedMapping, TypeConfiguration)} instead
30893091
*/
3092+
@Deprecated(forRemoval = true)
30903093
public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfiguration) {
30913094
return "null";
30923095
}
30933096

3097+
/**
3098+
* Given a type mapping, return the expression
3099+
* for a literal null value of that type, to use in a {@code select}
3100+
* clause.
3101+
* <p>
3102+
* The {@code select} query will be an element of a {@code UNION}
3103+
* or {@code UNION ALL}.
3104+
*
3105+
* @implNote Some databases require an explicit type cast.
3106+
*
3107+
* @param sqlTypeMapping The type mapping.
3108+
* @param typeConfiguration The type configuration
3109+
* @return The appropriate select clause value fragment.
3110+
*/
3111+
public String getSelectClauseNullString(SqlTypedMapping sqlTypeMapping, TypeConfiguration typeConfiguration) {
3112+
return getSelectClauseNullString( sqlTypeMapping.getJdbcMapping().getJdbcType().getDdlTypeCode(), typeConfiguration );
3113+
}
3114+
30943115
/**
30953116
* Does this dialect support {@code UNION ALL}?
30963117
*

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

+11
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.hibernate.mapping.UniqueKey;
5858
import org.hibernate.mapping.UserDefinedType;
5959
import org.hibernate.metamodel.mapping.EntityMappingType;
60+
import org.hibernate.metamodel.mapping.SqlTypedMapping;
6061
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
6162
import org.hibernate.persister.entity.EntityPersister;
6263
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
@@ -723,6 +724,16 @@ public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfi
723724
return wrapped.getSelectClauseNullString( sqlType, typeConfiguration );
724725
}
725726

727+
@Override
728+
public String getSelectClauseNullString(SqlTypedMapping sqlTypeMapping, TypeConfiguration typeConfiguration) {
729+
return wrapped.getSelectClauseNullString( sqlTypeMapping, typeConfiguration );
730+
}
731+
732+
@Override
733+
public boolean stripsTrailingSpacesFromChar() {
734+
return wrapped.stripsTrailingSpacesFromChar();
735+
}
736+
726737
@Override
727738
public boolean supportsUnionAll() {
728739
return wrapped.supportsUnionAll();

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

+13
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
import org.hibernate.mapping.AggregateColumn;
5454
import org.hibernate.mapping.Table;
5555
import org.hibernate.metamodel.mapping.EntityMappingType;
56+
import org.hibernate.metamodel.mapping.SqlExpressible;
57+
import org.hibernate.metamodel.mapping.SqlTypedMapping;
5658
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
5759
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
5860
import org.hibernate.procedure.internal.PostgreSQLCallableStatementSupport;
@@ -920,6 +922,17 @@ public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfi
920922
return "cast(null as " + typeConfiguration.getDdlTypeRegistry().getDescriptor( sqlType ).getRawTypeName() + ")";
921923
}
922924

925+
@Override
926+
public String getSelectClauseNullString(SqlTypedMapping sqlType, TypeConfiguration typeConfiguration) {
927+
final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry();
928+
final String castTypeName = ddlTypeRegistry
929+
.getDescriptor( sqlType.getJdbcMapping().getJdbcType().getDdlTypeCode() )
930+
.getCastTypeName( sqlType.toSize(), (SqlExpressible) sqlType.getJdbcMapping(), ddlTypeRegistry );
931+
// PostgreSQL assumes a plain null literal in the select statement to be of type text,
932+
// which can lead to issues in e.g. the union subclass strategy, so do a cast
933+
return "cast(null as " + castTypeName + ")";
934+
}
935+
923936
@Override
924937
public String quoteCollation(String collation) {
925938
return '\"' + collation + '\"';

hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java

+17-5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.hibernate.metamodel.mapping.SelectableConsumer;
4444
import org.hibernate.metamodel.mapping.SelectableMapping;
4545
import org.hibernate.metamodel.mapping.TableDetails;
46+
import org.hibernate.metamodel.mapping.internal.SqlTypedMappingImpl;
4647
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
4748
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
4849
import org.hibernate.spi.NavigablePath;
@@ -451,8 +452,7 @@ protected String generateSubquery(PersistentClass model, Metadata mapping) {
451452
subquery.append( "select " );
452453
for ( Column col : columns ) {
453454
if ( !table.containsColumn( col ) ) {
454-
int sqlType = col.getSqlTypeCode( mapping );
455-
subquery.append( dialect.getSelectClauseNullString( sqlType, getFactory().getTypeConfiguration() ) )
455+
subquery.append( getSelectClauseNullString( col, dialect ) )
456456
.append(" as ");
457457
}
458458
subquery.append( col.getQuotedName( dialect ) )
@@ -467,6 +467,20 @@ protected String generateSubquery(PersistentClass model, Metadata mapping) {
467467
return subquery.append( ")" ).toString();
468468
}
469469

470+
private String getSelectClauseNullString(Column col, Dialect dialect) {
471+
return dialect.getSelectClauseNullString(
472+
new SqlTypedMappingImpl(
473+
col.getTypeName(),
474+
col.getLength(),
475+
col.getPrecision(),
476+
col.getScale(),
477+
col.getTemporalPrecision(),
478+
col.getType()
479+
),
480+
getFactory().getTypeConfiguration()
481+
);
482+
}
483+
470484
protected String generateSubquery(Map<String, EntityNameUse> entityNameUses) {
471485
if ( !hasSubclasses() ) {
472486
return getTableName();
@@ -533,9 +547,7 @@ protected String generateSubquery(Map<String, EntityNameUse> entityNameUses) {
533547
if ( selectableMapping == null ) {
534548
// If there is no selectable mapping for a table name, we render a null expression
535549
selectableMapping = selectableMappings.values().iterator().next();
536-
final int sqlType = selectableMapping.getJdbcMapping().getJdbcType()
537-
.getDdlTypeCode();
538-
buf.append( dialect.getSelectClauseNullString( sqlType, getFactory().getTypeConfiguration() ) )
550+
buf.append( dialect.getSelectClauseNullString( selectableMapping, getFactory().getTypeConfiguration() ) )
539551
.append( " as " );
540552
}
541553
if ( selectableMapping.isFormula() ) {

hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQLDialectTestCase.java

+40
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.sql.SQLException;
1010

1111
import org.hibernate.JDBCException;
12+
import org.hibernate.Length;
1213
import org.hibernate.LockMode;
1314
import org.hibernate.LockOptions;
1415
import org.hibernate.PessimisticLockException;
@@ -26,8 +27,12 @@
2627
import org.hibernate.mapping.Table;
2728
import org.hibernate.mapping.UniqueKey;
2829

30+
import org.hibernate.metamodel.mapping.internal.SqlTypedMappingImpl;
31+
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
32+
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
2933
import org.hibernate.testing.orm.junit.JiraKey;
3034
import org.hibernate.testing.junit4.BaseUnitTestCase;
35+
import org.hibernate.type.spi.TypeConfiguration;
3136
import org.junit.Test;
3237

3338
import org.mockito.Mockito;
@@ -150,6 +155,41 @@ public void testAlterTableDropConstraintString() {
150155
assertEquals("alter table if exists table_name drop constraint if exists unique_something", sql );
151156
}
152157

158+
@Test
159+
@JiraKey( value = "HHH-18780" )
160+
public void testTextVsVarchar() {
161+
PostgreSQLDialect dialect = new PostgreSQLDialect();
162+
163+
final TypeConfiguration typeConfiguration = new TypeConfiguration();
164+
final SqmFunctionRegistry functionRegistry = new SqmFunctionRegistry();
165+
typeConfiguration.scope( new DialectFeatureChecks.FakeMetadataBuildingContext( typeConfiguration, functionRegistry ) );
166+
final DialectFeatureChecks.FakeTypeContributions typeContributions = new DialectFeatureChecks.FakeTypeContributions( typeConfiguration );
167+
final DialectFeatureChecks.FakeFunctionContributions functionContributions = new DialectFeatureChecks.FakeFunctionContributions(
168+
dialect,
169+
typeConfiguration,
170+
functionRegistry
171+
);
172+
dialect.contribute( typeContributions, typeConfiguration.getServiceRegistry() );
173+
dialect.initializeFunctionRegistry( functionContributions );
174+
final String varcharNullString = dialect.getSelectClauseNullString(
175+
new SqlTypedMappingImpl( typeConfiguration.getBasicTypeForJavaType( String.class ) ),
176+
typeConfiguration
177+
);
178+
final String textNullString = dialect.getSelectClauseNullString(
179+
new SqlTypedMappingImpl(
180+
null,
181+
(long) Length.LONG32,
182+
null,
183+
null,
184+
null,
185+
typeConfiguration.getBasicTypeForJavaType( String.class )
186+
),
187+
typeConfiguration
188+
);
189+
assertEquals("cast(null as varchar)", varcharNullString);
190+
assertEquals("cast(null as text)", textNullString);
191+
}
192+
153193
private static class MockSqlStringGenerationContext implements SqlStringGenerationContext {
154194

155195
@Override

hibernate-core/src/test/java/org/hibernate/orm/test/hql/joinedSubclass/JoinedSubclassNativeQueryTest.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66

77
import org.hibernate.cfg.AvailableSettings;
88
import org.hibernate.engine.spi.SessionFactoryImplementor;
9-
import org.hibernate.type.SqlTypes;
9+
import org.hibernate.metamodel.mapping.internal.SqlTypedMappingImpl;
1010

1111
import org.hibernate.testing.orm.junit.JiraKey;
1212
import org.hibernate.testing.orm.junit.DomainModel;
1313
import org.hibernate.testing.orm.junit.SessionFactory;
1414
import org.hibernate.testing.orm.junit.SessionFactoryScope;
15+
import org.hibernate.type.spi.TypeConfiguration;
1516
import org.junit.jupiter.api.AfterAll;
1617
import org.junit.jupiter.api.Assertions;
1718
import org.junit.jupiter.api.BeforeAll;
@@ -61,10 +62,14 @@ public void testJoinedInheritanceNativeQuery(SessionFactoryScope scope) {
6162
scope.inTransaction(
6263
session -> {
6364
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
65+
final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration();
6466
final String nullColumnString = sessionFactory
6567
.getJdbcServices()
6668
.getDialect()
67-
.getSelectClauseNullString( SqlTypes.VARCHAR, sessionFactory.getTypeConfiguration() );
69+
.getSelectClauseNullString(
70+
new SqlTypedMappingImpl( typeConfiguration.getBasicTypeForJavaType( String.class ) ),
71+
typeConfiguration
72+
);
6873
// PostgreSQLDialect#getSelectClauseNullString produces e.g. `null::text` which we interpret as parameter,
6974
// so workaround this problem by configuring to ignore JDBC parameters
7075
session.setProperty( AvailableSettings.NATIVE_IGNORE_JDBC_PARAMETERS, true );

hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1090,7 +1090,7 @@ private static SqmFunctionRegistry getSqmFunctionRegistry(Dialect dialect) {
10901090
return sqmFunctionRegistry;
10911091
}
10921092

1093-
private static class FakeTypeContributions implements TypeContributions {
1093+
public static class FakeTypeContributions implements TypeContributions {
10941094
private final TypeConfiguration typeConfiguration;
10951095

10961096
public FakeTypeContributions(TypeConfiguration typeConfiguration) {
@@ -1103,7 +1103,7 @@ public TypeConfiguration getTypeConfiguration() {
11031103
}
11041104
}
11051105

1106-
private static class FakeFunctionContributions implements FunctionContributions {
1106+
public static class FakeFunctionContributions implements FunctionContributions {
11071107
private final Dialect dialect;
11081108
private final TypeConfiguration typeConfiguration;
11091109
private final SqmFunctionRegistry functionRegistry;

0 commit comments

Comments
 (0)