Skip to content

Commit 296ea2f

Browse files
committed
HHH-18896 Use binary_float/binary_double on Oracle for Java float/double
1 parent d433133 commit 296ea2f

File tree

6 files changed

+56
-15
lines changed

6 files changed

+56
-15
lines changed

hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ public interface DialectSpecificSettings {
3636
*/
3737
String ORACLE_APPLICATION_CONTINUITY = "hibernate.dialect.oracle.application_continuity";
3838

39+
/**
40+
* Specifies whether the dialect should use the binary IEEE Oracle SQL types {@code binary_float}/{@code binary_double}
41+
* over {@code float(p)}/{@code real}/{@code double precision} when generating DDL or SQL casts for float types.
42+
*
43+
* @settingDefault {@code true}
44+
* @since 7.0
45+
*/
46+
String ORACLE_USE_BINARY_FLOATS = "hibernate.dialect.oracle.use_binary_floats";
47+
3948
/**
4049
* Specifies whether this database's {@code ansinull} setting is enabled.
4150
*

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
import org.hibernate.type.descriptor.jdbc.SqlTypedJdbcType;
9595
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
9696
import org.hibernate.type.descriptor.sql.internal.ArrayDdlTypeImpl;
97+
import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType;
9798
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
9899
import org.hibernate.type.descriptor.sql.internal.NamedNativeEnumDdlTypeImpl;
99100
import org.hibernate.type.descriptor.sql.internal.NamedNativeOrdinalEnumDdlTypeImpl;
@@ -110,6 +111,7 @@
110111
import static org.hibernate.cfg.AvailableSettings.BATCH_VERSIONED_DATA;
111112
import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_EXTENDED_STRING_SIZE;
112113
import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_AUTONOMOUS_DATABASE;
114+
import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_USE_BINARY_FLOATS;
113115
import static org.hibernate.dialect.OracleJdbcHelper.getArrayJdbcTypeConstructor;
114116
import static org.hibernate.dialect.OracleJdbcHelper.getNestedTableJdbcTypeConstructor;
115117
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
@@ -204,11 +206,9 @@ protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggr
204206

205207
// Is the database accessed using a database service protected by Application Continuity.
206208
protected final boolean applicationContinuity;
207-
208209
protected final int driverMajorVersion;
209-
210210
protected final int driverMinorVersion;
211-
211+
private boolean useBinaryFloat;
212212

213213
public OracleDialect() {
214214
this( MINIMUM_VERSION );
@@ -804,11 +804,11 @@ protected String columnType(int sqlTypeCode) {
804804
return "number(19,0)";
805805
case REAL:
806806
// Oracle's 'real' type is actually double precision
807-
return "float(24)";
807+
return useBinaryFloat ? "binary_float" : "float(24)";
808808
case DOUBLE:
809809
// Oracle's 'double precision' means float(126), and
810810
// we never need 126 bits (38 decimal digits)
811-
return "float(53)";
811+
return useBinaryFloat ? "binary_double" : "float(53)";
812812

813813
case NUMERIC:
814814
case DECIMAL:
@@ -999,6 +999,9 @@ public Exporter<UserDefinedType> getUserDefinedTypeExporter() {
999999

10001000
@Override
10011001
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
1002+
final ConfigurationService configurationService = serviceRegistry.requireService( ConfigurationService.class );
1003+
useBinaryFloat = configurationService.getSetting( ORACLE_USE_BINARY_FLOATS, StandardConverters.BOOLEAN, true );
1004+
10021005
super.contributeTypes( typeContributions, serviceRegistry );
10031006
if ( ConfigurationHelper.getPreferredSqlTypeCodeForBoolean( serviceRegistry, this ) == BIT ) {
10041007
typeContributions.contributeJdbcType( OracleBooleanJdbcType.INSTANCE );
@@ -1014,10 +1017,19 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
10141017

10151018
// account for Oracle's deprecated support for LONGVARBINARY
10161019
// prefer BLOB, unless the user explicitly opts out
1017-
final boolean preferLong = serviceRegistry.requireService( ConfigurationService.class )
1018-
.getSetting( PREFER_LONG_RAW, StandardConverters.BOOLEAN, false );
1020+
final boolean preferLong =
1021+
configurationService.getSetting( PREFER_LONG_RAW, StandardConverters.BOOLEAN, false );
10191022
typeContributions.contributeJdbcType( preferLong ? BlobJdbcType.PRIMITIVE_ARRAY_BINDING : BlobJdbcType.DEFAULT );
10201023

1024+
if ( useBinaryFloat ) {
1025+
// Override the descriptor for float to produce binary_float or binary_double based on precision
1026+
typeContributions.getTypeConfiguration().getDdlTypeRegistry().addDescriptor(
1027+
CapacityDependentDdlType.builder( FLOAT, columnType( DOUBLE ), this )
1028+
.withTypeCapacity( getFloatPrecision(), columnType( REAL ) )
1029+
.build()
1030+
);
1031+
}
1032+
10211033
if ( getVersion().isSameOrAfter( 21 ) ) {
10221034
typeContributions.contributeJdbcType( OracleJsonJdbcType.INSTANCE );
10231035
typeContributions.contributeJdbcTypeConstructor( OracleJsonArrayJdbcTypeConstructor.NATIVE_INSTANCE );

hibernate-core/src/test/java/org/hibernate/orm/test/hql/ASTParserLoadingTest.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
import java.math.BigDecimal;
88
import java.math.BigInteger;
9+
import java.math.MathContext;
10+
import java.math.RoundingMode;
911
import java.sql.Time;
1012
import java.sql.Timestamp;
1113
import java.util.ArrayList;
@@ -2825,12 +2827,8 @@ public void testStr() {
28252827
an.setBodyWeight(123.45f);
28262828
session.persist( an );
28272829
String str = (String) session.createQuery("select str(an.bodyWeight) from Animal an where str(an.bodyWeight) like '%1%'").uniqueResult();
2828-
if ( getDialect() instanceof DB2Dialect ) {
2829-
assertTrue( str.startsWith( "1.234" ) );
2830-
}
2831-
else {
2832-
assertTrue( str.startsWith("123.4") );
2833-
}
2830+
BigDecimal value = new BigDecimal( str, new MathContext( 4, RoundingMode.DOWN ) );
2831+
assertEquals( new BigDecimal( "123.4" ), value );
28342832

28352833
String dateStr1 = (String) session.createQuery("select str(current_date) from Animal").uniqueResult();
28362834
String dateStr2 = (String) session.createQuery("select str(year(current_date))||'-'||str(month(current_date))||'-'||str(day(current_date)) from Animal").uniqueResult();

hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonWithArrayEmbeddableTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@
5555
integrators = SharedDriverManagerTypeCacheClearingIntegrator.class
5656
)
5757
// Don't reorder columns in the types here to avoid the need to rewrite the test
58-
@ServiceRegistry(settings = @Setting(name = AvailableSettings.COLUMN_ORDERING_STRATEGY, value = "legacy"))
58+
@ServiceRegistry(settings = {
59+
@Setting(name = AvailableSettings.COLUMN_ORDERING_STRATEGY, value = "legacy")
60+
})
5961
@DomainModel(annotatedClasses = JsonWithArrayEmbeddableTest.JsonHolder.class)
6062
@SessionFactory
6163
public class JsonWithArrayEmbeddableTest {

hibernate-core/src/test/java/org/hibernate/orm/test/mapping/formula/FormulaTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public static class Account {
114114
@DialectOverride.Formula(dialect = DB2Dialect.class,
115115
override = @Formula("varchar_format(rate * 100) || '%'"))
116116
@DialectOverride.Formula(dialect = OracleDialect.class,
117-
override = @Formula("to_char(rate * 100) || '%'"))
117+
override = @Formula("to_char(cast(rate * 100 as number(10,2))) || '%'"))
118118
@DialectOverride.Formula(dialect = SQLServerDialect.class,
119119
override = @Formula("ltrim(str(rate * 100, 10, 2)) + '%'"))
120120
@DialectOverride.Formula(dialect = SybaseDialect.class,

migration-guide.adoc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,26 @@ The migration requires to read data and re-save it.
351351

352352
To retain backwards compatibility, configure the setting `hibernate.type.xml_format_mapper.legacy_format` to `true`.
353353

354+
[[float-mapping-changes-oracle]]
355+
== DDL type for Java `float` and `double` changed on Oracle
356+
357+
Previous version of Hibernate ORM mapped Java `float` and `double` to Oracle `float(p)`, `real` or `double precision`
358+
types, which are all internally implemented as `number`. To avoid potential misbehavior compared to Java execution
359+
and match the expectations of the IEEE floating point semantics as requested by using Java `float`/`double`,
360+
the default DDL types were changed to Oracles IEEE floating point types `binary_float` and `binary_double` respectively.
361+
362+
Migration requires multiple steps because Oracle doesn't support online type changes:
363+
364+
```sql
365+
alter table TBL add (NEW_COLUMN binary_float);
366+
update TBL set NEW_COLUMN=OLD_COLUMN;
367+
alter table TBL drop column OLD_COLUMN;
368+
alter table TBL rename column NEW_COLUMN to OLD_COLUMN;
369+
```
370+
371+
Note that changing the schema is not required for Hibernate ORM to work correctly.
372+
The previous behavior may be recovered by setting `hibernate.dialect.oracle.use_binary_floats` to `false`.
373+
354374
[[sf-name]]
355375
== SessionFactory Name (and JNDI)
356376

0 commit comments

Comments
 (0)