Index: java/engine/org/apache/derby/impl/sql/execute/CallStatementResultSet.java =================================================================== --- java/engine/org/apache/derby/impl/sql/execute/CallStatementResultSet.java (revision 601462) +++ java/engine/org/apache/derby/impl/sql/execute/CallStatementResultSet.java (working copy) @@ -21,10 +21,13 @@ package org.apache.derby.impl.sql.execute; +import java.sql.ResultSet; +import java.sql.SQLException; + import org.apache.derby.iapi.error.StandardException; +import org.apache.derby.iapi.jdbc.ConnectionContext; import org.apache.derby.iapi.services.loader.GeneratedMethod; import org.apache.derby.iapi.sql.Activation; -import org.apache.derby.iapi.sql.ResultSet; /** * Call the specified expression, ignoring the return, if any. @@ -49,14 +52,104 @@ } /** + * Just invoke the method. @exception StandardException Standard Derby error policy */ public void open() throws StandardException { setup(); methodCall.invoke(activation); - close(); } + + /** + * Need to explicitly close any dynamic result sets. + *
+ * If the dynamic results are not accessible then they + * need to be destroyed (ie. closed) according the the + * SQL Standard. + *
+ * An execution of a CALL statement through JDBC makes the + * dynamic results accessible, in this case the closing + * of the dynamic result sets is handled by the JDBC + * statement object (EmbedStatement) that executed the CALL. + * We cannot unify the closing of dynamic result sets to + * this close, as in accessible case it is called during + * the Statement.execute call, thus it would close the + * dynamic results before the application has a change + * to use them. + * + *
+ * With an execution of a CALL + * statement as a trigger's action statement the dynamic + * result sets are not accessible. In this case this close + * method is called after the execution of the trigger's + * action statement. + *
+ *
+ * Section 4.27.5 of the TECHNICAL CORRIGENDUM 1 to the SQL 2003 + * Standard details what happens to dynamic result sets in detail, + * the SQL 2003 foundation document is missing these details. + */ + public void close() throws StandardException + { + super.close(); + + ResultSet[][] dynamicResults = getActivation().getDynamicResults(); + if (dynamicResults != null) + { + // Need to ensure all the result sets opened by this + // CALL statement for this connection are closed. + // If any close() results in an exception we need to keep going, + // save any exceptions and then throw them once we are complete. + StandardException errorOnClose = null; + + ConnectionContext jdbcContext = null; + + for (int i = 0; i < dynamicResults.length; i++) + { + ResultSet[] param = dynamicResults[i]; + ResultSet drs = param[0]; + + // Can be null if the procedure never set this parameter + // or if the dynamic results were processed by JDBC (EmbedStatement). + if (drs == null) + continue; + + if (jdbcContext == null) + jdbcContext = (ConnectionContext) + lcc.getContextManager().getContext(ConnectionContext.CONTEXT_ID); + + try { + + // Is this a valid, open dynamic result set for this connection? + if (!jdbcContext.isMyOpenResultSet(drs)) + { + // If not just ignore it, not Derby's problem. + continue; + } + + drs.close(); + + } catch (SQLException e) { + + // Just report the first error + if (errorOnClose == null) + { + StandardException se = StandardException.plainWrapException(e); + errorOnClose = se; + } + } + finally { + // Remove any reference to the ResultSet to allow + // it and any associated resources to be garbage collected. + param[0] = null; + } + } + + if (errorOnClose != null) + throw errorOnClose; + } + } /** * @see ResultSet#cleanUp Index: java/engine/org/apache/derby/impl/jdbc/EmbedConnectionContext.java =================================================================== --- java/engine/org/apache/derby/impl/jdbc/EmbedConnectionContext.java (revision 601462) +++ java/engine/org/apache/derby/impl/jdbc/EmbedConnectionContext.java (working copy) @@ -133,4 +133,12 @@ false, (EmbedStatement) null, true); return rs; } + + public boolean isMyOpenResultSet(java.sql.ResultSet resultSet) { + EmbedConnection conn = (EmbedConnection) connRef.get(); + if (conn == null) + return false; + + return EmbedStatement.isOpenResultSetForConnection(conn, resultSet) != null; + } } Index: java/engine/org/apache/derby/impl/jdbc/EmbedStatement.java =================================================================== --- java/engine/org/apache/derby/impl/jdbc/EmbedStatement.java (revision 601462) +++ java/engine/org/apache/derby/impl/jdbc/EmbedStatement.java (working copy) @@ -1272,8 +1272,6 @@ } updateCount = resultsToWrap.modifiedRowCount(); - - resultsToWrap.finish(); // Don't need the result set any more results = null; // note that we have none. int dynamicResultCount = 0; @@ -1282,6 +1280,8 @@ processDynamicResults(a.getDynamicResults(), a.getMaxDynamicResults()); } + + resultsToWrap.finish(); // Don't need the result set any more // executeQuery() is not allowed if the statement // doesn't return exactly one ResultSet. @@ -1553,25 +1553,25 @@ java.sql.ResultSet[] param = holder[i]; - if (param[0] == null) - continue; + java.sql.ResultSet rs = param[0]; - java.sql.ResultSet rs = param[0]; + // Clear the JDBC dynamic ResultSet from the language + // ResultSet for the CALL statement. This stops the + // CALL statement closing the ResultSet when its language + // ResultSet is closed, which will happen just after the + // call to the processDynamicResults() method. param[0] = null; + + // ignore non-Derby result sets or results sets from another connection + // and closed result sets. + EmbedResultSet lrs = EmbedStatement.isOpenResultSetForConnection( + getEmbedConnection(), rs); + + if (lrs == null) + { + continue; + } - // ignore non-Derby result sets or results sets from another connection - if (!(rs instanceof EmbedResultSet)) - continue; - - EmbedResultSet lrs = (EmbedResultSet) rs; - - if (lrs.getEmbedConnection().rootConnection != getEmbedConnection().rootConnection) - continue; - - // ignore closed result sets. - if (lrs.isClosed) - continue; - lrs.setDynamicResultSet(this); sorted[actualCount++] = lrs; } @@ -1608,7 +1608,37 @@ return actualCount; } + + /** + * Does the ResultSet belong to the passed in connection + * and is open. + * @param conn Connection ResultSet needs to belong to + * @param resultSet ResultSet to be tested + * @return The result set cast down to EmbedResultSet, null if not valid + * for this connection. + */ + static EmbedResultSet isOpenResultSetForConnection(EmbedConnection conn, + java.sql.ResultSet resultSet) + { + if (resultSet == null) + return null; + // ignore non-Derby result sets or results sets from another connection + if (!(resultSet instanceof EmbedResultSet)) + return null; + + EmbedResultSet lrs = (EmbedResultSet) resultSet; + + if (lrs.getEmbedConnection().rootConnection != conn.rootConnection) + return null; + + // ignore closed result sets. + if (lrs.isClosed) + return null; + + return lrs; + } + /** Callback on the statement when one of its result sets is closed. This allows the statement to control when it completes and hence Index: java/engine/org/apache/derby/iapi/jdbc/ConnectionContext.java =================================================================== --- java/engine/org/apache/derby/iapi/jdbc/ConnectionContext.java (revision 601462) +++ java/engine/org/apache/derby/iapi/jdbc/ConnectionContext.java (working copy) @@ -57,4 +57,13 @@ ( ResultSet executionResultSet ) throws java.sql.SQLException; + + /** + * Return true if this ResultSet is one created by + * this connection and is open. + * @param resultSet ResultSet to test. + * @return True if this ResultSet was created by this connection + * and the result set is open. False otherwise. + */ + public boolean isMyOpenResultSet(java.sql.ResultSet resultSet); }