HHH-8395 - JPA StoredProcedureQuery#getUpdateCount should prefer return -1 rather than throw exceptions
This commit is contained in:
parent
33b7e9f441
commit
cd1c80b82d
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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( "(" );
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -32,4 +32,8 @@ public class NoMoreReturnsException extends HibernateException {
|
|||
public NoMoreReturnsException(String message) {
|
||||
super( message );
|
||||
}
|
||||
|
||||
public NoMoreReturnsException() {
|
||||
super( "Results have been exhausted" );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue