From 939c1f2743de8ba4de927394494dc062699a5633 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Mon, 26 Aug 2013 13:50:22 -0500 Subject: [PATCH] HHH-8445 - Implement REF_CURSOR support for StoredProcedureQuery --- .../AbstractParameterRegistrationImpl.java | 9 +- .../procedure/internal/ProcedureCallImpl.java | 23 ++- .../PostgresRefCursorSupportTest.java | 146 ++++++++++++++++++ 3 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/PostgresRefCursorSupportTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java index 21081e5681..141895717c 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java @@ -25,7 +25,6 @@ package org.hibernate.procedure.internal; import javax.persistence.ParameterMode; import javax.persistence.TemporalType; - import java.sql.CallableStatement; import java.sql.SQLException; import java.util.Calendar; @@ -119,6 +118,10 @@ public abstract class AbstractParameterRegistrationImpl implements ParameterR this.mode = mode; this.type = type; + if ( mode == ParameterMode.REF_CURSOR ) { + return; + } + setHibernateType( hibernateType ); } @@ -286,6 +289,10 @@ public abstract class AbstractParameterRegistrationImpl implements ParameterR } public int[] getSqlTypes() { + if ( mode == ParameterMode.REF_CURSOR ) { + // we could use the Types#REF_CURSOR added in Java 8, but that would require requiring Java 8... + throw new IllegalStateException( "REF_CURSOR parameters do not have a SQL/JDBC type" ); + } return sqlTypes; } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java index 97eeb00914..2770314159 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java @@ -398,10 +398,19 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements .append( "(" ); String sep = ""; for ( ParameterRegistrationImplementor parameter : registeredParameters ) { - for ( int i = 0; i < parameter.getSqlTypes().length; i++ ) { + if ( parameter == null ) { + throw new QueryException( "Registered stored procedure parameters had gaps" ); + } + if ( parameter.getMode() == ParameterMode.REF_CURSOR ) { buffer.append( sep ).append( "?" ); sep = ","; } + else { + for ( int i = 0; i < parameter.getSqlTypes().length; i++ ) { + buffer.append( sep ).append( "?" ); + sep = ","; + } + } } buffer.append( ")}" ); @@ -413,13 +422,15 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements // prepare parameters int i = 1; - for ( ParameterRegistrationImplementor parameter : registeredParameters ) { - if ( parameter == null ) { - throw new QueryException( "Registered stored procedure parameters had gaps" ); - } + for ( ParameterRegistrationImplementor parameter : registeredParameters ) { parameter.prepare( statement, i ); - i += parameter.getSqlTypes().length; + if ( parameter.getMode() == ParameterMode.REF_CURSOR ) { + i++; + } + else { + i += parameter.getSqlTypes().length; + } } return new ProcedureOutputsImpl( this, statement ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/PostgresRefCursorSupportTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/PostgresRefCursorSupportTest.java new file mode 100644 index 0000000000..ab8ce1eb7f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/PostgresRefCursorSupportTest.java @@ -0,0 +1,146 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.sql.storedproc; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ParameterMode; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import java.math.BigDecimal; +import java.util.Date; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.PostgreSQL82Dialect; +import org.hibernate.engine.spi.Mapping; +import org.hibernate.mapping.AuxiliaryDatabaseObject; +import org.hibernate.procedure.ProcedureCall; +import org.hibernate.procedure.ProcedureOutputs; +import org.hibernate.result.ResultSetOutput; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.RequiresDialects; +import org.hibernate.testing.junit4.BaseUnitTestCase; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; + +/** + * @author Steve Ebersole + */ +@RequiresDialect( value = PostgreSQL81Dialect.class, strictMatching = false ) +@FailureExpected( jiraKey = "HHH-8445", message = "Waiting on EG clarification" ) +public class PostgresRefCursorSupportTest extends BaseUnitTestCase { + + public static class ProcedureDefinitions implements AuxiliaryDatabaseObject { + /** + * Singleton access + */ + public static final ProcedureDefinitions INSTANCE = new ProcedureDefinitions(); + + @Override + public void addDialectScope(String dialectName) { + throw new IllegalStateException( "Not expecting addition of dialects to scope" ); + } + + @Override + public boolean appliesToDialect(Dialect dialect) { + return PostgreSQL81Dialect.class.isInstance( dialect ) + || PostgreSQL82Dialect.class.isInstance( dialect ); + } + + @Override + public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) { + return "create function all_items() return refcursor as \n" + + " 'declare someCursor refcursor;\n" + + " begin\n" + + " open someCursor for select * from ITEM;\n" + + " return someCursor;\n" + + " end;' language plpgsql;"; + } + + @Override + public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) { + return "drop function FIND_ITEMS()"; + } + } + + @Entity + @Table( name = "PROC_ITEM" ) + public static class Item { + @Id + private Integer id; + private String stockCode; + private String name; + private BigDecimal unitCost; + @Temporal( TemporalType.TIMESTAMP ) + private Date availabilityStartDate; + @Temporal( TemporalType.TIMESTAMP ) + private Date availabilityEndDate; + } + + private SessionFactory sf; + + @Before + public void beforeTest() { + Configuration cfg = new Configuration() + .addAnnotatedClass( Item.class ) + .setProperty( AvailableSettings.HBM2DDL_AUTO, "create-drop" ); + cfg.addAuxiliaryDatabaseObject( ProcedureDefinitions.INSTANCE ); + + sf = cfg.buildSessionFactory(); + } + + @After + public void afterTest() { + if ( sf != null ) { + sf.close(); + } + } + + @Test + public void testExplicitClassReturn() { + Session session = sf.openSession(); + session.beginTransaction(); + + ProcedureCall call = session.createStoredProcedureCall( "all_items", Item.class ); + call.registerParameter( 1, void.class, ParameterMode.REF_CURSOR ); + ProcedureOutputs outputs = call.getOutputs(); + ResultSetOutput results = assertTyping( ResultSetOutput.class, outputs.getCurrent() ); + + session.getTransaction().commit(); + session.close(); + } +}