Index: java/engine/org/apache/derby/impl/sql/execute/CallStatementResultSet.java =================================================================== --- java/engine/org/apache/derby/impl/sql/execute/CallStatementResultSet.java (revision 601557) +++ java/engine/org/apache/derby/impl/sql/execute/CallStatementResultSet.java (working copy) @@ -21,14 +21,30 @@ 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. - * + * Call a Java procedure. This calls a generated method in the + * activation which sets up the parameters and then calls the + * Java method that the procedure resolved to. + *

+ * Valid dynamic results returned by the procedure will be closed + * as inaccessible when this is closed (e.g. a CALL within a trigger). + * + *
+ * Any code that requires the dynamic results to be accessible + * (such as the JDBC Statement object executing the CALL) must + * obtain the dynamic results from Activation.getDynamicResults() + * and remove each ResultSet it will be handling by clearing the + * reference in the object returned. + * + * @see Activation#getDynamicResults() */ class CallStatementResultSet extends NoRowsResultSetImpl { @@ -49,14 +65,106 @@ } /** + * 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.processInaccessibleDynamicResult(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 601557) +++ java/engine/org/apache/derby/impl/jdbc/EmbedConnectionContext.java (working copy) @@ -133,4 +133,24 @@ false, (EmbedStatement) null, true); return rs; } + + /** + * Convert a ResultSet to a dynamic result, but one that will + * be closed due to it being inaccessible. We cannot simply + * close the ResultSet as it the nested connection that created + * it might be closed, leading to its close method being a no-op. + * This performs all the conversion required but does not close + * the ResultSet. + * + * @see EmbedStatement#processDynamicResult(EmbedConnection, java.sql.ResultSet, EmbedStatement) + */ + public boolean processInaccessibleDynamicResult(java.sql.ResultSet resultSet) { + EmbedConnection conn = (EmbedConnection) connRef.get(); + if (conn == null) + return false; + + // Pass in null as the Statement to own the ResultSet since + // we don't have one since the dynamic result will be inaccessible. + return EmbedStatement.processDynamicResult(conn, resultSet, null) != null; + } } Index: java/engine/org/apache/derby/impl/jdbc/EmbedResultSet.java =================================================================== --- java/engine/org/apache/derby/impl/jdbc/EmbedResultSet.java (revision 601557) +++ java/engine/org/apache/derby/impl/jdbc/EmbedResultSet.java (working copy) @@ -565,7 +565,7 @@ // just give up and return return; } - + try { try { theResults.finish(); // release the result set, don't just close it @@ -4389,16 +4389,26 @@ } /** - A dynamic result set was created in a procedure by a nested connection. + A dynamic result was created in a procedure by a nested connection. Once the procedure returns, there is a good chance that connection is closed, so we re-attach the result set to the connection of the statement the called the procedure, which will be still open. +
+ In the case where the dynamic result will not be accessible + then owningStmt will be null, the ResultSet will be linked to + the root connection to allow its close method to work. It + will remain attached to its original statement. */ void setDynamicResultSet(EmbedStatement owningStmt) { - this.owningStmt = owningStmt; - this.localConn = owningStmt.getEmbedConnection(); + if (owningStmt != null) { + this.owningStmt = owningStmt; + this.localConn = owningStmt.getEmbedConnection(); + } + else + this.localConn = this.localConn.rootConnection; + // The activation that created these results now becomes // a single use activation so it will be closed when this // object is closed. Otherwise the activation would Index: java/engine/org/apache/derby/impl/jdbc/EmbedStatement.java =================================================================== --- java/engine/org/apache/derby/impl/jdbc/EmbedStatement.java (revision 601557) +++ 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,26 +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.processDynamicResult( + getEmbedConnection(), rs, this); + + 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 +1607,52 @@ return actualCount; } + + /** + * Process a ResultSet created in a Java procedure as a dynamic result. + * To be a valid dynamic result the ResultSet must be: + *

+ * Any invalid ResultSet is ignored. + * + *

+ * if b + * + * @param conn Connection ResultSet needs to belong to + * @param resultSet ResultSet to be tested + * @param callStatement Statement that executed the CALL, null if + * @return The result set cast down to EmbedResultSet, null if not a valid + * dynamic result. + */ + static EmbedResultSet processDynamicResult(EmbedConnection conn, + java.sql.ResultSet resultSet, + EmbedStatement callStatement) + { + 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; + + lrs.setDynamicResultSet(callStatement); + + 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 601557) +++ java/engine/org/apache/derby/iapi/jdbc/ConnectionContext.java (working copy) @@ -21,7 +21,6 @@ package org.apache.derby.iapi.jdbc; -import org.apache.derby.iapi.error.StandardException; import org.apache.derby.iapi.sql.ResultSet; import java.sql.Connection; @@ -57,4 +56,27 @@ ( ResultSet executionResultSet ) throws java.sql.SQLException; + + /** + * Process the resultSet as a dynamic result for closure. + * The result set will have been created in a Java procedure. + * If the ResultSet is a valid dynamic ResultSet for + * this connection, then it is set up as a dynamic result + * which includes: + *

+ *

+ * If the result set a valid dynamic result then false will + * be returned and no action made against it. + * + * @param resultSet ResultSet to process. + * @return True if this ResultSet was created by this connection + * and the result set is open. False otherwise. + */ + public boolean processInaccessibleDynamicResult(java.sql.ResultSet resultSet); }