Skip to content

Commit 8af9424

Browse files
committed
HHH-18780 Use column type information to generate union subclass null casts
1 parent 79aa178 commit 8af9424

File tree

8 files changed

+118
-9
lines changed

8 files changed

+118
-9
lines changed

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

Lines changed: 10 additions & 0 deletions
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;
@@ -949,6 +951,14 @@ public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfi
949951
return "cast(null as " + typeConfiguration.getDdlTypeRegistry().getDescriptor( sqlType ).getRawTypeName() + ")";
950952
}
951953

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

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

Lines changed: 21 additions & 0 deletions
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;
@@ -3075,11 +3076,31 @@ public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
30753076
* @param sqlType The {@link Types} type code.
30763077
* @param typeConfiguration The type configuration
30773078
* @return The appropriate select clause value fragment.
3079+
* @deprecated Use {@link #getSelectClauseNullString(SqlTypedMapping, TypeConfiguration)} instead
30783080
*/
3081+
@Deprecated(forRemoval = true)
30793082
public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfiguration) {
30803083
return "null";
30813084
}
30823085

3086+
/**
3087+
* Given a type mapping, return the expression
3088+
* for a literal null value of that type, to use in a {@code select}
3089+
* clause.
3090+
* <p>
3091+
* The {@code select} query will be an element of a {@code UNION}
3092+
* or {@code UNION ALL}.
3093+
*
3094+
* @implNote Some databases require an explicit type cast.
3095+
*
3096+
* @param sqlTypeMapping The type mapping.
3097+
* @param typeConfiguration The type configuration
3098+
* @return The appropriate select clause value fragment.
3099+
*/
3100+
public String getSelectClauseNullString(SqlTypedMapping sqlTypeMapping, TypeConfiguration typeConfiguration) {
3101+
return getSelectClauseNullString( sqlTypeMapping.getJdbcMapping().getJdbcType().getDdlTypeCode(), typeConfiguration );
3102+
}
3103+
30833104
/**
30843105
* Does this dialect support {@code UNION ALL}?
30853106
*

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.hibernate.mapping.UniqueKey;
5757
import org.hibernate.mapping.UserDefinedType;
5858
import org.hibernate.metamodel.mapping.EntityMappingType;
59+
import org.hibernate.metamodel.mapping.SqlTypedMapping;
5960
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
6061
import org.hibernate.persister.entity.EntityPersister;
6162
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
@@ -722,6 +723,16 @@ public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfi
722723
return wrapped.getSelectClauseNullString( sqlType, typeConfiguration );
723724
}
724725

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

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

Lines changed: 10 additions & 0 deletions
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;
@@ -913,6 +915,14 @@ public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfi
913915
return "cast(null as " + typeConfiguration.getDdlTypeRegistry().getDescriptor( sqlType ).getRawTypeName() + ")";
914916
}
915917

918+
@Override
919+
public String getSelectClauseNullString(SqlTypedMapping sqlType, TypeConfiguration typeConfiguration) {
920+
final String castTypeName = typeConfiguration.getDdlTypeRegistry()
921+
.getDescriptor( sqlType.getJdbcMapping().getJdbcType().getDdlTypeCode() )
922+
.getCastTypeName( sqlType.toSize(), (SqlExpressible) sqlType.getJdbcMapping(), typeConfiguration.getDdlTypeRegistry() );
923+
return "cast(null as " + castTypeName + ")";
924+
}
925+
916926
@Override
917927
public String quoteCollation(String collation) {
918928
return '\"' + collation + '\"';

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

Lines changed: 17 additions & 5 deletions
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

Lines changed: 40 additions & 0 deletions
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

Lines changed: 7 additions & 2 deletions
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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,7 +1077,7 @@ private static SqmFunctionRegistry getSqmFunctionRegistry(Dialect dialect) {
10771077
return sqmFunctionRegistry;
10781078
}
10791079

1080-
private static class FakeTypeContributions implements TypeContributions {
1080+
public static class FakeTypeContributions implements TypeContributions {
10811081
private final TypeConfiguration typeConfiguration;
10821082

10831083
public FakeTypeContributions(TypeConfiguration typeConfiguration) {
@@ -1090,7 +1090,7 @@ public TypeConfiguration getTypeConfiguration() {
10901090
}
10911091
}
10921092

1093-
private static class FakeFunctionContributions implements FunctionContributions {
1093+
public static class FakeFunctionContributions implements FunctionContributions {
10941094
private final Dialect dialect;
10951095
private final TypeConfiguration typeConfiguration;
10961096
private final SqmFunctionRegistry functionRegistry;

0 commit comments

Comments
 (0)