Fixed "database locked" exception when transaction is rolling back
diff --git a/src/main/java/org/robolectric/shadows/ShadowSQLiteDatabase.java b/src/main/java/org/robolectric/shadows/ShadowSQLiteDatabase.java
index f84076a..65ba450 100644
--- a/src/main/java/org/robolectric/shadows/ShadowSQLiteDatabase.java
+++ b/src/main/java/org/robolectric/shadows/ShadowSQLiteDatabase.java
@@ -126,17 +126,11 @@
PreparedStatement insert = connection.prepareStatement(sqlInsertString.sql, Statement.RETURN_GENERATED_KEYS);
Iterator<Object> columns = sqlInsertString.columnValues.iterator();
int i = 1;
- long result = -1;
while (columns.hasNext()) {
insert.setObject(i++, columns.next());
}
insert.executeUpdate();
- ResultSet resultSet = insert.getGeneratedKeys();
- if (resultSet.next()) {
- result = resultSet.getLong(1);
- }
- resultSet.close();
- return result;
+ return fetchGeneratedKey(insert.getGeneratedKeys());
} catch (SQLException e) {
throw new android.database.SQLException(e.getLocalizedMessage());
}
diff --git a/src/main/java/org/robolectric/shadows/ShadowSQLiteStatement.java b/src/main/java/org/robolectric/shadows/ShadowSQLiteStatement.java
index 8f4206b..d0a1dac 100644
--- a/src/main/java/org/robolectric/shadows/ShadowSQLiteStatement.java
+++ b/src/main/java/org/robolectric/shadows/ShadowSQLiteStatement.java
@@ -5,6 +5,7 @@
import android.database.sqlite.SQLiteStatement;
import org.robolectric.internal.Implementation;
import org.robolectric.internal.Implements;
+import org.robolectric.util.SQLite;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -34,13 +35,7 @@
public long executeInsert() {
try {
actualDBstatement.executeUpdate();
- ResultSet resultSet = actualDBstatement.getGeneratedKeys();
-
- if (resultSet.next()) {
- return resultSet.getLong(1);
- } else {
- throw new RuntimeException("Could not retrive generatedKeys");
- }
+ return SQLite.fetchGeneratedKey(actualDBstatement.getGeneratedKeys());
} catch (SQLException e) {
throw new RuntimeException(e);
}
diff --git a/src/main/java/org/robolectric/util/SQLite.java b/src/main/java/org/robolectric/util/SQLite.java
index 19b0298..62bfcc0 100644
--- a/src/main/java/org/robolectric/util/SQLite.java
+++ b/src/main/java/org/robolectric/util/SQLite.java
@@ -3,6 +3,7 @@
import android.content.ContentValues;
import android.database.sqlite.SQLiteException;
+import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
@@ -190,6 +191,21 @@
}
/**
+ * Fetches generated key from {@link ResultSet resultSet} and immediately closes him.
+ * @param resultSet resultSet returned from {@link java.sql.Statement#getGeneratedKeys()}
+ * @return fetched key, <code>-1</code> otherwise
+ * @throws SQLException if {@link java.sql.ResultSet#next()} or {@link java.sql.ResultSet#close()}
+ * will throw an exception
+ */
+ public static long fetchGeneratedKey(ResultSet resultSet) throws SQLException {
+ try {
+ return resultSet.next() ? resultSet.getLong(1) : -1;
+ } finally {
+ resultSet.close();
+ }
+ }
+
+ /**
* Container for a SQL fragment and the objects which are to be
* bound to the arguments in the fragment.
*/
diff --git a/src/test/java/org/robolectric/shadows/SQLiteStatementTest.java b/src/test/java/org/robolectric/shadows/SQLiteStatementTest.java
index a028f11..022d0e2 100644
--- a/src/test/java/org/robolectric/shadows/SQLiteStatementTest.java
+++ b/src/test/java/org/robolectric/shadows/SQLiteStatementTest.java
@@ -66,6 +66,34 @@
}
@Test
+ public void testExecuteInsertShouldCloseGeneratedKeysResultSet() throws Exception {
+
+
+ //
+ // NOTE:
+ // As a side-effect we will get "database locked" exception
+ // on rollback when generatedKeys isn't closed
+ //
+ // Don't know how suitable to use Mockito here, but
+ // it will be a little bit simpler to test ShadowSQLiteStatement
+ // if actualDBStatement become mockable
+ //
+
+ database.beginTransaction();
+ try {
+ SQLiteStatement insertStatement = database.compileStatement("INSERT INTO `routine` " +
+ "(`name` ,`lastUsed`) VALUES ('test',0)");
+ try {
+ insertStatement.executeInsert();
+ } finally {
+ insertStatement.close();
+ }
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ @Test
public void testExecuteUpdateDelete() throws Exception {
SQLiteStatement insertStatement = database.compileStatement("INSERT INTO `routine` (`name`) VALUES (?)");