I've attached an "alpha" patch for the DERBY-213 for anyone who is
interested in the DERBY-213 issue. It should be noted that the patch is
contingent upon our current interpretation of the JDBC
specifications/documentation. This patch is meant to address both the
early close problem recently discussed in Derby-user and incorrect
auto-commit behavior for multiple ResultSets. It is *not* meant to
address the issue of DatabaseMetaData ResultSets invoking an
auto-commit. I may file this as a seperate issue in the future. I call
this patch an alpha patch because there is one known bug associated with
it and potentially other unspotted ones. The bug in question occurs in
the lang/updatableResultSet.java test
specifically a call to:
System.out.println("Have this call to rs.rowDeleted() just to make sure
the method does always return false? " + rs.rowDeleted());
rs.deleteRow(); //This line, approximately line 896 of the
test file.
System.out.println("Have this call to rs.rowDeleted() just to make
sure the method does always return false? " + rs.rowDeleted());
I will continue my attempts to establish why this is a bug but as always
input is appreciated.
Philip
Index: am/Connection.java
===================================================================
--- am/Connection.java (revision 191177)
+++ am/Connection.java (working copy)
@@ -515,10 +515,12 @@
}
// precondition: autoCommit_ is true
- public void flowAutoCommit() throws SqlException {
+ public boolean flowAutoCommit() throws SqlException {
if (willAutoCommitGenerateFlow()) {
flowCommit();
+ return true;
}
+ return false;
}
public boolean willAutoCommitGenerateFlow() throws
org.apache.derby.client.am.SqlException {
@@ -1638,4 +1640,10 @@
inUnitOfWork_ = inUnitOfWork;
}
+ public void markAutoCommitted() {
+ java.util.ListIterator iter = openStatements_.listIterator();
+ while(iter.hasNext()) {
+ ((Statement)iter.next()).markAutoCommitted();
+ }
+ }
}
Index: am/Statement.java
===================================================================
--- am/Statement.java (revision 191177)
+++ am/Statement.java (working copy)
@@ -459,6 +459,11 @@
} else {
flowCloseOutsideUOW();
}
+
+ //The connection will not auto commit on close in the case of
+ //multiple open resultsets. This call is here to insure proper
+ //autocommit.
+ connection_.flowAutoCommit();
} finally {
markClosed();
connection_.openStatements_.remove(this);
@@ -1240,33 +1245,29 @@
//
// This is fixed by passing a flag to our statement close processing that
prevents
// driving additional auto-commits after each statement close.
- // Connectino close drives its own final auto-commit.
+ // Connection close drives its own final auto-commit.
//
boolean writeCloseResultSets(int number, boolean allowAutoCommits) throws
SqlException {
- boolean requiresAutocommit = false;
+ ResultSet crs = resultSet_;
if (resultSetList_ != null) {
+ //TODO Potential for ArrayIndexOutOfBoundsException. Fix?
for (int i = 0; i < number; i++) {
if (resultSetList_[i] != null) {
if (resultSetList_[i].openOnServer_) {
resultSetList_[i].writeClose();
}
- if (!resultSetList_[i].autoCommitted_ && allowAutoCommits)
{
- requiresAutocommit = true;
- }
}
}
+ crs = resultSetList_[number - 1];
} else if (generatedKeysResultSet_ != null &&
generatedKeysResultSet_.openOnServer_) {
generatedKeysResultSet_.writeClose();
} else if (resultSet_ != null) {
if (resultSet_.openOnServer_) {
resultSet_.writeClose();
}
- if (!resultSet_.autoCommitted_ && allowAutoCommits) {
- requiresAutocommit = true;
- }
}
- if (connection_.autoCommit_ && requiresAutocommit &&
isAutoCommittableStatement_) {
- connection_.writeAutoCommit();
+ if (connection_.autoCommit_ && isAutoCommitRequired(crs) &&
isAutoCommittableStatement_) {
+ resultSetCommitting(true);
if (connection_.isXAConnection_) {
return (connection_.xaState_ ==
Connection.XA_T0_NOT_ASSOCIATED) ;
} else {
@@ -1283,8 +1284,18 @@
}
void readCloseResultSets(int number, boolean allowAutoCommits) throws
SqlException {
+ /* The call to isAutoCommitRequired must be done before
+ * the readClose() is done. The read close can mark ResultSets
+ * closed. If these Resultsets were open in the writeCloseResultSets
+ * method then we would have a read without the associated write
+ * which will cause a hang.
+ */
boolean requiredAutocommit = false;
+ ResultSet crs = resultSet_;
if (resultSetList_ != null) {
+ //TODO Potential for ArrayIndexOutOfBoundsException. Fix?
+ requiredAutocommit = isAutoCommitRequired(
+ resultSetList_[number - 1]);
for (int i = 0; i < number; i++) {
if (resultSetList_[i] != null) {
if (resultSetList_[i].openOnServer_) {
@@ -1292,9 +1303,6 @@
} else {
resultSetList_[i].markClosed();
}
- if (!resultSetList_[i].autoCommitted_ && allowAutoCommits)
{
- requiredAutocommit = true;
- }
}
}
} else if (generatedKeysResultSet_ != null) {
@@ -1304,14 +1312,12 @@
generatedKeysResultSet_.markClosed();
}
} else if (resultSet_ != null) {
+ requiredAutocommit = isAutoCommitRequired(resultSet_);
if (resultSet_.openOnServer_) {
resultSet_.readClose();
} else {
resultSet_.markClosed();
}
- if (!resultSet_.autoCommitted_ && allowAutoCommits) {
- requiredAutocommit = true;
- }
}
// we only commit when auto commit is turned on and at least one
result set needed closing on server.
if (connection_.autoCommit_ && requiredAutocommit &&
isAutoCommittableStatement_) {
@@ -1326,9 +1332,9 @@
// last result set closed in a set of multiple result sets of the
owning statement,
// if so, we need to flow the auto-commit (but only then).
// currently, the code to do this is only in the closeX() path, which
isn't called here
- writeCloseResultSets(numberOfResultSetsToClose, false);
+ writeCloseResultSets(numberOfResultSetsToClose, true);
agent_.flow(this);
- readCloseResultSets(numberOfResultSetsToClose, false); // true means
permit auto-commits
+ readCloseResultSets(numberOfResultSetsToClose, true); // true means
permit auto-commits
agent_.endReadChain();
}
@@ -1339,9 +1345,9 @@
// last result set closed in a set of multiple result sets of the
owning statement,
// if so, we need to flow the auto-commit (but only then).
// currently, the code to do this is only in the closeX() path, which
isn't called here
- writeCloseResultSets(numberOfResultSetsToClose, false);
+ writeCloseResultSets(numberOfResultSetsToClose, true);
agent_.flowOutsideUOW();
- readCloseResultSets(numberOfResultSetsToClose, false); // true means
permit auto-commits
+ readCloseResultSets(numberOfResultSetsToClose, true); // true means
permit auto-commits
agent_.endReadChain();
}
@@ -2107,5 +2113,60 @@
connection_.clientCursorNameCache_.remove(cursorName_);
cursorName_ = null;
}
+ }
+
+ /**
+ * Method that checks to see if any other ResultSets are open. If not
+ * proceeds with the autocommit.
+ *
+ * @param closingRS The ResultSet to be closed
+ * @param writeChain A Boolean indicating whether this method
+ * is part of a chain of write from client to Server
+ * @throws SqlException
+ */
+ public void resultSetCommitting(boolean writeChain) throws SqlException {
+ if (writeChain) {
+ connection_.writeAutoCommit();
+ } else {
+ if (connection_.flowAutoCommit())
+ connection_.markAutoCommitted();
+ }
+ }
+
+ public void markAutoCommitted() {
+ if (resultSetList_ != null) {
+ for (int i = 0; i < resultSetList_.length; i++) {
+ resultSetList_[i].markAutoCommitted();
+ }
+ }
+ if (resultSet_ != null) resultSet_.markAutoCommitted();
}
+
+ /**
+ * Method to check to see if there needs to be a commit.
+ * @param committingRS
+ * @return
+ */
+ public boolean isAutoCommitRequired(ResultSet committingRS) {
+ // If the Connection is not in auto commit then this statement
completion
+ // cannot cause a commit.
+ if (!connection_.autoCommit_ || committingRS == null ||
committingRS.autoCommitted_)
+ return false;
+
+ if (resultSetList_ != null) {
+ for (int i = 0; i < resultSetList_.length; i++) {
+ ResultSet currentRS = resultSetList_[i];
+ if (currentRS == null)
+ continue;
+ if (!currentRS.openOnClient_)
+ continue;
+ if (currentRS == committingRS)
+ continue;
+
+ // at least one still open so no commit now.
+ return false;
+ }
+ }
+ return true;
+ }
}
Index: am/ResultSet.java
===================================================================
--- am/ResultSet.java (revision 191177)
+++ am/ResultSet.java (working copy)
@@ -283,14 +283,13 @@
// we will close the result set, the autocommit logic is in the
closeX() method
// if (!isValidCursorPosition_ && // We've gone past the end (+100)
// cursor_ != null) {
- if ((!isValidCursorPosition_ && cursor_ != null) ||
- (statement_.maxRows_ > 0 && cursor_.rowsRead_ >
statement_.maxRows_)) {
+ if (statement_.maxRows_ > 0 && cursor_.rowsRead_ >
statement_.maxRows_) {
isValidCursorPosition_ = false;
-
+
// if not on a valid row and the query is closed at the server.
// check for an error which may have caused the cursor to
terminate.
// if there were no more rows because of an error, then this
method
- // should throw an SqlException rather than just returning
false.
+ // should throw an SqlException rather than
just returning false.
// note: closeX is still called and this will cause the
// result set to be closed on the client. any additional calls
to
// next() will fail checkForClosedResultSet(), the query
terminating exception is
@@ -306,14 +305,12 @@
sqlException = new SqlException(agent_.logWriter_,
queryTerminatingSqlca_);
}
}
- try {
- closeX(); // the auto commit logic is in closeX()
- } catch (SqlException sqle) {
- sqlException = Utils.accumulateSQLException(sqle,
sqlException);
- }
if (sqlException != null) {
throw sqlException;
}
+
+ if (statement_.isAutoCommitRequired(this))
+ statement_.resultSetCommitting(false);
}
}
@@ -355,9 +352,7 @@
// for forward-only cursors, check if rowsRead_ > maxRows_.
// for scrollable cursors, check if absolute row number > maxRows_.
// maxRows_ will be ignored by sensitive dynamic cursors since we
don't know the rowCount
- if (!openOnClient_) {
- isValidCursorPosition_ = false;
- } else if (sensitivity_ != sensitivity_sensitive_dynamic__ &&
statement_.maxRows_ > 0 &&
+ if (sensitivity_ != sensitivity_sensitive_dynamic__ &&
statement_.maxRows_ > 0 &&
(firstRowInRowset_ + currentRowInRowset_ >
statement_.maxRows_)) {
isValidCursorPosition_ = false;
}
@@ -383,16 +378,17 @@
}
try {
if (openOnServer_) {
- flowCloseAndAutoCommitIfNotAutoCommitted();
+ flowCloseAndAutoCommit();
} else {
- flowAutoCommitIfNotAutoCommitted(); // in case of early close
+ if (statement_.isAutoCommitRequired(this))
+ statement_.resultSetCommitting(false);
}
} finally {
markClosed();
connection_.CommitAndRollbackListeners_.remove(this);
}
- flowAutoCommitIfLastOpenMultipleResultSetWasJustClosed();
+// flowAutoCommitIfLastOpenMultipleResultSetWasJustClosed();
if (statement_.openOnClient_ && statement_.isCatalogQuery_) {
statement_.closeX();
}
@@ -411,33 +407,31 @@
resultSetMetaData_ = null;
}
- void flowCloseAndAutoCommitIfNotAutoCommitted() throws SqlException {
+ void flowCloseAndAutoCommit() throws SqlException {
agent_.beginWriteChain(statement_);
- writeCloseAndAutoCommitIfNotAutoCommitted();
+ writeCloseAndAutoCommit();
agent_.flow(statement_);
- readCloseAndAutoCommitIfNotAutoCommitted();
+ readCloseAndAutoCommit();
agent_.endReadChain();
}
- private void writeCloseAndAutoCommitIfNotAutoCommitted() throws
SqlException {
+ private void writeCloseAndAutoCommit() throws SqlException {
// set autoCommitted_ to false so commit will flow following
// close cursor if autoCommit is true.
autoCommitted_ = false;
if (generatedSection_ == null) { // none call statement result set case
writeCursorClose_(statement_.section_);
- writeAutoCommitIfNotAutoCommitted();
} else { // call statement result set(s) case
writeCursorClose_(generatedSection_);
}
+ if (statement_.isAutoCommitRequired(this))
+ statement_.resultSetCommitting(true);
}
- private void readCloseAndAutoCommitIfNotAutoCommitted() throws
SqlException {
- if (generatedSection_ == null) { // none call statement result set case
- readCursorClose_();
+ private void readCloseAndAutoCommit() throws SqlException {
+ readCursorClose_();
+ if (statement_.isAutoCommitRequired(this))
readAutoCommitIfNotAutoCommitted();
- } else { // call statement result set(s) case
- readCursorClose_();
- }
}
void writeClose() throws SqlException {
@@ -463,13 +457,6 @@
}
}
- void flowAutoCommitIfNotAutoCommitted() throws SqlException {
- if (generatedSection_ == null && connection_.autoCommit_ &&
!autoCommitted_) {
- connection_.flowAutoCommit();
- markAutoCommitted();
- }
- }
-
// precondition: transaction state allows for auto commit to generate flow
private void writeAutoCommitIfNotAutoCommitted() throws SqlException {
if (connection_.autoCommit_ && !autoCommitted_) {
@@ -480,29 +467,10 @@
private void readAutoCommitIfNotAutoCommitted() throws SqlException {
if (connection_.autoCommit_ && !autoCommitted_) {
connection_.readAutoCommit();
- markAutoCommitted();
+ connection_.markAutoCommitted();
}
}
- private void flowAutoCommitIfLastOpenMultipleResultSetWasJustClosed()
throws SqlException {
- // After this call, the generatedSection_ is reset to null to avoid
repeating the commit.
- if (generatedSection_ != null && statement_ != null &&
statement_.resultSetList_ != null) {
- int count = 0;
- for (int i = 0; i < statement_.resultSetList_.length; i++) {
- if (statement_.resultSetList_[i] == null) {
- count++;
- }
- }
- if (count == statement_.resultSetList_.length) {
- if (connection_.autoCommit_ && !autoCommitted_) {
- connection_.flowAutoCommit();
- markAutoCommitted();
- }
- }
- }
- generatedSection_ = null; // this is prevent a subsequent close() call
from doing another autocommit.
- }
-
public boolean wasNull() throws SqlException {
if (agent_.loggingEnabled()) {
@@ -2947,7 +2915,7 @@
public void completeLocalCommit(java.util.Iterator listenerIterator) {
cursorUnpositionedOnServer_ = true;
- markAutoCommitted();
+ connection_.markAutoCommitted();
if (!cursorHold_) {
// only non-held cursors need to be closed at commit
markClosed();
@@ -2959,7 +2927,7 @@
}
public void completeLocalRollback(java.util.Iterator listenerIterator) {
- markAutoCommitted();
+ connection_.markAutoCommitted();
// all cursors need to be closed at rollback
markClosed();
nullOutReferenceInStatement();
@@ -3028,7 +2996,7 @@
// the result set now.
public void setAutoCommitStateForSingletonCursors() {
if (connection_.autoCommit_) {
- markAutoCommitted();
+ connection_.markAutoCommitted();
}
}
@@ -3164,7 +3132,7 @@
throw new SqlException(agent_.logWriter_, "No updateXXX issued on
this row.");
}
preparedStatementForUpdate_ =
-
statement_.connection_.preparePositionedUpdateStatement(updateString,
+ connection_.preparePositionedUpdateStatement(updateString,
statement_.section_.getPositionedUpdateSection());
}
@@ -3174,7 +3142,7 @@
String deleteString = buildDeleteString();
preparedStatementForDelete_ =
-
statement_.connection_.preparePositionedUpdateStatement(deleteString,
+ connection_.preparePositionedUpdateStatement(deleteString,
statement_.section_.getPositionedUpdateSection()); //
update section
}
@@ -3856,18 +3824,13 @@
}
}
}
+
+ /**
+ * For debug purposes
+ *
+ * @return Whether this statement has been auto-committed
+ */
+ public boolean isAutoCommitted() {
+ return autoCommitted_;
+ }
}
-
-
-
-
-
-
-
-
-
-
-
-
-
-