Skip to content

Commit e3f81a1

Browse files
beikovsebersole
authored andcommitted
HHH-18896 Use binary_float/binary_double on Oracle for Java float/double
1 parent 4d9cb1b commit e3f81a1

File tree

6 files changed

+73
-30
lines changed

6 files changed

+73
-30
lines changed

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

+9
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ public interface DialectSpecificSettings {
4343
*/
4444
String ORACLE_APPLICATION_CONTINUITY = "hibernate.dialect.oracle.application_continuity";
4545

46+
/**
47+
* Specifies whether the dialect should use the binary IEEE Oracle SQL types {@code binary_float}/{@code binary_double}
48+
* over {@code float(p)}/{@code real}/{@code double precision} when generating DDL or SQL casts for float types.
49+
*
50+
* @settingDefault {@code true}
51+
* @since 7.0
52+
*/
53+
String ORACLE_USE_BINARY_FLOATS = "hibernate.dialect.oracle.use_binary_floats";
54+
4655
/**
4756
* Specifies whether the {@code ansinull} setting is enabled on Sybase.
4857
* <p>

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

+34-21
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,8 @@
44
*/
55
package org.hibernate.dialect;
66

7-
import java.sql.CallableStatement;
8-
import java.sql.DatabaseMetaData;
9-
import java.sql.ResultSet;
10-
import java.sql.SQLException;
11-
import java.sql.Types;
12-
import java.time.temporal.ChronoField;
13-
import java.time.temporal.TemporalAccessor;
14-
import java.util.TimeZone;
15-
import java.util.regex.Matcher;
16-
import java.util.regex.Pattern;
17-
7+
import jakarta.persistence.GenerationType;
8+
import jakarta.persistence.TemporalType;
189
import org.hibernate.Length;
1910
import org.hibernate.QueryTimeoutException;
2011
import org.hibernate.boot.model.FunctionContributions;
@@ -34,6 +25,8 @@
3425
import org.hibernate.dialect.temptable.TemporaryTableKind;
3526
import org.hibernate.dialect.unique.CreateTableUniqueDelegate;
3627
import org.hibernate.dialect.unique.UniqueDelegate;
28+
import org.hibernate.engine.config.spi.ConfigurationService;
29+
import org.hibernate.engine.config.spi.StandardConverters;
3730
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
3831
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
3932
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
@@ -47,20 +40,20 @@
4740
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
4841
import org.hibernate.internal.util.config.ConfigurationHelper;
4942
import org.hibernate.mapping.AggregateColumn;
43+
import org.hibernate.mapping.CheckConstraint;
5044
import org.hibernate.mapping.Table;
5145
import org.hibernate.mapping.UserDefinedType;
52-
import org.hibernate.mapping.CheckConstraint;
5346
import org.hibernate.metamodel.mapping.EntityMappingType;
5447
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
5548
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
5649
import org.hibernate.procedure.internal.OracleCallableStatementSupport;
5750
import org.hibernate.procedure.spi.CallableStatementSupport;
5851
import org.hibernate.query.SemanticException;
52+
import org.hibernate.query.common.FetchClauseType;
53+
import org.hibernate.query.common.TemporalUnit;
5954
import org.hibernate.query.spi.QueryOptions;
6055
import org.hibernate.query.sqm.CastType;
61-
import org.hibernate.query.common.FetchClauseType;
6256
import org.hibernate.query.sqm.IntervalType;
63-
import org.hibernate.query.common.TemporalUnit;
6457
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableInsertStrategy;
6558
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy;
6659
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
@@ -92,19 +85,29 @@
9285
import org.hibernate.type.descriptor.jdbc.SqlTypedJdbcType;
9386
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
9487
import org.hibernate.type.descriptor.sql.internal.ArrayDdlTypeImpl;
88+
import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType;
9589
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
9690
import org.hibernate.type.descriptor.sql.internal.NamedNativeEnumDdlTypeImpl;
9791
import org.hibernate.type.descriptor.sql.internal.NamedNativeOrdinalEnumDdlTypeImpl;
9892
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
9993
import org.hibernate.type.spi.TypeConfiguration;
10094

101-
import jakarta.persistence.GenerationType;
102-
import jakarta.persistence.TemporalType;
95+
import java.sql.CallableStatement;
96+
import java.sql.DatabaseMetaData;
97+
import java.sql.ResultSet;
98+
import java.sql.SQLException;
99+
import java.sql.Types;
100+
import java.time.temporal.ChronoField;
101+
import java.time.temporal.TemporalAccessor;
102+
import java.util.TimeZone;
103+
import java.util.regex.Matcher;
104+
import java.util.regex.Pattern;
103105

104106
import static java.util.regex.Pattern.CASE_INSENSITIVE;
105107
import static org.hibernate.LockOptions.NO_WAIT;
106108
import static org.hibernate.LockOptions.SKIP_LOCKED;
107109
import static org.hibernate.LockOptions.WAIT_FOREVER;
110+
import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_USE_BINARY_FLOATS;
108111
import static org.hibernate.dialect.OracleJdbcHelper.getArrayJdbcTypeConstructor;
109112
import static org.hibernate.dialect.OracleJdbcHelper.getNestedTableJdbcTypeConstructor;
110113
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
@@ -200,11 +203,9 @@ protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggr
200203

201204
// Is the database accessed using a database service protected by Application Continuity.
202205
protected final boolean applicationContinuity;
203-
204206
protected final int driverMajorVersion;
205-
206207
protected final int driverMinorVersion;
207-
208+
private boolean useBinaryFloat;
208209

209210
public OracleDialect() {
210211
this( MINIMUM_VERSION );
@@ -770,11 +771,11 @@ protected String columnType(int sqlTypeCode) {
770771
return "number(19,0)";
771772
case REAL:
772773
// Oracle's 'real' type is actually double precision
773-
return "float(24)";
774+
return useBinaryFloat ? "binary_float" : "float(24)";
774775
case DOUBLE:
775776
// Oracle's 'double precision' means float(126), and
776777
// we never need 126 bits (38 decimal digits)
777-
return "float(53)";
778+
return useBinaryFloat ? "binary_double" : "float(53)";
778779

779780
case NUMERIC:
780781
case DECIMAL:
@@ -959,6 +960,9 @@ public Exporter<UserDefinedType> getUserDefinedTypeExporter() {
959960

960961
@Override
961962
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
963+
final ConfigurationService configurationService = serviceRegistry.requireService( ConfigurationService.class );
964+
useBinaryFloat = configurationService.getSetting( ORACLE_USE_BINARY_FLOATS, StandardConverters.BOOLEAN, true );
965+
962966
super.contributeTypes( typeContributions, serviceRegistry );
963967
if ( ConfigurationHelper.getPreferredSqlTypeCodeForBoolean( serviceRegistry, this ) == BIT ) {
964968
typeContributions.contributeJdbcType( OracleBooleanJdbcType.INSTANCE );
@@ -972,6 +976,15 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
972976
typeContributions.contributeJdbcType( OracleReflectionStructJdbcType.INSTANCE );
973977
}
974978

979+
if ( useBinaryFloat ) {
980+
// Override the descriptor for float to produce binary_float or binary_double based on precision
981+
typeContributions.getTypeConfiguration().getDdlTypeRegistry().addDescriptor(
982+
CapacityDependentDdlType.builder( FLOAT, columnType( DOUBLE ), this )
983+
.withTypeCapacity( getFloatPrecision(), columnType( REAL ) )
984+
.build()
985+
);
986+
}
987+
975988
if ( getVersion().isSameOrAfter( 21 ) ) {
976989
typeContributions.contributeJdbcType( OracleJsonJdbcType.INSTANCE );
977990
typeContributions.contributeJdbcTypeConstructor( OracleJsonArrayJdbcTypeConstructor.NATIVE_INSTANCE );

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

+6-7
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
import org.hibernate.ScrollableResults;
1212
import org.hibernate.TypeMismatchException;
1313
import org.hibernate.cfg.Environment;
14-
import org.hibernate.community.dialect.InformixDialect;
1514
import org.hibernate.community.dialect.DerbyDialect;
15+
import org.hibernate.community.dialect.InformixDialect;
1616
import org.hibernate.dialect.CockroachDialect;
1717
import org.hibernate.dialect.DB2Dialect;
1818
import org.hibernate.dialect.Dialect;
@@ -63,6 +63,8 @@
6363

6464
import java.math.BigDecimal;
6565
import java.math.BigInteger;
66+
import java.math.MathContext;
67+
import java.math.RoundingMode;
6668
import java.sql.Time;
6769
import java.sql.Timestamp;
6870
import java.util.ArrayList;
@@ -80,6 +82,7 @@
8082
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
8183
import static org.hibernate.testing.orm.junit.ExtraAssertions.assertClassAssignability;
8284
import static org.hibernate.testing.orm.junit.ExtraAssertions.assertTyping;
85+
import static org.junit.Assert.assertEquals;
8386

8487
/**
8588
* Tests the integration of the new AST parser into the loading of query results using
@@ -2791,12 +2794,8 @@ public void testStr(SessionFactoryScope scope) {
27912794
String str = (String) session.createQuery(
27922795
"select str(an.bodyWeight) from Animal an where str(an.bodyWeight) like '%1%'" )
27932796
.uniqueResult();
2794-
if ( session.getDialect() instanceof DB2Dialect ) {
2795-
assertThat( str ).startsWith( "1.234" );
2796-
}
2797-
else {
2798-
assertThat( str ).startsWith( "123.4" );
2799-
}
2797+
BigDecimal value = new BigDecimal( str, new MathContext( 4, RoundingMode.DOWN ) );
2798+
assertEquals( new BigDecimal( "123.4" ), value );
28002799

28012800
String dateStr1 = (String) session.createQuery( "select str(current_date) from Animal" )
28022801
.uniqueResult();

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

+3-1
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

+1-1
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

+20
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,26 @@ The default precision for SQL Server timestamps was changed to 7, i.e. 100 nanos
653653

654654
Note that these changes only affect DDL generation.
655655

656+
[[float-mapping-changes-oracle]]
657+
=== DDL type for Java `float` and `double` changed on Oracle
658+
659+
Previous version of Hibernate ORM mapped Java `float` and `double` to Oracle `float(p)`, `real` or `double precision`
660+
types, which are all internally implemented as `number`. To avoid potential misbehavior compared to Java execution
661+
and match the expectations of the IEEE floating point semantics as requested by using Java `float`/`double`,
662+
the default DDL types were changed to Oracles IEEE floating point types `binary_float` and `binary_double` respectively.
663+
664+
Migration requires multiple steps because Oracle doesn't support online type changes:
665+
666+
```sql
667+
alter table TBL add (NEW_COLUMN binary_float);
668+
update TBL set NEW_COLUMN=OLD_COLUMN;
669+
alter table TBL drop column OLD_COLUMN;
670+
alter table TBL rename column NEW_COLUMN to OLD_COLUMN;
671+
```
672+
673+
Note that changing the schema is not required for Hibernate ORM to work correctly.
674+
The previous behavior may be recovered by setting `hibernate.dialect.oracle.use_binary_floats` to `false`.
675+
656676
[[array-mapping-changes-on-db2-sap-hana-sql-server-and-sybase-ase]]
657677
=== Array Mapping Changes
658678

0 commit comments

Comments
 (0)