Account for PG < 14 not supporting stored procedure OUT parameters

This commit is contained in:
Christian Beikov 2023-09-06 19:09:54 +02:00
parent 96da7272fb
commit 2176d3aaeb
5 changed files with 63 additions and 30 deletions

View File

@ -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<TheStruct> resultParameter = structFunction.registerParameter(
"structType",
TheStruct.class,
ParameterMode.OUT
parameterMode
);
structFunction.setParameter( resultParameter, null );
TheStruct result = structFunction.getOutputs().getOutputParameterValue( resultParameter );

View File

@ -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<EmbeddableAggregate> resultParameter = structFunction.registerParameter(
"structType",
EmbeddableAggregate.class,
ParameterMode.OUT
parameterMode
);
structFunction.setParameter( resultParameter, null );
EmbeddableAggregate result = structFunction.getOutputs().getOutputParameterValue( resultParameter );

View File

@ -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" );
} );

View File

@ -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<AuditJoinTableInfo> mceRe1AuditJoinTableInfos = getAuditJoinTableRows(
"MCE_RE1_AUD", "MCE_ID",

View File

@ -129,10 +129,12 @@ public final class Helper {
if ( methodSingularAnn != null ) {
collection.add( methodSingularAnn );
}
else {
final S classSingularAnn = testClass.getAnnotation( singularAnnotationClass );
if ( classSingularAnn != null ) {
collection.add( classSingularAnn );
}
}
final P methodPluralAnn = frameworkMethod.getAnnotation( pluralAnnotationClass );
if ( methodPluralAnn != null ) {
try {
@ -142,6 +144,7 @@ public final class Helper {
throw new RuntimeException( e );
}
}
else {
final P classPluralAnn = testClass.getAnnotation( pluralAnnotationClass );
if ( classPluralAnn != null ) {
try {
@ -151,6 +154,7 @@ public final class Helper {
throw new RuntimeException( e );
}
}
}
return collection;
}