From 2176d3aaeb9148a63500128afdb72b28a8288aca Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 6 Sep 2023 19:09:54 +0200 Subject: [PATCH] Account for PG < 14 not supporting stored procedure OUT parameters --- .../NestedStructEmbeddableTest.java | 15 ++++++-- .../embeddable/StructEmbeddableTest.java | 15 ++++++-- .../PostgreSQLStoredProcedureTest.java | 37 ++++++++++++------- .../DetachedMultipleCollectionChangeTest.java | 2 + .../org/hibernate/testing/junit4/Helper.java | 24 +++++++----- 5 files changed, 63 insertions(+), 30 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructEmbeddableTest.java index dadade780b..17eb5dff4b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructEmbeddableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructEmbeddableTest.java @@ -31,6 +31,7 @@ import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.Dialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.PostgresPlusDialect; @@ -106,7 +107,7 @@ public class NestedStructEmbeddableTest extends BaseSessionFactoryFunctionalTest new NamedAuxiliaryDatabaseObject( "PostgreSQL structProcedure", namespace, - "create procedure structProcedure(OUT result theStruct) AS $$ begin result.nested.theBinary = bytea '\\x01'; result.nested.theString = 'ABC'; result.nested.theDouble = 0; result.nested.theInt = 0; result.nested.theLocalDateTime = timestamp '2022-12-01 01:00:00'; result.nested.theUuid = '53886a8a-7082-4879-b430-25cb94415be8'::uuid; end $$ language plpgsql", + "create procedure structProcedure(INOUT result theStruct) AS $$ declare nested structType; begin nested.theBinary = bytea '\\x01'; nested.theString = 'ABC'; nested.theDouble = 0; nested.theInt = 0; nested.theLocalDateTime = timestamp '2022-12-01 01:00:00'; nested.theUuid = '53886a8a-7082-4879-b430-25cb94415be8'::uuid; result.nested = nested; end $$ language plpgsql", "drop procedure structProcedure", Set.of( PostgreSQLDialect.class.getName() ) ) @@ -129,7 +130,7 @@ public class NestedStructEmbeddableTest extends BaseSessionFactoryFunctionalTest new NamedAuxiliaryDatabaseObject( "PostgrePlus structProcedure", namespace, - "create procedure structProcedure(result OUT theStruct) AS $$ begin result.nested.theBinary = bytea '\\x01'; result.nested.theString = 'ABC'; result.nested.theDouble = 0; result.nested.theInt = 0; result.nested.theLocalDateTime = timestamp '2022-12-01 01:00:00'; result.nested.theUuid = '53886a8a-7082-4879-b430-25cb94415be8'::uuid; end $$ language plpgsql", + "create procedure structProcedure(result INOUT theStruct) AS $$ declare nested structType; begin nested.theBinary = bytea '\\x01'; nested.theString = 'ABC'; nested.theDouble = 0; nested.theInt = 0; nested.theLocalDateTime = timestamp '2022-12-01 01:00:00'; nested.theUuid = '53886a8a-7082-4879-b430-25cb94415be8'::uuid; result.nested = nested; end $$ language plpgsql", "drop procedure structProcedure", Set.of( PostgresPlusDialect.class.getName() ) ) @@ -568,11 +569,19 @@ public class NestedStructEmbeddableTest extends BaseSessionFactoryFunctionalTest public void testProcedure() { sessionFactoryScope().inTransaction( entityManager -> { + final Dialect dialect = entityManager.getJdbcServices().getDialect(); + final ParameterMode parameterMode; + if ( dialect instanceof PostgreSQLDialect ) { + parameterMode = ParameterMode.INOUT; + } + else { + parameterMode = ParameterMode.OUT; + } ProcedureCall structFunction = entityManager.createStoredProcedureCall( "structProcedure" ); ProcedureParameter resultParameter = structFunction.registerParameter( "structType", TheStruct.class, - ParameterMode.OUT + parameterMode ); structFunction.setParameter( resultParameter, null ); TheStruct result = structFunction.getOutputs().getOutputParameterValue( resultParameter ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableTest.java index 4ba96ed585..6db61b3ac5 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableTest.java @@ -30,6 +30,7 @@ import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.Dialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.PostgresPlusDialect; @@ -105,7 +106,7 @@ public class StructEmbeddableTest extends BaseSessionFactoryFunctionalTest { new NamedAuxiliaryDatabaseObject( "PostgreSQL structProcedure", namespace, - "create procedure structProcedure(OUT result structType) AS $$ begin result.theBinary = bytea '\\x01'; result.theString = 'ABC'; result.theDouble = 0; result.theInt = 0; result.theLocalDateTime = timestamp '2022-12-01 01:00:00'; result.theUuid = '53886a8a-7082-4879-b430-25cb94415be8'::uuid; end $$ language plpgsql", + "create procedure structProcedure(INOUT result structType) AS $$ declare res structType; begin res.theBinary = bytea '\\x01'; res.theString = 'ABC'; res.theDouble = 0; res.theInt = 0; res.theLocalDateTime = timestamp '2022-12-01 01:00:00'; res.theUuid = '53886a8a-7082-4879-b430-25cb94415be8'::uuid; result = res; end $$ language plpgsql", "drop procedure structProcedure", Set.of( PostgreSQLDialect.class.getName() ) ) @@ -128,7 +129,7 @@ public class StructEmbeddableTest extends BaseSessionFactoryFunctionalTest { new NamedAuxiliaryDatabaseObject( "PostgrePlus structProcedure", namespace, - "create procedure structProcedure(result OUT structType) AS $$ begin result.theBinary = bytea '\\x01'; result.theString = 'ABC'; result.theDouble = 0; result.theInt = 0; result.theLocalDateTime = timestamp '2022-12-01 01:00:00'; result.theUuid = '53886a8a-7082-4879-b430-25cb94415be8'::uuid; end $$ language plpgsql", + "create procedure structProcedure(result INOUT structType) AS $$ declare res structType; begin res.theBinary = bytea '\\x01'; res.theString = 'ABC'; res.theDouble = 0; res.theInt = 0; res.theLocalDateTime = timestamp '2022-12-01 01:00:00'; res.theUuid = '53886a8a-7082-4879-b430-25cb94415be8'::uuid; result = res; end $$ language plpgsql", "drop procedure structProcedure", Set.of( PostgresPlusDialect.class.getName() ) ) @@ -527,11 +528,19 @@ public class StructEmbeddableTest extends BaseSessionFactoryFunctionalTest { public void testProcedure() { sessionFactoryScope().inTransaction( entityManager -> { + final Dialect dialect = entityManager.getJdbcServices().getDialect(); + final ParameterMode parameterMode; + if ( dialect instanceof PostgreSQLDialect ) { + parameterMode = ParameterMode.INOUT; + } + else { + parameterMode = ParameterMode.OUT; + } ProcedureCall structFunction = entityManager.createStoredProcedureCall( "structProcedure" ); ProcedureParameter resultParameter = structFunction.registerParameter( "structType", EmbeddableAggregate.class, - ParameterMode.OUT + parameterMode ); structFunction.setParameter( resultParameter, null ); EmbeddableAggregate result = structFunction.getOutputs().getOutputParameterValue( resultParameter ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/PostgreSQLStoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/PostgreSQLStoredProcedureTest.java index a651b8a525..9174b0fc5b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/PostgreSQLStoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/PostgreSQLStoredProcedureTest.java @@ -24,14 +24,16 @@ import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.NamedAuxiliaryDatabaseObject; import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.dialect.Dialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.PostgresPlusDialect; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; import org.hibernate.procedure.ProcedureCall; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.RequiresDialect; -import org.hibernate.testing.TestForIssue; import org.junit.Before; import org.junit.Test; @@ -44,7 +46,7 @@ import static org.junit.Assert.fail; /** * @author Vlad Mihalcea */ -@RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 11, comment = "Stored procedures are only supported since version 11") +@RequiresDialect(value = PostgreSQLDialect.class) public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTestCase { @Override @@ -65,7 +67,7 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe namespace, "CREATE OR REPLACE PROCEDURE sp_count_phones( " + " IN personId bigint, " + - " OUT phoneCount bigint) " + + " INOUT phoneCount bigint) " + " AS " + "$BODY$ " + " BEGIN " + @@ -83,7 +85,7 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe new NamedAuxiliaryDatabaseObject( "sp_phones", namespace, - "CREATE OR REPLACE PROCEDURE sp_phones(IN personId BIGINT, OUT phones REFCURSOR) " + + "CREATE OR REPLACE PROCEDURE sp_phones(IN personId BIGINT, INOUT phones REFCURSOR) " + " AS " + "$BODY$ " + " BEGIN " + @@ -102,7 +104,7 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe new NamedAuxiliaryDatabaseObject( "singleRefCursor", namespace, - "CREATE OR REPLACE PROCEDURE singleRefCursor(OUT p_recordset REFCURSOR) " + + "CREATE OR REPLACE PROCEDURE singleRefCursor(INOUT p_recordset REFCURSOR) " + " AS " + "$BODY$ " + " BEGIN " + @@ -120,7 +122,7 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe namespace, "CREATE OR REPLACE PROCEDURE sp_is_null( " + " IN param varchar(255), " + - " OUT result boolean) " + + " INOUT result boolean) " + " AS " + "$BODY$ " + " BEGIN " + @@ -162,9 +164,10 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe doInJPA( this::entityManagerFactory, entityManager -> { StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_count_phones" ); query.registerStoredProcedureParameter( "personId", Long.class, ParameterMode.IN ); - query.registerStoredProcedureParameter( "phoneCount", Long.class, ParameterMode.OUT ); + query.registerStoredProcedureParameter( "phoneCount", Long.class, ParameterMode.INOUT ); query.setParameter( "personId", 1L ); + query.setParameter( "phoneCount", null ); query.execute(); Long phoneCount = (Long) query.getOutputParameterValue( "phoneCount" ); @@ -173,6 +176,7 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe } @Test + @RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 14, comment = "Stored procedure OUT parameters are only supported since version 14") public void testStoredProcedureRefCursor() { doInJPA( this::entityManagerFactory, entityManager -> { StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_phones" ); @@ -196,6 +200,7 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe procedure = connection.prepareCall( "{ call sp_count_phones(?,?) }" ); procedure.registerOutParameter( 2, Types.BIGINT ); procedure.setLong( 1, 1L ); + procedure.setNull( 2, Types.BIGINT ); procedure.execute(); return procedure.getLong( 2 ); } @@ -238,7 +243,8 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe } @Test - @TestForIssue(jiraKey = "HHH-11863") + @JiraKey("HHH-11863") + @RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 14, comment = "Stored procedure OUT parameters are only supported since version 14") public void testSysRefCursorAsOutParameter() { doInJPA( this::entityManagerFactory, entityManager -> { @@ -249,7 +255,7 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe try (ResultSet resultSet = session.doReturningWork( connection -> { CallableStatement procedure = null; try { - procedure = connection.prepareCall( "{ call singleRefCursor(?) }" ); + procedure = connection.prepareCall( "call singleRefCursor(?)" ); procedure.registerOutParameter( 1, Types.REF_CURSOR ); procedure.execute(); return (ResultSet) procedure.getObject( 1 ); @@ -292,15 +298,16 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe } @Test - @TestForIssue(jiraKey = "HHH-12905") + @JiraKey("HHH-12905") public void testStoredProcedureNullParameterHibernate() { doInJPA( this::entityManagerFactory, entityManager -> { ProcedureCall procedureCall = entityManager.unwrap( Session.class ) .createStoredProcedureCall( "sp_is_null" ); procedureCall.registerParameter( 1, StandardBasicTypes.STRING, ParameterMode.IN ); - procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT ); + procedureCall.registerParameter( 2, Boolean.class, ParameterMode.INOUT ); procedureCall.setParameter( 1, null ); + procedureCall.setParameter( 2, null ); Boolean result = (Boolean) procedureCall.getOutputParameterValue( 2 ); @@ -311,8 +318,9 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe ProcedureCall procedureCall = entityManager.unwrap( Session.class ) .createStoredProcedureCall( "sp_is_null" ); procedureCall.registerParameter( 1, StandardBasicTypes.STRING, ParameterMode.IN ); - procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT ); + procedureCall.registerParameter( 2, Boolean.class, ParameterMode.INOUT ); procedureCall.setParameter( 1, "test" ); + procedureCall.setParameter( 2, null ); Boolean result = (Boolean) procedureCall.getOutputParameterValue( 2 ); @@ -321,15 +329,16 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe } @Test - @TestForIssue(jiraKey = "HHH-12905") + @JiraKey("HHH-12905") public void testStoredProcedureNullParameterHibernateWithoutEnablePassingNulls() { doInJPA( this::entityManagerFactory, entityManager -> { ProcedureCall procedureCall = entityManager.unwrap( Session.class ) .createStoredProcedureCall( "sp_is_null" ); procedureCall.registerParameter( "param", StandardBasicTypes.STRING, ParameterMode.IN ); - procedureCall.registerParameter( "result", Boolean.class, ParameterMode.OUT ); + procedureCall.registerParameter( "result", Boolean.class, ParameterMode.INOUT ); procedureCall.setParameter( "param", null ); + procedureCall.setParameter( "result", null ); procedureCall.getOutputParameterValue( "result" ); } ); diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/collection/DetachedMultipleCollectionChangeTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/collection/DetachedMultipleCollectionChangeTest.java index 48e83002df..f64aca66a5 100644 --- a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/collection/DetachedMultipleCollectionChangeTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/collection/DetachedMultipleCollectionChangeTest.java @@ -228,6 +228,8 @@ public class DetachedMultipleCollectionChangeTest extends BaseEnversJPAFunctiona @Test @SkipForDialect(value = CockroachDialect.class, comment = "requires serial_normalization=sql_sequence setting") + @SkipForDialect(value = OracleDialect.class, + comment = "Oracle does not support identity key generation") public void testAuditJoinTable() throws Exception { List mceRe1AuditJoinTableInfos = getAuditJoinTableRows( "MCE_RE1_AUD", "MCE_ID", diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/Helper.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/Helper.java index 652ce1d557..c6dc1d11d4 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/Helper.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/Helper.java @@ -129,9 +129,11 @@ public final class Helper { if ( methodSingularAnn != null ) { collection.add( methodSingularAnn ); } - final S classSingularAnn = testClass.getAnnotation( singularAnnotationClass ); - if ( classSingularAnn != null ) { - collection.add( classSingularAnn ); + else { + final S classSingularAnn = testClass.getAnnotation( singularAnnotationClass ); + if ( classSingularAnn != null ) { + collection.add( classSingularAnn ); + } } final P methodPluralAnn = frameworkMethod.getAnnotation( pluralAnnotationClass ); if ( methodPluralAnn != null ) { @@ -142,13 +144,15 @@ public final class Helper { throw new RuntimeException( e ); } } - final P classPluralAnn = testClass.getAnnotation( pluralAnnotationClass ); - if ( classPluralAnn != null ) { - try { - collection.addAll( Arrays.asList( (S[]) pluralAnnotationClass.getDeclaredMethods()[0].invoke( classPluralAnn ) ) ); - } - catch (Exception e) { - throw new RuntimeException( e ); + else { + final P classPluralAnn = testClass.getAnnotation( pluralAnnotationClass ); + if ( classPluralAnn != null ) { + try { + collection.addAll( Arrays.asList( (S[]) pluralAnnotationClass.getDeclaredMethods()[0].invoke( classPluralAnn ) ) ); + } + catch (Exception e) { + throw new RuntimeException( e ); + } } } return collection;