diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java
index dbeabf4328f3..d7dca4889247 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java
@@ -43,6 +43,15 @@ public interface DialectSpecificSettings {
*/
String ORACLE_APPLICATION_CONTINUITY = "hibernate.dialect.oracle.application_continuity";
+ /**
+ * Specifies whether the dialect should use the binary IEEE Oracle SQL types {@code binary_float}/{@code binary_double}
+ * over {@code float(p)}/{@code real}/{@code double precision} when generating DDL or SQL casts for float types.
+ *
+ * @settingDefault {@code true}
+ * @since 7.0
+ */
+ String ORACLE_USE_BINARY_FLOATS = "hibernate.dialect.oracle.use_binary_floats";
+
/**
* Specifies whether the {@code ansinull} setting is enabled on Sybase.
*
diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java
index 9bd34c047891..f5b027b74c52 100644
--- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java
@@ -4,17 +4,8 @@
*/
package org.hibernate.dialect;
-import java.sql.CallableStatement;
-import java.sql.DatabaseMetaData;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
-import java.time.temporal.ChronoField;
-import java.time.temporal.TemporalAccessor;
-import java.util.TimeZone;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.TemporalType;
import org.hibernate.Length;
import org.hibernate.QueryTimeoutException;
import org.hibernate.boot.model.FunctionContributions;
@@ -34,6 +25,8 @@
import org.hibernate.dialect.temptable.TemporaryTableKind;
import org.hibernate.dialect.unique.CreateTableUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
+import org.hibernate.engine.config.spi.ConfigurationService;
+import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
@@ -47,20 +40,20 @@
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.mapping.AggregateColumn;
+import org.hibernate.mapping.CheckConstraint;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UserDefinedType;
-import org.hibernate.mapping.CheckConstraint;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
import org.hibernate.procedure.internal.OracleCallableStatementSupport;
import org.hibernate.procedure.spi.CallableStatementSupport;
import org.hibernate.query.SemanticException;
+import org.hibernate.query.common.FetchClauseType;
+import org.hibernate.query.common.TemporalUnit;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.sqm.CastType;
-import org.hibernate.query.common.FetchClauseType;
import org.hibernate.query.sqm.IntervalType;
-import org.hibernate.query.common.TemporalUnit;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableInsertStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
@@ -92,19 +85,29 @@
import org.hibernate.type.descriptor.jdbc.SqlTypedJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.ArrayDdlTypeImpl;
+import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
import org.hibernate.type.descriptor.sql.internal.NamedNativeEnumDdlTypeImpl;
import org.hibernate.type.descriptor.sql.internal.NamedNativeOrdinalEnumDdlTypeImpl;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.TemporalType;
+import java.sql.CallableStatement;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAccessor;
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import static org.hibernate.LockOptions.NO_WAIT;
import static org.hibernate.LockOptions.SKIP_LOCKED;
import static org.hibernate.LockOptions.WAIT_FOREVER;
+import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_USE_BINARY_FLOATS;
import static org.hibernate.dialect.OracleJdbcHelper.getArrayJdbcTypeConstructor;
import static org.hibernate.dialect.OracleJdbcHelper.getNestedTableJdbcTypeConstructor;
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
@@ -200,11 +203,9 @@ protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggr
// Is the database accessed using a database service protected by Application Continuity.
protected final boolean applicationContinuity;
-
protected final int driverMajorVersion;
-
protected final int driverMinorVersion;
-
+ private boolean useBinaryFloat;
public OracleDialect() {
this( MINIMUM_VERSION );
@@ -770,11 +771,11 @@ protected String columnType(int sqlTypeCode) {
return "number(19,0)";
case REAL:
// Oracle's 'real' type is actually double precision
- return "float(24)";
+ return useBinaryFloat ? "binary_float" : "float(24)";
case DOUBLE:
// Oracle's 'double precision' means float(126), and
// we never need 126 bits (38 decimal digits)
- return "float(53)";
+ return useBinaryFloat ? "binary_double" : "float(53)";
case NUMERIC:
case DECIMAL:
@@ -959,6 +960,9 @@ public Exporter getUserDefinedTypeExporter() {
@Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
+ final ConfigurationService configurationService = serviceRegistry.requireService( ConfigurationService.class );
+ useBinaryFloat = configurationService.getSetting( ORACLE_USE_BINARY_FLOATS, StandardConverters.BOOLEAN, true );
+
super.contributeTypes( typeContributions, serviceRegistry );
if ( ConfigurationHelper.getPreferredSqlTypeCodeForBoolean( serviceRegistry, this ) == BIT ) {
typeContributions.contributeJdbcType( OracleBooleanJdbcType.INSTANCE );
@@ -972,6 +976,15 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
typeContributions.contributeJdbcType( OracleReflectionStructJdbcType.INSTANCE );
}
+ if ( useBinaryFloat ) {
+ // Override the descriptor for float to produce binary_float or binary_double based on precision
+ typeContributions.getTypeConfiguration().getDdlTypeRegistry().addDescriptor(
+ CapacityDependentDdlType.builder( FLOAT, columnType( DOUBLE ), this )
+ .withTypeCapacity( getFloatPrecision(), columnType( REAL ) )
+ .build()
+ );
+ }
+
if ( getVersion().isSameOrAfter( 21 ) ) {
typeContributions.contributeJdbcType( OracleJsonJdbcType.INSTANCE );
typeContributions.contributeJdbcTypeConstructor( OracleJsonArrayJdbcTypeConstructor.NATIVE_INSTANCE );
diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/ASTParserLoadingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/ASTParserLoadingTest.java
index 60f2de99801d..71d96604798b 100644
--- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/ASTParserLoadingTest.java
+++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/ASTParserLoadingTest.java
@@ -11,8 +11,8 @@
import org.hibernate.ScrollableResults;
import org.hibernate.TypeMismatchException;
import org.hibernate.cfg.Environment;
-import org.hibernate.community.dialect.InformixDialect;
import org.hibernate.community.dialect.DerbyDialect;
+import org.hibernate.community.dialect.InformixDialect;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
@@ -63,6 +63,8 @@
import java.math.BigDecimal;
import java.math.BigInteger;
+import java.math.MathContext;
+import java.math.RoundingMode;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
@@ -80,6 +82,7 @@
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.hibernate.testing.orm.junit.ExtraAssertions.assertClassAssignability;
import static org.hibernate.testing.orm.junit.ExtraAssertions.assertTyping;
+import static org.junit.Assert.assertEquals;
/**
* Tests the integration of the new AST parser into the loading of query results using
@@ -2791,12 +2794,8 @@ public void testStr(SessionFactoryScope scope) {
String str = (String) session.createQuery(
"select str(an.bodyWeight) from Animal an where str(an.bodyWeight) like '%1%'" )
.uniqueResult();
- if ( session.getDialect() instanceof DB2Dialect ) {
- assertThat( str ).startsWith( "1.234" );
- }
- else {
- assertThat( str ).startsWith( "123.4" );
- }
+ BigDecimal value = new BigDecimal( str, new MathContext( 4, RoundingMode.DOWN ) );
+ assertEquals( new BigDecimal( "123.4" ), value );
String dateStr1 = (String) session.createQuery( "select str(current_date) from Animal" )
.uniqueResult();
diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonWithArrayEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonWithArrayEmbeddableTest.java
index 154e21bab536..be84f182a45b 100644
--- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonWithArrayEmbeddableTest.java
+++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonWithArrayEmbeddableTest.java
@@ -55,7 +55,9 @@
integrators = SharedDriverManagerTypeCacheClearingIntegrator.class
)
// Don't reorder columns in the types here to avoid the need to rewrite the test
-@ServiceRegistry(settings = @Setting(name = AvailableSettings.COLUMN_ORDERING_STRATEGY, value = "legacy"))
+@ServiceRegistry(settings = {
+ @Setting(name = AvailableSettings.COLUMN_ORDERING_STRATEGY, value = "legacy")
+})
@DomainModel(annotatedClasses = JsonWithArrayEmbeddableTest.JsonHolder.class)
@SessionFactory
public class JsonWithArrayEmbeddableTest {
diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/formula/FormulaTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/formula/FormulaTests.java
index 4a92a6dda6bd..3429ccb915d5 100644
--- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/formula/FormulaTests.java
+++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/formula/FormulaTests.java
@@ -114,7 +114,7 @@ public static class Account {
@DialectOverride.Formula(dialect = DB2Dialect.class,
override = @Formula("varchar_format(rate * 100) || '%'"))
@DialectOverride.Formula(dialect = OracleDialect.class,
- override = @Formula("to_char(rate * 100) || '%'"))
+ override = @Formula("to_char(cast(rate * 100 as number(10,2))) || '%'"))
@DialectOverride.Formula(dialect = SQLServerDialect.class,
override = @Formula("ltrim(str(rate * 100, 10, 2)) + '%'"))
@DialectOverride.Formula(dialect = SybaseDialect.class,
diff --git a/migration-guide.adoc b/migration-guide.adoc
index a2cb5710167f..821072e6efef 100644
--- a/migration-guide.adoc
+++ b/migration-guide.adoc
@@ -635,6 +635,26 @@ The default precision for SQL Server timestamps was changed to 7, i.e. 100 nanos
Note that these changes only affect DDL generation.
+[[float-mapping-changes-oracle]]
+=== DDL type for Java `float` and `double` changed on Oracle
+
+Previous version of Hibernate ORM mapped Java `float` and `double` to Oracle `float(p)`, `real` or `double precision`
+types, which are all internally implemented as `number`. To avoid potential misbehavior compared to Java execution
+and match the expectations of the IEEE floating point semantics as requested by using Java `float`/`double`,
+the default DDL types were changed to Oracles IEEE floating point types `binary_float` and `binary_double` respectively.
+
+Migration requires multiple steps because Oracle doesn't support online type changes:
+
+```sql
+alter table TBL add (NEW_COLUMN binary_float);
+update TBL set NEW_COLUMN=OLD_COLUMN;
+alter table TBL drop column OLD_COLUMN;
+alter table TBL rename column NEW_COLUMN to OLD_COLUMN;
+```
+
+Note that changing the schema is not required for Hibernate ORM to work correctly.
+The previous behavior may be recovered by setting `hibernate.dialect.oracle.use_binary_floats` to `false`.
+
[[array-mapping-changes-on-db2-sap-hana-sql-server-and-sybase-ase]]
=== Array Mapping Changes