HHH-8395 - JPA StoredProcedureQuery#getUpdateCount should prefer return -1 rather than throw exceptions

This commit is contained in:
Steve Ebersole 2013-07-31 08:34:52 -05:00
parent 33b7e9f441
commit cd1c80b82d
11 changed files with 298 additions and 189 deletions

View File

@ -613,23 +613,7 @@ public class CustomLoader extends Loader {
rowProcessor.columnProcessors[i].performDiscovery( metadata, types, aliases );
}
// lets make sure we did not end up with duplicate aliases. this can occur when the user supplied query
// did not rename same-named columns. e.g.:
// select u.username, u2.username from t_user u, t_user u2 ...
//
// the above will lead to an unworkable situation in most cases (the difference is how the driver/db
// handle this situation. But if the 'aliases' variable contains duplicate names, then we have that
// troublesome condition, so lets throw an error. See HHH-5992
final HashSet<String> aliasesSet = new HashSet<String>();
for ( String alias : aliases ) {
boolean alreadyExisted = !aliasesSet.add( alias );
if ( alreadyExisted ) {
throw new NonUniqueDiscoveredSqlAliasException(
"Encountered a duplicated sql alias [" + alias +
"] during auto-discovery of a native-sql query"
);
}
}
validateAliases( aliases );
resultTypes = ArrayHelper.toTypeArray( types );
transformerAliases = ArrayHelper.toStringArray( aliases );
@ -639,6 +623,30 @@ public class CustomLoader extends Loader {
}
}
private void validateAliases(List<String> aliases) {
// lets make sure we did not end up with duplicate aliases. this can occur when the user supplied query
// did not rename same-named columns. e.g.:
// select u.username, u2.username from t_user u, t_user u2 ...
//
// the above will lead to an unworkable situation in most cases (the difference is how the driver/db
// handle this situation. But if the 'aliases' variable contains duplicate names, then we have that
// troublesome condition, so lets throw an error. See HHH-5992
final HashSet<String> aliasesSet = new HashSet<String>();
for ( String alias : aliases ) {
validateAlias( alias );
boolean alreadyExisted = !aliasesSet.add( alias );
if ( alreadyExisted ) {
throw new NonUniqueDiscoveredSqlAliasException(
"Encountered a duplicated sql alias [" + alias +
"] during auto-discovery of a native-sql query"
);
}
}
}
protected void validateAlias(String alias) {
}
private static class Metadata {
private final SessionFactoryImplementor factory;
private final ResultSet resultSet;

View File

@ -134,11 +134,12 @@ public interface ProcedureCall extends BasicQueryContract, SynchronizeableQuery
/**
* Retrieves access to outputs of this procedure call. Can be called multiple times, returning the same
* Output instance each time.
* ProcedureResult instance each time.
* <p/>
* Note that the procedure will not actually be executed until the outputs are actually accessed.
* If the procedure call has not actually be executed yet, it will be executed and then the ProcedureResult
* will be returned.
*
* @return The outputs representation
* @return The ProcedureResult representation
*/
public ProcedureResult getResult();

View File

@ -29,7 +29,7 @@ import org.hibernate.Session;
import org.hibernate.engine.spi.SessionImplementor;
/**
* Represents a "memento" of a ProcedureCall
* Represents a "memento" (disconnected, externalizable form) of a ProcedureCall
*
* @author Steve Ebersole
*/

View File

@ -385,6 +385,11 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements
// for now assume there are no resultClasses nor mappings defined..
// TOTAL PROOF-OF-CONCEPT!!!!!!
// todo : how to identify calls which should be in the form `{? = call procName...}` ??? (note leading param marker)
// more than likely this will need to be a method on the native API. I can see this as a trigger to
// both: (1) add the `? = ` part and also (2) register a REFCURSOR parameter for DBs (Oracle, PGSQL) that
// need it.
final StringBuilder buffer = new StringBuilder().append( "{call " )
.append( procedureName )
.append( "(" );

View File

@ -25,9 +25,7 @@ package org.hibernate.procedure.internal;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.hibernate.JDBCException;
import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport;
import org.hibernate.procedure.ParameterRegistration;
import org.hibernate.procedure.ProcedureResult;
@ -70,19 +68,25 @@ public class ProcedureResultImpl extends ResultImpl implements ProcedureResult {
}
@Override
protected CurrentReturnDescriptor buildCurrentReturnDescriptor(boolean isResultSet, int updateCount) {
return new ProcedureCurrentReturnDescriptor( isResultSet, updateCount, refCursorParamIndex );
protected CurrentReturnState buildCurrentReturnDescriptor(boolean isResultSet, int updateCount) {
return new ProcedureCurrentReturnState( isResultSet, updateCount, refCursorParamIndex );
}
protected boolean hasMoreReturns(CurrentReturnDescriptor descriptor) {
protected boolean hasMoreReturns(CurrentReturnState descriptor) {
return super.hasMoreReturns( descriptor )
|| ( (ProcedureCurrentReturnDescriptor) descriptor ).refCursorParamIndex < refCursorParameters.length;
|| ( (ProcedureCurrentReturnState) descriptor ).refCursorParamIndex < refCursorParameters.length;
}
@Override
protected Return buildExtendedReturn(CurrentReturnDescriptor returnDescriptor) {
protected boolean hasExtendedReturns(CurrentReturnState currentReturnState) {
return ProcedureCurrentReturnState.class.isInstance( currentReturnState )
&& ( (ProcedureCurrentReturnState) currentReturnState ).refCursorParamIndex < refCursorParameters.length;
}
@Override
protected Return buildExtendedReturn(CurrentReturnState returnDescriptor) {
this.refCursorParamIndex++;
final int refCursorParamIndex = ( (ProcedureCurrentReturnDescriptor) returnDescriptor ).refCursorParamIndex;
final int refCursorParamIndex = ( (ProcedureCurrentReturnState) returnDescriptor ).refCursorParamIndex;
final ParameterRegistrationImplementor refCursorParam = refCursorParameters[refCursorParamIndex];
ResultSet resultSet;
if ( refCursorParam.getName() != null ) {
@ -95,21 +99,13 @@ public class ProcedureResultImpl extends ResultImpl implements ProcedureResult {
.getService( RefCursorSupport.class )
.getResultSet( callableStatement, refCursorParam.getPosition() );
}
return new ResultSetReturn( this, resultSet );
return new ResultSetReturnImpl( extractResults( resultSet ) );
}
protected JDBCException convert(SQLException e, String message) {
return procedureCall.getSession().getFactory().getSQLExceptionHelper().convert(
e,
message,
procedureCall.getProcedureName()
);
}
protected static class ProcedureCurrentReturnDescriptor extends CurrentReturnDescriptor {
protected class ProcedureCurrentReturnState extends CurrentReturnState {
private final int refCursorParamIndex;
private ProcedureCurrentReturnDescriptor(boolean isResultSet, int updateCount, int refCursorParamIndex) {
private ProcedureCurrentReturnState(boolean isResultSet, int updateCount, int refCursorParamIndex) {
super( isResultSet, updateCount );
this.refCursorParamIndex = refCursorParamIndex;
}

View File

@ -32,4 +32,8 @@ public class NoMoreReturnsException extends HibernateException {
public NoMoreReturnsException(String message) {
super( message );
}
public NoMoreReturnsException() {
super( "Results have been exhausted" );
}
}

View File

@ -33,6 +33,13 @@ package org.hibernate.result;
* @author Steve Ebersole
*/
public interface Result {
/**
* Retrieve the current return.
*
* @return The current return.
*/
public Return getCurrentReturn();
/**
* Are there any more returns associated with this result?
*
@ -42,9 +49,12 @@ public interface Result {
public boolean hasMoreReturns();
/**
* Retrieve the next return.
* Retrieve the next return
*
* @return The next return.
*
* @throws NoMoreReturnsException Thrown if there are no more returns associated with this Result, as would
* have been indicated by a {@code false} return from {@link #hasMoreReturns()}.
*/
public Return getNextReturn() throws NoMoreReturnsException;
}

View File

@ -40,7 +40,9 @@ import org.hibernate.loader.custom.sql.SQLQueryReturnProcessor;
import org.hibernate.loader.spi.AfterLoadAction;
import org.hibernate.result.NoMoreReturnsException;
import org.hibernate.result.Result;
import org.hibernate.result.ResultSetReturn;
import org.hibernate.result.Return;
import org.hibernate.result.UpdateCountReturn;
import org.hibernate.result.spi.ResultContext;
/**
@ -51,102 +53,101 @@ public class ResultImpl implements Result {
private final PreparedStatement jdbcStatement;
private final CustomLoaderExtension loader;
private CurrentReturnDescriptor currentReturnDescriptor;
private boolean executed = false;
private CurrentReturnState currentReturnState;
public ResultImpl(ResultContext context, PreparedStatement jdbcStatement) {
this.context = context;
this.jdbcStatement = jdbcStatement;
// For now...
// For now... but see the LoadPlan work; eventually this should just be a ResultSetProcessor.
this.loader = buildSpecializedCustomLoader( context );
try {
final boolean isResultSet = jdbcStatement.execute();
currentReturnState = buildCurrentReturnDescriptor( isResultSet );
}
catch (SQLException e) {
throw convert( e, "Error calling CallableStatement.getMoreResults" );
}
}
private CurrentReturnState buildCurrentReturnDescriptor(boolean isResultSet) {
int updateCount = -1;
if ( ! isResultSet ) {
try {
updateCount = jdbcStatement.getUpdateCount();
}
catch (SQLException e) {
throw convert( e, "Error calling CallableStatement.getUpdateCount" );
}
}
return buildCurrentReturnDescriptor( isResultSet, updateCount );
}
protected CurrentReturnState buildCurrentReturnDescriptor(boolean isResultSet, int updateCount) {
return new CurrentReturnState( isResultSet, updateCount );
}
@Override
public Return getCurrentReturn() {
if ( currentReturnState == null ) {
return null;
}
return currentReturnState.getReturn();
}
@Override
public boolean hasMoreReturns() {
if ( currentReturnDescriptor == null ) {
final boolean isResultSet;
if ( executed ) {
try {
isResultSet = jdbcStatement.getMoreResults();
}
catch (SQLException e) {
throw convert( e, "Error calling CallableStatement.getMoreResults" );
}
}
else {
try {
isResultSet = jdbcStatement.execute();
}
catch (SQLException e) {
throw convert( e, "Error calling CallableStatement.execute" );
}
executed = true;
}
int updateCount = -1;
if ( ! isResultSet ) {
try {
updateCount = jdbcStatement.getUpdateCount();
}
catch (SQLException e) {
throw convert( e, "Error calling CallableStatement.getUpdateCount" );
}
}
currentReturnDescriptor = buildCurrentReturnDescriptor( isResultSet, updateCount );
// prepare the next return state
try {
final boolean isResultSet = jdbcStatement.getMoreResults();
currentReturnState = buildCurrentReturnDescriptor( isResultSet );
}
catch (SQLException e) {
throw convert( e, "Error calling CallableStatement.getMoreResults" );
}
return hasMoreReturns( currentReturnDescriptor );
return hasMoreReturns( currentReturnState );
}
protected CurrentReturnDescriptor buildCurrentReturnDescriptor(boolean isResultSet, int updateCount) {
return new CurrentReturnDescriptor( isResultSet, updateCount );
}
protected boolean hasMoreReturns(CurrentReturnDescriptor descriptor) {
return descriptor.isResultSet
|| descriptor.updateCount >= 0;
protected boolean hasMoreReturns(CurrentReturnState descriptor) {
return descriptor != null && ( descriptor.isResultSet() || descriptor.getUpdateCount() >= 0 );
}
@Override
public Return getNextReturn() {
if ( currentReturnDescriptor == null ) {
if ( executed ) {
throw new IllegalStateException( "Unexpected condition" );
}
else {
throw new IllegalStateException( "hasMoreReturns() not called before getNextReturn()" );
}
}
if ( ! hasMoreReturns( currentReturnDescriptor ) ) {
if ( !hasMoreReturns() ) {
throw new NoMoreReturnsException( "Results have been exhausted" );
}
CurrentReturnDescriptor copyReturnDescriptor = currentReturnDescriptor;
currentReturnDescriptor = null;
return getCurrentReturn();
}
if ( copyReturnDescriptor.isResultSet ) {
try {
return new ResultSetReturn( this, jdbcStatement.getResultSet() );
}
catch (SQLException e) {
throw convert( e, "Error calling CallableStatement.getResultSet" );
}
protected boolean hasExtendedReturns(CurrentReturnState currentReturnState) {
return false;
}
private List extractCurrentResults() {
try {
return extractResults( jdbcStatement.getResultSet() );
}
else if ( copyReturnDescriptor.updateCount >= 0 ) {
return new UpdateCountReturn( this, copyReturnDescriptor.updateCount );
}
else {
return buildExtendedReturn( copyReturnDescriptor );
catch (SQLException e) {
throw convert( e, "Error calling CallableStatement.getResultSet" );
}
}
protected Return buildExtendedReturn(CurrentReturnDescriptor copyReturnDescriptor) {
throw new NoMoreReturnsException( "Results have been exhausted" );
protected List extractResults(ResultSet resultSet) {
try {
return loader.processResultSet( resultSet );
}
catch (SQLException e) {
throw convert( e, "Error extracting results from CallableStatement" );
}
}
protected Return buildExtendedReturn(CurrentReturnState copyReturnDescriptor) {
throw new NoMoreReturnsException();
}
protected JDBCException convert(SQLException e, String message) {
@ -157,23 +158,55 @@ public class ResultImpl implements Result {
);
}
protected static class CurrentReturnDescriptor {
/**
* Encapsulates the information needed to interpret the current return within a result
*/
protected class CurrentReturnState {
private final boolean isResultSet;
private final int updateCount;
protected CurrentReturnDescriptor(boolean isResultSet, int updateCount) {
private Return rtn;
protected CurrentReturnState(boolean isResultSet, int updateCount) {
this.isResultSet = isResultSet;
this.updateCount = updateCount;
}
public boolean isResultSet() {
return isResultSet;
}
public int getUpdateCount() {
return updateCount;
}
public Return getReturn() {
if ( rtn == null ) {
rtn = buildReturn();
}
return rtn;
}
protected Return buildReturn() {
if ( isResultSet() ) {
return new ResultSetReturnImpl( extractCurrentResults() );
}
else if ( getUpdateCount() >= 0 ) {
return new UpdateCountReturnImpl( updateCount );
}
else if ( hasExtendedReturns( currentReturnState ) ) {
return buildExtendedReturn( currentReturnState );
}
throw new NoMoreReturnsException();
}
}
protected static class ResultSetReturn implements org.hibernate.result.ResultSetReturn {
private final ResultImpl storedProcedureOutputs;
private final ResultSet resultSet;
protected static class ResultSetReturnImpl implements ResultSetReturn {
private final List results;
public ResultSetReturn(ResultImpl storedProcedureOutputs, ResultSet resultSet) {
this.storedProcedureOutputs = storedProcedureOutputs;
this.resultSet = resultSet;
public ResultSetReturnImpl(List results) {
this.results = results;
}
@Override
@ -184,17 +217,12 @@ public class ResultImpl implements Result {
@Override
@SuppressWarnings("unchecked")
public List getResultList() {
try {
return storedProcedureOutputs.loader.processResultSet( resultSet );
}
catch (SQLException e) {
throw storedProcedureOutputs.convert( e, "Error calling ResultSet.next" );
}
return results;
}
@Override
public Object getSingleResult() {
List results = getResultList();
final List results = getResultList();
if ( results == null || results.isEmpty() ) {
return null;
}
@ -204,12 +232,10 @@ public class ResultImpl implements Result {
}
}
protected static class UpdateCountReturn implements org.hibernate.result.UpdateCountReturn {
private final ResultImpl result;
protected static class UpdateCountReturnImpl implements UpdateCountReturn {
private final int updateCount;
public UpdateCountReturn(ResultImpl result, int updateCount) {
this.result = result;
public UpdateCountReturnImpl(int updateCount) {
this.updateCount = updateCount;
}
@ -266,6 +292,9 @@ public class ResultImpl implements Result {
private QueryParameters queryParameters;
private SessionImplementor session;
// temp
private final CustomQuery customQuery;
public CustomLoaderExtension(
CustomQuery customQuery,
QueryParameters queryParameters,
@ -273,6 +302,8 @@ public class ResultImpl implements Result {
super( customQuery, session.getFactory() );
this.queryParameters = queryParameters;
this.session = session;
this.customQuery = customQuery;
}
// todo : this would be a great way to add locking to stored procedure support (at least where returning entities).
@ -289,5 +320,12 @@ public class ResultImpl implements Result {
Collections.<AfterLoadAction>emptyList()
);
}
@Override
protected void validateAlias(String alias) {
System.out.println(
"TEMPORARY... discovered result set alias from stored procedure [" + alias + "] : " + customQuery.getSQL()
);
}
}
}

View File

@ -44,6 +44,7 @@ import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.testing.junit4.ExtraAssertions;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@ -171,11 +172,9 @@ public class StoredProcedureTest extends BaseCoreFunctionalTestCase {
ProcedureCall query = session.createStoredProcedureCall( "user");
ProcedureResult procedureResult = query.getResult();
assertTrue( "Checking ProcedureResult has more returns", procedureResult.hasMoreReturns() );
Return nextReturn = procedureResult.getNextReturn();
assertNotNull( nextReturn );
ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() );
ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn;
Return currentReturn = procedureResult.getCurrentReturn();
assertNotNull( currentReturn );
ResultSetReturn resultSetReturn = assertTyping( ResultSetReturn.class, currentReturn );
String name = (String) resultSetReturn.getSingleResult();
assertEquals( "SA", name );
@ -190,13 +189,11 @@ public class StoredProcedureTest extends BaseCoreFunctionalTestCase {
ProcedureCall query = session.createStoredProcedureCall( "findOneUser" );
ProcedureResult procedureResult = query.getResult();
assertTrue( "Checking ProcedureResult has more returns", procedureResult.hasMoreReturns() );
Return nextReturn = procedureResult.getNextReturn();
assertNotNull( nextReturn );
ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() );
ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn;
Return currentReturn = procedureResult.getCurrentReturn();
assertNotNull( currentReturn );
ResultSetReturn resultSetReturn = assertTyping( ResultSetReturn.class, currentReturn );
Object result = resultSetReturn.getSingleResult();
ExtraAssertions.assertTyping( Object[].class, result );
assertTyping( Object[].class, result );
String name = (String) ( (Object[]) result )[1];
assertEquals( "Steve", name );
@ -211,16 +208,14 @@ public class StoredProcedureTest extends BaseCoreFunctionalTestCase {
ProcedureCall query = session.createStoredProcedureCall( "findUsers" );
ProcedureResult procedureResult = query.getResult();
assertTrue( "Checking ProcedureResult has more returns", procedureResult.hasMoreReturns() );
Return nextReturn = procedureResult.getNextReturn();
assertNotNull( nextReturn );
ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() );
ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn;
Return currentReturn = procedureResult.getCurrentReturn();
assertNotNull( currentReturn );
ResultSetReturn resultSetReturn = assertTyping( ResultSetReturn.class, currentReturn );
List results = resultSetReturn.getResultList();
assertEquals( 3, results.size() );
for ( Object result : results ) {
ExtraAssertions.assertTyping( Object[].class, result );
assertTyping( Object[].class, result );
Integer id = (Integer) ( (Object[]) result )[0];
String name = (String) ( (Object[]) result )[1];
if ( id.equals( 1 ) ) {
@ -250,15 +245,13 @@ public class StoredProcedureTest extends BaseCoreFunctionalTestCase {
query.registerParameter( "start", Integer.class, ParameterMode.IN ).bindValue( 1 );
query.registerParameter( "end", Integer.class, ParameterMode.IN ).bindValue( 2 );
ProcedureResult procedureResult = query.getResult();
assertTrue( "Checking ProcedureResult has more returns", procedureResult.hasMoreReturns() );
Return nextReturn = procedureResult.getNextReturn();
assertNotNull( nextReturn );
ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() );
ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn;
Return currentReturn = procedureResult.getCurrentReturn();
assertNotNull( currentReturn );
ResultSetReturn resultSetReturn = assertTyping( ResultSetReturn.class, currentReturn );
List results = resultSetReturn.getResultList();
assertEquals( 1, results.size() );
Object result = results.get( 0 );
ExtraAssertions.assertTyping( Object[].class, result );
assertTyping( Object[].class, result );
Integer id = (Integer) ( (Object[]) result )[0];
String name = (String) ( (Object[]) result )[1];
assertEquals( 1, (int) id );
@ -277,15 +270,13 @@ public class StoredProcedureTest extends BaseCoreFunctionalTestCase {
query.registerParameter( 1, Integer.class, ParameterMode.IN ).bindValue( 1 );
query.registerParameter( 2, Integer.class, ParameterMode.IN ).bindValue( 2 );
ProcedureResult procedureResult = query.getResult();
assertTrue( "Checking ProcedureResult has more returns", procedureResult.hasMoreReturns() );
Return nextReturn = procedureResult.getNextReturn();
assertNotNull( nextReturn );
ExtraAssertions.assertClassAssignability( ResultSetReturn.class, nextReturn.getClass() );
ResultSetReturn resultSetReturn = (ResultSetReturn) nextReturn;
Return currentReturn = procedureResult.getCurrentReturn();
assertNotNull( currentReturn );
ResultSetReturn resultSetReturn = assertTyping( ResultSetReturn.class, currentReturn );
List results = resultSetReturn.getResultList();
assertEquals( 1, results.size() );
Object result = results.get( 0 );
ExtraAssertions.assertTyping( Object[].class, result );
assertTyping( Object[].class, result );
Integer id = (Integer) ( (Object[]) result )[0];
String name = (String) ( (Object[]) result )[1];
assertEquals( 1, (int) id );
@ -307,9 +298,8 @@ public class StoredProcedureTest extends BaseCoreFunctionalTestCase {
ProcedureCall query = session.createStoredProcedureCall( "findUserRange" );
query.registerParameter( 1, Integer.class, ParameterMode.IN );
query.registerParameter( 2, Integer.class, ParameterMode.IN ).bindValue( 2 );
ProcedureResult procedureResult = query.getResult();
try {
procedureResult.hasMoreReturns();
query.getResult();
fail( "Expecting failure due to missing parameter bind" );
}
catch (JDBCException expected) {
@ -320,9 +310,8 @@ public class StoredProcedureTest extends BaseCoreFunctionalTestCase {
ProcedureCall query = session.createStoredProcedureCall( "findUserRange" );
query.registerParameter( "start", Integer.class, ParameterMode.IN );
query.registerParameter( "end", Integer.class, ParameterMode.IN ).bindValue( 2 );
ProcedureResult procedureResult = query.getResult();
try {
procedureResult.hasMoreReturns();
query.getResult();
fail( "Expecting failure due to missing parameter bind" );
}
catch (JDBCException expected) {

View File

@ -25,8 +25,11 @@ package org.hibernate.jpa.internal;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Parameter;
import javax.persistence.ParameterMode;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import javax.persistence.StoredProcedureQuery;
import javax.persistence.TemporalType;
@ -197,7 +200,8 @@ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredPro
@Override
public boolean execute() {
return outputs().hasMoreReturns();
final Return rtn = outputs().getCurrentReturn();
return rtn != null && ResultSetReturn.class.isInstance( rtn );
}
@Override
@ -212,34 +216,76 @@ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredPro
@Override
public int getUpdateCount() {
final Return nextReturn = outputs().getNextReturn();
if ( nextReturn.isResultSet() ) {
final Return rtn = outputs().getCurrentReturn();
if ( rtn == null ) {
return -1;
}
else if ( UpdateCountReturn.class.isInstance( rtn ) ) {
return ( (UpdateCountReturn) rtn ).getUpdateCount();
}
else {
return -1;
}
return ( (UpdateCountReturn) nextReturn ).getUpdateCount();
}
@Override
public List getResultList() {
final Return nextReturn = outputs().getNextReturn();
if ( ! nextReturn.isResultSet() ) {
return null; // todo : what should be thrown/returned here?
final Return rtn = outputs().getCurrentReturn();
if ( ! ResultSetReturn.class.isInstance( rtn ) ) {
throw new IllegalStateException( "Current CallableStatement was not a ResultSet, but getResultList was called" );
}
return ( (ResultSetReturn) nextReturn ).getResultList();
if ( outputs().hasMoreReturns() ) {
outputs().getNextReturn();
}
return ( (ResultSetReturn) rtn ).getResultList();
}
@Override
public Object getSingleResult() {
final Return nextReturn = outputs().getNextReturn();
if ( ! nextReturn.isResultSet() ) {
return null; // todo : what should be thrown/returned here?
final List resultList = getResultList();
if ( resultList == null || resultList.isEmpty() ) {
throw new NoResultException(
String.format(
"Call to stored procedure [%s] returned no results",
procedureCall.getProcedureName()
)
);
}
return ( (ResultSetReturn) nextReturn ).getSingleResult();
else if ( resultList.size() > 1 ) {
throw new NonUniqueResultException(
String.format(
"Call to stored procedure [%s] returned multiple results",
procedureCall.getProcedureName()
)
);
}
return resultList.get( 0 );
}
@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> cls) {
return null;
if ( ProcedureCall.class.isAssignableFrom( cls ) ) {
return (T) procedureCall;
}
else if ( ProcedureResult.class.isAssignableFrom( cls ) ) {
return (T) outputs();
}
else if ( BaseQueryImpl.class.isAssignableFrom( cls ) ) {
return (T) this;
}
throw new PersistenceException(
String.format(
"Unsure how to unwrap %s impl [%s] as requested type [%s]",
StoredProcedureQuery.class.getSimpleName(),
this.getClass().getName(),
cls.getName()
)
);
}

View File

@ -44,31 +44,43 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Tests various JPA usage scenarios for performing stored procedures.
* Tests various JPA usage scenarios for performing stored procedures. Inspired by the awesomely well-done JPA TCK
*
* @author Steve Ebersole
*/
@RequiresDialect( H2Dialect.class )
@FailureExpected( jiraKey = "HHH-8389", message = "Waiting clarification from EG" )
public class JpaUsageTest extends BaseEntityManagerFunctionalTestCase {
/**
* Some tests inspired by the awesomely well-done JPA TCK
*/
@Test
public void testJpaUsage1() {
public void testMultipleGetUpdateCountCalls() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
StoredProcedureQuery query = em.createStoredProcedureQuery( "findOneUser", User.class );
StoredProcedureQuery query = em.createStoredProcedureQuery( "findOneUser" );
// this is what the TCK attempts to do, don't shoot the messenger...
query.getUpdateCount();
// yep, twice
int updateCount = query.getUpdateCount();
em.getTransaction().commit();
em.close();
}
@Test
public void testBasicScalarResults() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
StoredProcedureQuery query = em.createStoredProcedureQuery( "findOneUser" );
boolean isResult = query.execute();
assertTrue( isResult );
int updateCount = query.getUpdateCount();
boolean results = false;
do {
List list = query.getResultList();
assertEquals( 1, list.size() );
results = query.hasMoreResults();
// and it only sets the updateCount once lol
} while ( results || updateCount != -1);
@ -78,15 +90,15 @@ public class JpaUsageTest extends BaseEntityManagerFunctionalTestCase {
}
@Test
public void testJpaUsage2() {
@FailureExpected( jiraKey = "HHH-8398" )
public void testResultClassHandling() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
StoredProcedureQuery query = em.createStoredProcedureQuery( "findOneUser", User.class );
boolean isResult = query.execute();
assertTrue( isResult );
// int updateCount = query.getUpdateCount();
int updateCount = -1;
int updateCount = query.getUpdateCount();
boolean results = false;
do {
@ -122,7 +134,7 @@ public class JpaUsageTest extends BaseEntityManagerFunctionalTestCase {
" return rs;\n" +
"}\n" +
"$$";
public static final String DROP_CMD = "DROP ALIAS findUser IF EXISTS";
public static final String DROP_CMD = "DROP ALIAS findOneUser IF EXISTS";
@Override
protected void afterEntityManagerFactoryBuilt() {
@ -130,6 +142,7 @@ public class JpaUsageTest extends BaseEntityManagerFunctionalTestCase {
}
private void execute(String sql) {
System.out.println( "Executing SQL : " + sql );
final SessionFactoryImplementor sf = entityManagerFactory().unwrap( SessionFactoryImplementor.class );
final Connection conn;
try {
@ -153,14 +166,13 @@ public class JpaUsageTest extends BaseEntityManagerFunctionalTestCase {
}
}
catch (SQLException e) {
throw new RuntimeException( "Unable to execute SQL : " + sql );
throw new RuntimeException( "Unable to execute SQL : " + sql, e );
}
}
@Override
public void releaseResources() {
execute( DROP_CMD );
super.releaseResources();
}
}