Skip to content

Commit 67e9b29

Browse files
committed
Switch to IEEE binary floats by default on Oracle
1 parent d6e85b0 commit 67e9b29

File tree

6 files changed

+54
-10
lines changed

6 files changed

+54
-10
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 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_IEEE_FLOATS = "hibernate.dialect.oracle.use_ieee_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: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
import org.hibernate.type.descriptor.jdbc.SqlTypedJdbcType;
9292
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
9393
import org.hibernate.type.descriptor.sql.internal.ArrayDdlTypeImpl;
94+
import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType;
9495
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
9596
import org.hibernate.type.descriptor.sql.internal.NamedNativeEnumDdlTypeImpl;
9697
import org.hibernate.type.descriptor.sql.internal.NamedNativeOrdinalEnumDdlTypeImpl;
@@ -107,6 +108,7 @@
107108
import static org.hibernate.cfg.AvailableSettings.BATCH_VERSIONED_DATA;
108109
import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_EXTENDED_STRING_SIZE;
109110
import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_AUTONOMOUS_DATABASE;
111+
import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_USE_IEEE_FLOATS;
110112
import static org.hibernate.dialect.OracleJdbcHelper.getArrayJdbcTypeConstructor;
111113
import static org.hibernate.dialect.OracleJdbcHelper.getNestedTableJdbcTypeConstructor;
112114
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
@@ -993,11 +995,21 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
993995
typeContributions.contributeJdbcType( OracleReflectionStructJdbcType.INSTANCE );
994996
}
995997

998+
final ConfigurationService configurationService = serviceRegistry.requireService( ConfigurationService.class );
996999
// account for Oracle's deprecated support for LONGVARBINARY
9971000
// prefer BLOB, unless the user explicitly opts out
998-
final boolean preferLong = serviceRegistry.requireService( ConfigurationService.class )
999-
.getSetting( PREFER_LONG_RAW, StandardConverters.BOOLEAN, false );
1001+
final boolean preferLong =
1002+
configurationService.getSetting( PREFER_LONG_RAW, StandardConverters.BOOLEAN, false );
1003+
final boolean preferIeeeFloats =
1004+
configurationService.getSetting( ORACLE_USE_IEEE_FLOATS, StandardConverters.BOOLEAN, true );
10001005
typeContributions.contributeJdbcType( preferLong ? BlobJdbcType.PRIMITIVE_ARRAY_BINDING : BlobJdbcType.DEFAULT );
1006+
if ( preferIeeeFloats ) {
1007+
typeContributions.getTypeConfiguration().getDdlTypeRegistry().addDescriptor(
1008+
CapacityDependentDdlType.builder( FLOAT, columnType( DOUBLE ), this )
1009+
.withTypeCapacity( getFloatPrecision(), columnType( REAL ) )
1010+
.build()
1011+
);
1012+
}
10011013

10021014
if ( getVersion().isSameOrAfter( 21 ) ) {
10031015
typeContributions.contributeJdbcType( OracleJsonJdbcType.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: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.hibernate.annotations.JdbcTypeCode;
2222
import org.hibernate.cfg.AvailableSettings;
23+
import org.hibernate.cfg.DialectSpecificSettings;
2324
import org.hibernate.dialect.OracleDialect;
2425

2526
import org.hibernate.testing.orm.junit.SkipForDialect;
@@ -55,7 +56,11 @@
5556
integrators = SharedDriverManagerTypeCacheClearingIntegrator.class
5657
)
5758
// 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"))
59+
@ServiceRegistry(settings = {
60+
@Setting(name = AvailableSettings.COLUMN_ORDERING_STRATEGY, value = "legacy"),
61+
// Avoids running into Oracle bugs when converting a JSON float array to varray of binary_double
62+
@Setting(name = DialectSpecificSettings.ORACLE_USE_IEEE_FLOATS, value = "false")
63+
})
5964
@DomainModel(annotatedClasses = JsonWithArrayEmbeddableTest.JsonHolder.class)
6065
@SessionFactory
6166
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
@@ -310,6 +310,26 @@ The migration requires to read data and re-save it.
310310

311311
To retain backwards compatibility, configure the setting `hibernate.type.preferred_array_jdbc_type` to `VARBINARY`.
312312

313+
[[float-mapping-changes-oracle]]
314+
== DDL type for Java `float` and `double` changed on Oracle
315+
316+
Previous version of Hibernate ORM mapped Java `float` and `double` to Oracle `float(p)`, `real` or `double precision`
317+
types, which are all internally implemented as `number`. To avoid potential misbehavior compared to Java execution
318+
and match the expectations of the IEEE floating point semantics as requested by using Java `float`/`double`,
319+
the default DDL types were changed to Oracles IEEE floating point types `binary_float` and `binary_double` respectively.
320+
321+
Migration requires multiple steps because Oracle doesn't support online type changes:
322+
323+
```sql
324+
alter table TBL add (NEW_COLUMN binary_float);
325+
update TBL set NEW_COLUMN=OLD_COLUMN;
326+
alter table TBL drop column OLD_COLUMN;
327+
alter table TBL rename column NEW_COLUMN to OLD_COLUMN;
328+
```
329+
330+
Note that changing the schema is not required for Hibernate ORM to work correctly.
331+
The previous behavior may be recovered by setting `hibernate.dialect.oracle.use_ieee_floats` to `false`.
332+
313333
[[sf-name]]
314334
== SessionFactory Name (and JNDI)
315335

0 commit comments

Comments
 (0)