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:
+ *
+ * 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); }