Merge "Do not start a nested transaction on blocking DAO writes" into androidx-main
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InTransactionTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InTransactionTest.kt
index 112ff24..6bf6a0e 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InTransactionTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InTransactionTest.kt
@@ -16,6 +16,7 @@
package androidx.room.integration.kotlintestapp.test
+import android.database.sqlite.SQLiteTransactionListener
import androidx.kruth.assertThat
import androidx.room.Room
import androidx.room.RoomDatabase
@@ -23,6 +24,7 @@
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
+import kotlin.test.fail
import org.junit.Test
@SmallTest
@@ -46,4 +48,64 @@
assertThat(database.inTransaction()).isFalse()
assertThat(onOpenCalled).isEqualTo(0)
}
+
+ @Test
+ fun beginTransactionWithListener_commit() {
+ val roomDb =
+ Room.inMemoryDatabaseBuilder<TestDatabase>(ApplicationProvider.getApplicationContext())
+ .build()
+ val supportDb = roomDb.openHelper.writableDatabase
+ supportDb.beginTransactionWithListenerNonExclusive(
+ object : SQLiteTransactionListener {
+ override fun onBegin() {
+ // TODO(b/408279360): Assert this case once fixed in framework.
+ // We do not assert that `inTransaction()` is true here because at the time this
+ // callback is invoked the transaction stack has not been set and the API
+ // will return false. Meanwhile starting a transaction here will fail due to
+ // the recursive invocation of beginTransaction(), i.e. the database is
+ // neither in a transaction nor once can be started at this time.
+ }
+
+ override fun onCommit() {
+ assertThat(supportDb.inTransaction()).isTrue()
+ roomDb.booksDao().insertPublisher("p1", "pub1")
+ }
+
+ override fun onRollback() {
+ fail("onRollback should not be called")
+ }
+ }
+ )
+ supportDb.setTransactionSuccessful()
+ supportDb.endTransaction()
+
+ assertThat(roomDb.booksDao().getPublishers()).isNotEmpty()
+ roomDb.close()
+ }
+
+ @Test
+ fun beginTransactionWithListener_rollback() {
+ val roomDb =
+ Room.inMemoryDatabaseBuilder<TestDatabase>(ApplicationProvider.getApplicationContext())
+ .build()
+ val supportDb = roomDb.openHelper.writableDatabase
+ supportDb.beginTransactionWithListenerNonExclusive(
+ object : SQLiteTransactionListener {
+ override fun onBegin() {}
+
+ override fun onCommit() {
+ fail("onCommit should not be called")
+ }
+
+ override fun onRollback() {
+ assertThat(supportDb.inTransaction()).isTrue()
+ roomDb.booksDao().insertPublisher("p1", "pub1")
+ }
+ }
+ )
+ supportDb.endTransaction()
+
+ assertThat(roomDb.booksDao().getPublishers()).isEmpty()
+ roomDb.close()
+ }
}
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/util/DBUtil.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/util/DBUtil.android.kt
index a3f17aa..3b7d0df 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/util/DBUtil.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/util/DBUtil.android.kt
@@ -67,6 +67,10 @@
db.assertNotMainThread()
db.assertNotSuspendingTransaction()
return runBlockingUninterruptible {
+ // If in compatibility mode and the database is already in a transaction, then do not
+ // start a nested transaction to avoid the overhead and because the SupportSQLite APIs
+ // do not support real SAVEPOINT-based nested transactions.
+ val inTransaction = !(db.inCompatibilityMode() && db.inTransaction()) && inTransaction
db.internalPerform(isReadOnly, inTransaction) { connection ->
val rawConnection = (connection as RawConnectionAccessor).rawConnection
block.invoke(rawConnection)