From 4efeb846933673b4ad74a84e4cbb15dddbe715c1 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 7 Nov 2025 09:44:29 +0100 Subject: [PATCH 1/2] [#2738] Return the correct column data type for Oracle The Oracle schema extractor is only partially implemented, returning data type 0 for most of the SQL column types. This causes the schema validation to fail even if the columns on the table are valid. --- ...leSqlReactiveInformationExtractorImpl.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java index d85b74d1e..369dbe56a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java @@ -190,12 +190,22 @@ protected String getResultSetIsNullableLabel() { @Override protected int dataTypeCode(String typeName) { - // ORACLE only supports "float" sql type for double precision - // so return code for double for both double and float column types - if ( typeName.equalsIgnoreCase( "float" ) || - typeName.toLowerCase().startsWith( "double" ) ) { - return Types.DOUBLE; - } - return super.dataTypeCode( typeName ); + return switch ( typeName.toLowerCase() ) { + // ORACLE only supports "float" sql type for double precision + // so return code for double for both double and float column types + case "float", "double", "binary_double" -> Types.DOUBLE; + case "timestamp" -> Types.TIMESTAMP; + case "timestamp with time zone", "timestamp with local time zone" -> Types.TIMESTAMP_WITH_TIMEZONE; + case "clob" -> Types.CLOB; + case "blob" -> Types.BLOB; + case "raw" -> Types.VARBINARY; + case "long raw" -> Types.LONGVARBINARY; + case "ref cursor" -> Types.REF_CURSOR; + case "number" -> Types.NUMERIC; + case "date" -> Types.DATE; + case "nvarchar2" -> Types.NVARCHAR; + case "varchar2" -> Types.VARCHAR; + default -> 0; + }; } } From 07dfa1fd29c020f396a6e70c8a4055aa949a97fd Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 7 Nov 2025 11:41:37 +0100 Subject: [PATCH 2/2] [#2738] Test column type validation in Oracle --- .../reactive/schema/SchemaValidationTest.java | 73 ++++++++++++++++++- .../resources/oracle-SchemaValidationTest.sql | 12 +++ 2 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 hibernate-reactive-core/src/test/resources/oracle-SchemaValidationTest.sql diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java index 3132b8498..262325692 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java @@ -6,13 +6,16 @@ package org.hibernate.reactive.schema; +import java.net.URL; import java.util.concurrent.CompletionStage; import java.util.stream.Stream; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.reactive.BaseReactiveTest; import org.hibernate.reactive.annotations.DisabledFor; +import org.hibernate.reactive.annotations.EnabledFor; import org.hibernate.reactive.provider.Settings; import org.hibernate.tool.schema.spi.SchemaManagementException; @@ -23,6 +26,7 @@ import io.vertx.junit5.Timeout; import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; @@ -34,6 +38,7 @@ import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MARIA; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MYSQL; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.ORACLE; import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.GROUPED; import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.INDIVIDUALLY; import static org.junit.jupiter.params.provider.Arguments.arguments; @@ -46,7 +51,7 @@ * - TODO: Test that validation fails when a column is the wrong type */ @DisabledFor(value = DB2, reason = "We don't have an information extractor. See https://github.com/hibernate/hibernate-reactive/issues/911") -@DisabledFor(value = {MARIA, MYSQL}, reason = "HHH-18869: Schema creation creates an invalid schema") +@DisabledFor(value = { MARIA, MYSQL }, reason = "HHH-18869: Schema creation creates an invalid schema") public class SchemaValidationTest extends BaseReactiveTest { static Stream settings() { @@ -76,9 +81,18 @@ public void before(VertxTestContext context) { } public CompletionStage setupFactory(String strategy, String type) { + return setupFactory( strategy, type, null ); + } + + public CompletionStage setupFactory(String strategy, String type, String importFile) { Configuration createConf = constructConfiguration( "create", strategy, type ); createConf.addAnnotatedClass( BasicTypesTestEntity.class ); - + if ( importFile != null ) { + final URL importFileURL = Thread.currentThread() + .getContextClassLoader() + .getResource( importFile ); + createConf.setProperty( AvailableSettings.JAKARTA_HBM2DDL_LOAD_SCRIPT_SOURCE, importFileURL.getFile() ); + } // Make sure that the extra table is not in the db Configuration dropConf = constructConfiguration( "drop", strategy, type ); dropConf.addAnnotatedClass( Extra.class ); @@ -96,6 +110,21 @@ public void after(VertxTestContext context) { closeFactory( context ); } + @ParameterizedTest + @MethodSource("settings") + @EnabledFor( ORACLE ) + public void testOracleColumnTypeValidation(final String strategy, final String type, VertxTestContext context) { + test( + context, setupFactory( strategy, type, "oracle-SchemaValidationTest.sql" ) + .thenCompose( v -> { + Configuration validateConf = constructConfiguration( "validate", strategy, type ); + validateConf.addAnnotatedClass( Fruit.class ); + new StandardServiceRegistryBuilder().applySettings( validateConf.getProperties() ); + return setupSessionFactory( validateConf ); + } ) + ); + } + // When we have created the table, the validation should pass @ParameterizedTest @MethodSource("settings") @@ -112,7 +141,6 @@ context, setupFactory( strategy, type ) ); } - // Validation should fail if a table is missing @ParameterizedTest @MethodSource("settings") @@ -151,4 +179,43 @@ public static class Extra { private String description; } + + @Entity(name = "Fruit") + public static class Fruit { + + @Id + @GeneratedValue + private Integer id; + + @Column(name = "something_name", nullable = false, updatable = false) + private String name; + + public Fruit() { + } + + public Fruit(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "Fruit{" + id + "," + name + '}'; + } + } } diff --git a/hibernate-reactive-core/src/test/resources/oracle-SchemaValidationTest.sql b/hibernate-reactive-core/src/test/resources/oracle-SchemaValidationTest.sql new file mode 100644 index 000000000..69a01b357 --- /dev/null +++ b/hibernate-reactive-core/src/test/resources/oracle-SchemaValidationTest.sql @@ -0,0 +1,12 @@ +-- Import file for testing schema validation in SchemaValidationTest +drop table if exists Fruit cascade constraints +drop sequence if exists Fruit_SEQ + +-- Create the table manually, so that we can check if the validation succeeds +create sequence fruit_seq start with 1 increment by 50; +create table Fruit (id number(10,0) not null, something_name nvarchar2(20) not null, primary key (id)) + +INSERT INTO fruit(id, something_name) VALUES (1, 'Cherry'); +INSERT INTO fruit(id, something_name) VALUES (2, 'Apple'); +INSERT INTO fruit(id, something_name) VALUES (3, 'Banana'); +ALTER SEQUENCE fruit_seq RESTART start with 4; \ No newline at end of file