Expose SQLite deferred transactions as public API
Bug: 274020993
New public APIs are added to SQLiteDatabase:
beginTransactionDeferred() and beginTransactionWithListenerDeferred().
The listener parameter is nullable, so
beginTransactionWithListenerDeferred(nul) is strictly identical to
beginTransactionDeferred().
To maintain symmetry, existing beginTransactionWithListener*() APIs
have their listener parameter also marked nullable. See the Anroid
API guidelines for this situation:
go/androidx-api-guidelines#extending-apis-that-are-missing-annotations
A CTS test will be added in a follow-on commit.
Test: atest
* SQLiteDatabaseTest (from CtsDatabaseTestCases)
Change-Id: Icc94bf5bb058936cfa32d92a91379ddad3cea4b4
diff --git a/core/api/current.txt b/core/api/current.txt
index 3b34f3a..393576b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14244,9 +14244,11 @@
public final class SQLiteDatabase extends android.database.sqlite.SQLiteClosable {
method public void beginTransaction();
+ method public void beginTransactionDeferred();
method public void beginTransactionNonExclusive();
- method public void beginTransactionWithListener(android.database.sqlite.SQLiteTransactionListener);
- method public void beginTransactionWithListenerNonExclusive(android.database.sqlite.SQLiteTransactionListener);
+ method public void beginTransactionWithListener(@Nullable android.database.sqlite.SQLiteTransactionListener);
+ method public void beginTransactionWithListenerDeferred(@Nullable android.database.sqlite.SQLiteTransactionListener);
+ method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener);
method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException;
method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams);
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index c08294f..87fc8c4 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -675,6 +675,30 @@
}
/**
+ * Begins a transaction in DEFERRED mode.
+ * <p>
+ * Transactions can be nested. When the outer transaction is ended all of the work done in
+ * that transaction and all of the nested transactions will be committed or rolled back. The
+ * changes will be rolled back if any transaction is ended without being marked as clean (by
+ * calling setTransactionSuccessful). Otherwise they will be committed.
+ * <p>
+ * Here is the standard idiom for transactions:
+ *
+ * <pre>
+ * db.beginTransactionDeferred();
+ * try {
+ * ...
+ * db.setTransactionSuccessful();
+ * } finally {
+ * db.endTransaction();
+ * }
+ * </pre>
+ */
+ public void beginTransactionDeferred() {
+ beginTransactionWithListenerDeferred(null);
+ }
+
+ /**
* Begins a transaction in EXCLUSIVE mode.
* <p>
* Transactions can be nested.
@@ -699,7 +723,8 @@
* commits, or is rolled back, either explicitly or by a call to
* {@link #yieldIfContendedSafely}.
*/
- public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
+ public void beginTransactionWithListener(
+ @Nullable SQLiteTransactionListener transactionListener) {
beginTransaction(transactionListener, true);
}
@@ -728,19 +753,53 @@
* explicitly or by a call to {@link #yieldIfContendedSafely}.
*/
public void beginTransactionWithListenerNonExclusive(
- SQLiteTransactionListener transactionListener) {
+ @Nullable SQLiteTransactionListener transactionListener) {
beginTransaction(transactionListener, false);
}
+ /**
+ * Begins a transaction in DEFERRED mode.
+ * <p>
+ * Transactions can be nested. When the outer transaction is ended all of the work done in
+ * that transaction and all of the nested transactions will be committed or rolled back. The
+ * changes will be rolled back if any transaction is ended without being marked as clean (by
+ * calling setTransactionSuccessful). Otherwise they will be committed.
+ * <p>
+ * Here is the standard idiom for transactions:
+ *
+ * <pre>
+ * db.beginTransactionDeferred();
+ * try {
+ * ...
+ * db.setTransactionSuccessful();
+ * } finally {
+ * db.endTransaction();
+ * }
+ * </pre>
+ */
+ public void beginTransactionWithListenerDeferred(
+ @Nullable SQLiteTransactionListener transactionListener) {
+ beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_DEFERRED);
+ }
+
@UnsupportedAppUsage
private void beginTransaction(SQLiteTransactionListener transactionListener,
boolean exclusive) {
+ beginTransaction(transactionListener,
+ exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE :
+ SQLiteSession.TRANSACTION_MODE_IMMEDIATE);
+ }
+
+ /**
+ * Begin a transaction with the specified mode. Valid modes are
+ * {@link SquLiteSession.TRANSACTION_MODE_DEFERRED},
+ * {@link SquLiteSession.TRANSACTION_MODE_IMMEDIATE}, and
+ * {@link SquLiteSession.TRANSACTION_MODE_EXCLUSIVE}.
+ */
+ private void beginTransaction(@Nullable SQLiteTransactionListener listener, int mode) {
acquireReference();
try {
- getThreadSession().beginTransaction(
- exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE :
- SQLiteSession.TRANSACTION_MODE_IMMEDIATE,
- transactionListener,
+ getThreadSession().beginTransaction(mode, listener,
getThreadDefaultConnectionFlags(false /*readOnly*/), null);
} finally {
releaseReference();
@@ -3113,4 +3172,3 @@
ContentResolver.onDbCorruption(tag, message, stacktrace);
}
}
-
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index 24b62b8..8811dd4 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -325,7 +325,12 @@
mConnection.execute("BEGIN EXCLUSIVE;", null,
cancellationSignal); // might throw
break;
+ case TRANSACTION_MODE_DEFERRED:
+ mConnection.execute("BEGIN DEFERRED;", null,
+ cancellationSignal); // might throw
+ break;
default:
+ // Per SQLite documentation, this executes in DEFERRED mode.
mConnection.execute("BEGIN;", null, cancellationSignal); // might throw
break;
}