Fix broken Relation support in Room Paging KMP.

This CL fixes an issue in code generation in Room Paging which emerged when using @Relation with PagingSource. The solution resolves the issue by declaring a new suspend convertRows() method that can acquire a connection to perform the query.

Bug: 369136627
Test: BaseQueryTest.kt
Relnote: Revisit the newly added convertRows() method signature to be a suspend function that receives a RawRoomQuery for room-paging.
Change-Id: Ie57b558e217ce995a7d3dfe772c314aabbfcda8c
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
index 87bb342..8fd3564 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
@@ -17,11 +17,13 @@
 package androidx.room.integration.kotlintestapp.dao
 
 import androidx.lifecycle.LiveData
+import androidx.room.ColumnInfo
 import androidx.room.Dao
 import androidx.room.Delete
 import androidx.room.Insert
 import androidx.room.Query
 import androidx.room.RawQuery
+import androidx.room.Relation
 import androidx.room.RoomWarnings
 import androidx.room.Transaction
 import androidx.room.TypeConverters
@@ -474,4 +476,15 @@
     @Upsert suspend fun upsertBookSuspendReturnId(book: Book): Long
 
     @Upsert suspend fun upsertBooksSuspendReturnIds(books: List<Book>): List<Long>
+
+    @Transaction
+    @Query("SELECT * FROM Publisher")
+    fun getPagingSourceRelation(): androidx.paging.PagingSource<Int, PublisherRelation>
+
+    data class PublisherRelation(
+        val publisherId: String,
+        @ColumnInfo(defaultValue = "0") val name: String,
+        @Relation(parentColumn = "publisherId", entityColumn = "publisherId")
+        val relationEntity: Publisher
+    )
 }
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
index 1b0d047..81aefdb7c 100644
--- a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
@@ -243,6 +243,16 @@
     @Query("SELECT * FROM StringSampleEntity1")
     suspend fun getSampleManyToMany(): SampleManyAndMany
 
+    @Transaction
+    @Query("SELECT * FROM SampleEntity3")
+    fun getPagingSourceRelation(): androidx.paging.PagingSource<Int, SampleRelation>
+
+    data class SampleRelation(
+        val pk3: Long,
+        @ColumnInfo(defaultValue = "0") val data3: Long,
+        @Relation(parentColumn = "pk3", entityColumn = "pk3") val relationEntity: SampleEntity3
+    )
+
     @Query("SELECT * FROM SampleEntity")
     fun getAllIds(): androidx.paging.PagingSource<Int, SampleEntity>
 
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
index 384b238..ebc088f 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
@@ -18,9 +18,11 @@
 
 import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XTypeName
+import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XRawType
 import androidx.room.compiler.processing.XType
 import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.CommonTypeNames.LIST
 import androidx.room.ext.PagingTypeNames
 import androidx.room.parser.ParsedQuery
 import androidx.room.processor.Context
@@ -41,6 +43,16 @@
         context.processingEnv.findType(pagingSourceTypeName.canonicalName)?.rawType
     }
 
+    private val roomPagingSourceTypeElement by lazy {
+        context.processingEnv.requireTypeElement(roomPagingClassName)
+    }
+
+    private val convertExecutableElement by lazy {
+        roomPagingSourceTypeElement.getDeclaredMethods().first {
+            it.isSuspendFunction() && it.name == "convertRows"
+        }
+    }
+
     override fun provide(
         declared: XType,
         query: ParsedQuery,
@@ -58,11 +70,24 @@
             ((listAdapter?.accessedTableNames() ?: emptyList()) + query.tables.map { it.name })
                 .toSet()
 
+        val convertRowsOverrideInfo =
+            if (pagingSourceTypeName == PagingTypeNames.PAGING_SOURCE) {
+                ConvertRowsOverrideInfo(
+                    method = convertExecutableElement,
+                    continuationParamName = convertExecutableElement.parameters.last().name,
+                    owner =
+                        context.processingEnv.getDeclaredType(roomPagingSourceTypeElement, typeArg),
+                    returnTypeName = LIST.parametrizedBy(typeArg.asTypeName())
+                )
+            } else {
+                null
+            }
+
         return MultiTypedPagingSourceQueryResultBinder(
             listAdapter = listAdapter,
             tableNames = tableNames,
             className = roomPagingClassName,
-            isBasePagingSource = pagingSourceTypeName == PagingTypeNames.PAGING_SOURCE
+            convertRowsOverrideInfo = convertRowsOverrideInfo
         )
     }
 
@@ -94,3 +119,11 @@
         return true
     }
 }
+
+/** Data class used to store necessary info when generating the suspending `convertRows` method. */
+class ConvertRowsOverrideInfo(
+    val continuationParamName: String,
+    val method: XMethodElement,
+    val owner: XType,
+    val returnTypeName: XTypeName
+)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultiTypedPagingSourceQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultiTypedPagingSourceQueryResultBinder.kt
index 055401a..92d90c5 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultiTypedPagingSourceQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultiTypedPagingSourceQueryResultBinder.kt
@@ -20,6 +20,7 @@
 import androidx.room.compiler.codegen.VisibilityModifier
 import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XCodeBlock
+import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
 import androidx.room.compiler.codegen.XFunSpec
 import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
 import androidx.room.compiler.codegen.XPropertySpec
@@ -28,10 +29,14 @@
 import androidx.room.ext.AndroidTypeNames
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.InvokeWithLambdaParameter
 import androidx.room.ext.KotlinTypeNames
+import androidx.room.ext.LambdaSpec
+import androidx.room.ext.RoomMemberNames.DB_UTIL_PERFORM_SUSPENDING
 import androidx.room.ext.RoomTypeNames.RAW_QUERY
 import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.solver.CodeGenScope
+import androidx.room.solver.binderprovider.ConvertRowsOverrideInfo
 
 /**
  * This Binder binds queries directly to native Paging3 PagingSource (i.e.
@@ -42,14 +47,14 @@
     private val listAdapter: ListQueryResultAdapter?,
     private val tableNames: Set<String>,
     className: XClassName,
-    val isBasePagingSource: Boolean
+    val convertRowsOverrideInfo: ConvertRowsOverrideInfo?
 ) : QueryResultBinder(listAdapter) {
 
     private val itemTypeName: XTypeName =
         listAdapter?.rowAdapters?.firstOrNull()?.out?.asTypeName() ?: XTypeName.ANY_OBJECT
     private val pagingSourceTypeName: XTypeName = className.parametrizedBy(itemTypeName)
 
-    override fun isMigratedToDriver(): Boolean = isBasePagingSource
+    override fun isMigratedToDriver(): Boolean = convertRowsOverrideInfo != null
 
     override fun convertAndReturn(
         roomSQLiteQueryVar: String,
@@ -71,12 +76,26 @@
                     .apply {
                         superclass(pagingSourceTypeName)
                         addFunction(
-                            createConvertRowsMethod(
-                                scope = scope,
-                                stmtParamName = "cursor",
-                                stmtParamTypeName = AndroidTypeNames.CURSOR,
-                                rawQueryParamName = null
-                            )
+                            XFunSpec.builder(
+                                    language = scope.language,
+                                    name = "convertRows",
+                                    visibility = VisibilityModifier.PROTECTED,
+                                    isOverride = true
+                                )
+                                .apply {
+                                    val rowsScope = scope.fork()
+                                    val cursorParamName = "cursor"
+                                    val resultVar = scope.getTmpVar("_result")
+                                    returns(CommonTypeNames.LIST.parametrizedBy(itemTypeName))
+                                    addParameter(
+                                        typeName = AndroidTypeNames.CURSOR,
+                                        name = cursorParamName
+                                    )
+                                    listAdapter?.convert(resultVar, cursorParamName, rowsScope)
+                                    addCode(rowsScope.generate())
+                                    addStatement("return %L", resultVar)
+                                }
+                                .build(),
                         )
                     }
                     .build()
@@ -92,7 +111,7 @@
         inTransaction: Boolean,
         scope: CodeGenScope
     ) {
-        check(isBasePagingSource) {
+        checkNotNull(convertRowsOverrideInfo) {
             "This version of `convertAndReturn` should only be called when the binder is for the " +
                 "base PagingSource. "
         }
@@ -130,13 +149,25 @@
                             sqlQueryVar
                         )
                     }
-                scope.builder.addLocalVariable(
-                    name = rawQueryVarName,
-                    typeName = RAW_QUERY,
-                    assignExpr = assignExpr
-                )
+                scope.builder.apply {
+                    addLocalVariable(
+                        name = rawQueryVarName,
+                        typeName = RAW_QUERY,
+                        assignExpr = assignExpr
+                    )
+                    addStatement(
+                        "return %L",
+                        createPagingSourceSpec(
+                            scope = scope,
+                            rawQueryVarName = rawQueryVarName,
+                            dbProperty = dbProperty,
+                            stmtVarName = stmtVarName,
+                            inTransaction = inTransaction
+                        )
+                    )
+                }
             }
-            CodeLanguage.KOTLIN ->
+            CodeLanguage.KOTLIN -> {
                 scope.builder.apply {
                     if (bindStatement != null) {
                         beginControlFlow(
@@ -162,69 +193,117 @@
                                 )
                         )
                     }
-                }
-        }
-
-        scope.builder.apply {
-            val tableNamesList = tableNames.joinToString(", ") { "\"$it\"" }
-            val statementParamName = "statement"
-            val pagingSourceSpec =
-                XTypeSpec.anonymousClassBuilder(
-                        language = language,
-                        argsFormat = "%L, %N, %L",
-                        rawQueryVarName,
-                        dbProperty,
-                        tableNamesList
-                    )
-                    .apply {
-                        superclass(pagingSourceTypeName)
-                        addFunction(
-                            createConvertRowsMethod(
-                                scope = scope,
-                                stmtParamName = statementParamName,
-                                stmtParamTypeName = SQLiteDriverTypeNames.STATEMENT,
-                                rawQueryParamName = rawQueryVarName
-                            )
+                    addStatement(
+                        "return %L",
+                        createPagingSourceSpec(
+                            scope = scope,
+                            rawQueryVarName = rawQueryVarName,
+                            stmtVarName = stmtVarName,
+                            dbProperty = dbProperty,
+                            inTransaction = false
                         )
-                    }
-                    .build()
-            addStatement("return %L", pagingSourceSpec)
+                    )
+                }
+            }
         }
     }
 
+    private fun XCodeBlock.Builder.createPagingSourceSpec(
+        scope: CodeGenScope,
+        rawQueryVarName: String,
+        stmtVarName: String,
+        dbProperty: XPropertySpec,
+        inTransaction: Boolean
+    ): XTypeSpec {
+        val tableNamesList = tableNames.joinToString(", ") { "\"$it\"" }
+        return XTypeSpec.anonymousClassBuilder(
+                language = language,
+                argsFormat = "%L, %N, %L",
+                rawQueryVarName,
+                dbProperty,
+                tableNamesList
+            )
+            .apply {
+                superclass(pagingSourceTypeName)
+                addFunction(
+                    createConvertRowsMethod(
+                        scope = scope,
+                        dbProperty = dbProperty,
+                        stmtVarName = stmtVarName,
+                        inTransaction = inTransaction
+                    )
+                )
+            }
+            .build()
+    }
+
     private fun createConvertRowsMethod(
         scope: CodeGenScope,
-        stmtParamName: String,
-        stmtParamTypeName: XTypeName,
-        rawQueryParamName: String?
+        dbProperty: XPropertySpec,
+        stmtVarName: String,
+        inTransaction: Boolean
     ): XFunSpec {
-        return XFunSpec.builder(
+        val resultVar = scope.getTmpVar("_result")
+        checkNotNull(convertRowsOverrideInfo)
+        return XFunSpec.overridingBuilder(
                 language = scope.language,
-                name = "convertRows",
-                visibility = VisibilityModifier.PROTECTED,
-                isOverride = true
+                element = convertRowsOverrideInfo.method,
+                owner = convertRowsOverrideInfo.owner
             )
             .apply {
-                returns(CommonTypeNames.LIST.parametrizedBy(itemTypeName))
-                addParameter(typeName = stmtParamTypeName, name = stmtParamName)
-                if (stmtParamTypeName == SQLiteDriverTypeNames.STATEMENT) {
-                    // The SQLiteStatement version requires a second parameter for backwards
-                    // compatibility for delegating to CursorSQLiteStatement.
-                    addParameter(typeName = XTypeName.PRIMITIVE_INT, name = "itemCount")
-                }
-                val resultVar = scope.getTmpVar("_result")
+                val limitRawQueryParamName = "limitOffsetQuery"
                 val rowsScope = scope.fork()
-                if (stmtParamTypeName == SQLiteDriverTypeNames.STATEMENT) {
-                    checkNotNull(rawQueryParamName)
-                    addStatement(
-                        "%L.getBindingFunction().invoke(%L)",
-                        rawQueryParamName,
-                        stmtParamName,
+                val connectionVar = scope.getTmpVar("_connection")
+                val performBlock =
+                    InvokeWithLambdaParameter(
+                        scope = scope,
+                        functionName = DB_UTIL_PERFORM_SUSPENDING,
+                        argFormat = listOf("%N", "%L", "%L"),
+                        args = listOf(dbProperty, /* isReadOnly= */ true, inTransaction),
+                        continuationParamName = convertRowsOverrideInfo.continuationParamName,
+                        lambdaSpec =
+                            object :
+                                LambdaSpec(
+                                    parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+                                    parameterName = connectionVar,
+                                    returnTypeName = convertRowsOverrideInfo.returnTypeName,
+                                    javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
+                                ) {
+                                override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+                                    val returnPrefix =
+                                        when (language) {
+                                            CodeLanguage.JAVA -> "return "
+                                            CodeLanguage.KOTLIN -> ""
+                                        }
+                                    val getSql =
+                                        when (language) {
+                                            CodeLanguage.JAVA -> "getSql()"
+                                            CodeLanguage.KOTLIN -> "sql"
+                                        }
+                                    addLocalVal(
+                                        stmtVarName,
+                                        SQLiteDriverTypeNames.STATEMENT,
+                                        "%L.prepare(%L.$getSql)",
+                                        connectionVar,
+                                        limitRawQueryParamName
+                                    )
+                                    addStatement(
+                                        "%L.getBindingFunction().invoke(%L)",
+                                        limitRawQueryParamName,
+                                        stmtVarName
+                                    )
+                                    beginControlFlow("try").apply {
+                                        listAdapter?.convert(resultVar, stmtVarName, scope)
+                                        addStatement("$returnPrefix%L", resultVar)
+                                    }
+                                    nextControlFlow("finally")
+                                    addStatement("%L.close()", stmtVarName)
+                                    endControlFlow()
+                                }
+                            }
                     )
-                }
-                listAdapter?.convert(resultVar, stmtParamName, rowsScope)
+                rowsScope.builder.add("return %L", performBlock)
                 addCode(rowsScope.generate())
-                addStatement("return %L", resultVar)
             }
             .build()
     }
diff --git a/room/room-compiler/src/test/test-data/common/input/LimitOffsetPagingSource.kt b/room/room-compiler/src/test/test-data/common/input/LimitOffsetPagingSource.kt
index a9921a2..cf90bd9 100644
--- a/room/room-compiler/src/test/test-data/common/input/LimitOffsetPagingSource.kt
+++ b/room/room-compiler/src/test/test-data/common/input/LimitOffsetPagingSource.kt
@@ -18,7 +18,7 @@
 import androidx.paging.PagingState
 import androidx.room.RoomDatabase
 import androidx.room.RoomRawQuery
-import androidx.sqlite.SQLiteStatement
+import androidx.sqlite.SQLiteConnection
 
 @Suppress("UNUSED_PARAMETER")
 abstract class LimitOffsetPagingSource<T : Any>(
@@ -33,5 +33,5 @@
     override public suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
         return LoadResult.Invalid()
     }
-    protected abstract fun convertRows(statement: SQLiteStatement, itemCount: Int): List<T>
+    protected abstract suspend fun convertRows(limitOffsetQuery: RoomRawQuery, itemCount: Int): List<T>
 }
\ No newline at end of file
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java
index 32f9284..4af5e72 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java
@@ -16,6 +16,7 @@
 import com.google.common.util.concurrent.ListenableFuture;
 import java.lang.Class;
 import java.lang.Integer;
+import java.lang.Object;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.StringBuilder;
@@ -24,6 +25,7 @@
 import java.util.Collections;
 import java.util.List;
 import javax.annotation.processing.Generated;
+import kotlin.coroutines.Continuation;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation", "removal"})
@@ -634,41 +636,47 @@
     final RoomRawQuery _rawQuery = new RoomRawQuery(_sql);
     return new LimitOffsetPagingSource<Child1>(_rawQuery, __db, "Child1") {
       @Override
-      @NonNull
-      protected List<Child1> convertRows(@NonNull final SQLiteStatement statement,
-          final int itemCount) {
-        _rawQuery.getBindingFunction().invoke(statement);
-        final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(statement, "id");
-        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(statement, "name");
-        final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(statement, "serial");
-        final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(statement, "code");
-        final List<Child1> _result = new ArrayList<Child1>();
-        while (statement.step()) {
-          final Child1 _item;
-          final int _tmpId;
-          _tmpId = (int) (statement.getLong(_cursorIndexOfId));
-          final String _tmpName;
-          if (statement.isNull(_cursorIndexOfName)) {
-            _tmpName = null;
-          } else {
-            _tmpName = statement.getText(_cursorIndexOfName);
-          }
-          final Info _tmpInfo;
-          if (!(statement.isNull(_cursorIndexOfSerial) && statement.isNull(_cursorIndexOfCode))) {
-            _tmpInfo = new Info();
-            _tmpInfo.serial = (int) (statement.getLong(_cursorIndexOfSerial));
-            if (statement.isNull(_cursorIndexOfCode)) {
-              _tmpInfo.code = null;
-            } else {
-              _tmpInfo.code = statement.getText(_cursorIndexOfCode);
+      protected Object convertRows(final RoomRawQuery limitOffsetQuery, final int itemCount,
+          final Continuation<? super List<? extends Child1>> arg2) {
+        return DBUtil.performSuspending(__db, true, false, (_connection) -> {
+          final SQLiteStatement _stmt = _connection.prepare(limitOffsetQuery.getSql());
+          limitOffsetQuery.getBindingFunction().invoke(_stmt);
+          try {
+            final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
+            final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+            final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
+            final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
+            final List<Child1> _result = new ArrayList<Child1>();
+            while (_stmt.step()) {
+              final Child1 _item;
+              final int _tmpId;
+              _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
+              final String _tmpName;
+              if (_stmt.isNull(_cursorIndexOfName)) {
+                _tmpName = null;
+              } else {
+                _tmpName = _stmt.getText(_cursorIndexOfName);
+              }
+              final Info _tmpInfo;
+              if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
+                _tmpInfo = new Info();
+                _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
+                if (_stmt.isNull(_cursorIndexOfCode)) {
+                  _tmpInfo.code = null;
+                } else {
+                  _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
+                }
+              } else {
+                _tmpInfo = null;
+              }
+              _item = new Child1(_tmpId,_tmpName,_tmpInfo);
+              _result.add(_item);
             }
-          } else {
-            _tmpInfo = null;
+            return _result;
+          } finally {
+            _stmt.close();
           }
-          _item = new Child1(_tmpId,_tmpName,_tmpInfo);
-          _result.add(_item);
-        }
-        return _result;
+        }, arg2);
       }
     };
   }
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java
index 6d31a43..e924379 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java
@@ -19,6 +19,7 @@
 import java.lang.Boolean;
 import java.lang.Class;
 import java.lang.Integer;
+import java.lang.Object;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.StringBuilder;
@@ -27,6 +28,7 @@
 import java.util.Collections;
 import java.util.List;
 import javax.annotation.processing.Generated;
+import kotlin.coroutines.Continuation;
 import kotlin.jvm.functions.Function1;
 
 @Generated("androidx.room.RoomProcessor")
@@ -697,41 +699,51 @@
     final RoomRawQuery _rawQuery = new RoomRawQuery(_sql);
     return new LimitOffsetPagingSource<Child1>(_rawQuery, __db, "Child1") {
       @Override
-      @NonNull
-      protected List<Child1> convertRows(@NonNull final SQLiteStatement statement,
-          final int itemCount) {
-        _rawQuery.getBindingFunction().invoke(statement);
-        final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(statement, "id");
-        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(statement, "name");
-        final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(statement, "serial");
-        final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(statement, "code");
-        final List<Child1> _result = new ArrayList<Child1>();
-        while (statement.step()) {
-          final Child1 _item;
-          final int _tmpId;
-          _tmpId = (int) (statement.getLong(_cursorIndexOfId));
-          final String _tmpName;
-          if (statement.isNull(_cursorIndexOfName)) {
-            _tmpName = null;
-          } else {
-            _tmpName = statement.getText(_cursorIndexOfName);
-          }
-          final Info _tmpInfo;
-          if (!(statement.isNull(_cursorIndexOfSerial) && statement.isNull(_cursorIndexOfCode))) {
-            _tmpInfo = new Info();
-            _tmpInfo.serial = (int) (statement.getLong(_cursorIndexOfSerial));
-            if (statement.isNull(_cursorIndexOfCode)) {
-              _tmpInfo.code = null;
-            } else {
-              _tmpInfo.code = statement.getText(_cursorIndexOfCode);
+      protected Object convertRows(final RoomRawQuery limitOffsetQuery, final int itemCount,
+          final Continuation<? super List<? extends Child1>> arg2) {
+        return DBUtil.performSuspending(__db, true, false, new Function1<SQLiteConnection, List<Child1>>() {
+          @Override
+          @NonNull
+          public List<Child1> invoke(@NonNull final SQLiteConnection _connection) {
+            final SQLiteStatement _stmt = _connection.prepare(limitOffsetQuery.getSql());
+            limitOffsetQuery.getBindingFunction().invoke(_stmt);
+            try {
+              final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
+              final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+              final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
+              final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
+              final List<Child1> _result = new ArrayList<Child1>();
+              while (_stmt.step()) {
+                final Child1 _item;
+                final int _tmpId;
+                _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
+                final String _tmpName;
+                if (_stmt.isNull(_cursorIndexOfName)) {
+                  _tmpName = null;
+                } else {
+                  _tmpName = _stmt.getText(_cursorIndexOfName);
+                }
+                final Info _tmpInfo;
+                if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
+                  _tmpInfo = new Info();
+                  _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
+                  if (_stmt.isNull(_cursorIndexOfCode)) {
+                    _tmpInfo.code = null;
+                  } else {
+                    _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
+                  }
+                } else {
+                  _tmpInfo = null;
+                }
+                _item = new Child1(_tmpId,_tmpName,_tmpInfo);
+                _result.add(_item);
+              }
+              return _result;
+            } finally {
+              _stmt.close();
             }
-          } else {
-            _tmpInfo = null;
           }
-          _item = new Child1(_tmpId,_tmpName,_tmpInfo);
-          _result.add(_item);
-        }
-        return _result;
+        }, arg2);
       }
     };
   }
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
index 41bd29f..fafe000 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
@@ -16,6 +16,7 @@
 import com.google.common.util.concurrent.ListenableFuture;
 import java.lang.Class;
 import java.lang.Integer;
+import java.lang.Object;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.StringBuilder;
@@ -24,6 +25,7 @@
 import java.util.Collections;
 import java.util.List;
 import javax.annotation.processing.Generated;
+import kotlin.coroutines.Continuation;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation", "removal"})
@@ -630,41 +632,47 @@
     final RoomRawQuery _rawQuery = new RoomRawQuery(_sql);
     return new LimitOffsetPagingSource<Child1>(_rawQuery, __db, "Child1") {
       @Override
-      @NonNull
-      protected List<Child1> convertRows(@NonNull final SQLiteStatement statement,
-          final int itemCount) {
-        _rawQuery.getBindingFunction().invoke(statement);
-        final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(statement, "id");
-        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(statement, "name");
-        final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(statement, "serial");
-        final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(statement, "code");
-        final List<Child1> _result = new ArrayList<Child1>();
-        while (statement.step()) {
-          final Child1 _item;
-          final int _tmpId;
-          _tmpId = (int) (statement.getLong(_cursorIndexOfId));
-          final String _tmpName;
-          if (statement.isNull(_cursorIndexOfName)) {
-            _tmpName = null;
-          } else {
-            _tmpName = statement.getText(_cursorIndexOfName);
-          }
-          final Info _tmpInfo;
-          if (!(statement.isNull(_cursorIndexOfSerial) && statement.isNull(_cursorIndexOfCode))) {
-            _tmpInfo = new Info();
-            _tmpInfo.serial = (int) (statement.getLong(_cursorIndexOfSerial));
-            if (statement.isNull(_cursorIndexOfCode)) {
-              _tmpInfo.code = null;
-            } else {
-              _tmpInfo.code = statement.getText(_cursorIndexOfCode);
+      protected Object convertRows(final RoomRawQuery limitOffsetQuery, final int itemCount,
+          final Continuation<? super List<? extends Child1>> $completion) {
+        return DBUtil.performSuspending(__db, true, false, (_connection) -> {
+          final SQLiteStatement _stmt = _connection.prepare(limitOffsetQuery.getSql());
+          limitOffsetQuery.getBindingFunction().invoke(_stmt);
+          try {
+            final int _cursorIndexOfId = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "id");
+            final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+            final int _cursorIndexOfSerial = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "serial");
+            final int _cursorIndexOfCode = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "code");
+            final List<Child1> _result = new ArrayList<Child1>();
+            while (_stmt.step()) {
+              final Child1 _item;
+              final int _tmpId;
+              _tmpId = (int) (_stmt.getLong(_cursorIndexOfId));
+              final String _tmpName;
+              if (_stmt.isNull(_cursorIndexOfName)) {
+                _tmpName = null;
+              } else {
+                _tmpName = _stmt.getText(_cursorIndexOfName);
+              }
+              final Info _tmpInfo;
+              if (!(_stmt.isNull(_cursorIndexOfSerial) && _stmt.isNull(_cursorIndexOfCode))) {
+                _tmpInfo = new Info();
+                _tmpInfo.serial = (int) (_stmt.getLong(_cursorIndexOfSerial));
+                if (_stmt.isNull(_cursorIndexOfCode)) {
+                  _tmpInfo.code = null;
+                } else {
+                  _tmpInfo.code = _stmt.getText(_cursorIndexOfCode);
+                }
+              } else {
+                _tmpInfo = null;
+              }
+              _item = new Child1(_tmpId,_tmpName,_tmpInfo);
+              _result.add(_item);
             }
-          } else {
-            _tmpInfo = null;
+            return _result;
+          } finally {
+            _stmt.close();
           }
-          _item = new Child1(_tmpId,_tmpName,_tmpInfo);
-          _result.add(_item);
-        }
-        return _result;
+        }, $completion);
       }
     };
   }
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt
index eab9d14..fb9b14c 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt
@@ -8,6 +8,7 @@
 import androidx.room.paging.LimitOffsetPagingSource
 import androidx.room.paging.guava.LimitOffsetListenableFuturePagingSource
 import androidx.room.util.getColumnIndexOrThrow
+import androidx.room.util.performSuspending
 import androidx.sqlite.SQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
@@ -37,19 +38,24 @@
     val _sql: String = "SELECT pk FROM MyEntity"
     val _rawQuery: RoomRawQuery = RoomRawQuery(_sql)
     return object : LimitOffsetPagingSource<MyEntity>(_rawQuery, __db, "MyEntity") {
-      protected override fun convertRows(statement: SQLiteStatement, itemCount: Int):
-          List<MyEntity> {
-        _rawQuery.getBindingFunction().invoke(statement)
-        val _cursorIndexOfPk: Int = 0
-        val _result: MutableList<MyEntity> = mutableListOf()
-        while (statement.step()) {
-          val _item: MyEntity
-          val _tmpPk: Int
-          _tmpPk = statement.getLong(_cursorIndexOfPk).toInt()
-          _item = MyEntity(_tmpPk)
-          _result.add(_item)
+      protected override suspend fun convertRows(limitOffsetQuery: RoomRawQuery, itemCount: Int):
+          List<MyEntity> = performSuspending(__db, true, false) { _connection ->
+        val _stmt: SQLiteStatement = _connection.prepare(limitOffsetQuery.sql)
+        limitOffsetQuery.getBindingFunction().invoke(_stmt)
+        try {
+          val _cursorIndexOfPk: Int = 0
+          val _result: MutableList<MyEntity> = mutableListOf()
+          while (_stmt.step()) {
+            val _item: MyEntity
+            val _tmpPk: Int
+            _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+            _item = MyEntity(_tmpPk)
+            _result.add(_item)
+          }
+          _result
+        } finally {
+          _stmt.close()
         }
-        return _result
       }
     }
   }
@@ -61,19 +67,24 @@
       _stmt.bindLong(_argIndex, gt)
     }
     return object : LimitOffsetPagingSource<MyEntity>(_rawQuery, __db, "MyEntity") {
-      protected override fun convertRows(statement: SQLiteStatement, itemCount: Int):
-          List<MyEntity> {
-        _rawQuery.getBindingFunction().invoke(statement)
-        val _cursorIndexOfPk: Int = getColumnIndexOrThrow(statement, "pk")
-        val _result: MutableList<MyEntity> = mutableListOf()
-        while (statement.step()) {
-          val _item: MyEntity
-          val _tmpPk: Int
-          _tmpPk = statement.getLong(_cursorIndexOfPk).toInt()
-          _item = MyEntity(_tmpPk)
-          _result.add(_item)
+      protected override suspend fun convertRows(limitOffsetQuery: RoomRawQuery, itemCount: Int):
+          List<MyEntity> = performSuspending(__db, true, false) { _connection ->
+        val _stmt: SQLiteStatement = _connection.prepare(limitOffsetQuery.sql)
+        limitOffsetQuery.getBindingFunction().invoke(_stmt)
+        try {
+          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+          val _result: MutableList<MyEntity> = mutableListOf()
+          while (_stmt.step()) {
+            val _item: MyEntity
+            val _tmpPk: Int
+            _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+            _item = MyEntity(_tmpPk)
+            _result.add(_item)
+          }
+          _result
+        } finally {
+          _stmt.close()
         }
-        return _result
       }
     }
   }
diff --git a/room/room-paging/bcv/native/current.txt b/room/room-paging/bcv/native/current.txt
index 0fc19cc..ebce7db 100644
--- a/room/room-paging/bcv/native/current.txt
+++ b/room/room-paging/bcv/native/current.txt
@@ -18,8 +18,8 @@
     open val jumpingSupported // androidx.room.paging/LimitOffsetPagingSource.jumpingSupported|{}jumpingSupported[0]
         open fun <get-jumpingSupported>(): kotlin/Boolean // androidx.room.paging/LimitOffsetPagingSource.jumpingSupported.<get-jumpingSupported>|<get-jumpingSupported>(){}[0]
 
-    open fun convertRows(androidx.sqlite/SQLiteStatement, kotlin/Int): kotlin.collections/List<#A> // androidx.room.paging/LimitOffsetPagingSource.convertRows|convertRows(androidx.sqlite.SQLiteStatement;kotlin.Int){}[0]
     open fun getRefreshKey(androidx.paging/PagingState<kotlin/Int, #A>): kotlin/Int? // androidx.room.paging/LimitOffsetPagingSource.getRefreshKey|getRefreshKey(androidx.paging.PagingState<kotlin.Int,1:0>){}[0]
+    open suspend fun convertRows(androidx.room/RoomRawQuery, kotlin/Int): kotlin.collections/List<#A> // androidx.room.paging/LimitOffsetPagingSource.convertRows|convertRows(androidx.room.RoomRawQuery;kotlin.Int){}[0]
     open suspend fun load(androidx.paging/PagingSource.LoadParams<kotlin/Int>): androidx.paging/PagingSource.LoadResult<kotlin/Int, #A> // androidx.room.paging/LimitOffsetPagingSource.load|load(androidx.paging.PagingSource.LoadParams<kotlin.Int>){}[0]
 }
 
@@ -29,5 +29,5 @@
 final fun <#A: kotlin/Any> (androidx.paging/PagingState<kotlin/Int, #A>).androidx.room.paging.util/getClippedRefreshKey(): kotlin/Int? // androidx.room.paging.util/getClippedRefreshKey|[email protected]<kotlin.Int,0:0>(){0§<kotlin.Any>}[0]
 final fun androidx.room.paging.util/getLimit(androidx.paging/PagingSource.LoadParams<kotlin/Int>, kotlin/Int): kotlin/Int // androidx.room.paging.util/getLimit|getLimit(androidx.paging.PagingSource.LoadParams<kotlin.Int>;kotlin.Int){}[0]
 final fun androidx.room.paging.util/getOffset(androidx.paging/PagingSource.LoadParams<kotlin/Int>, kotlin/Int, kotlin/Int): kotlin/Int // androidx.room.paging.util/getOffset|getOffset(androidx.paging.PagingSource.LoadParams<kotlin.Int>;kotlin.Int;kotlin.Int){}[0]
-final suspend fun <#A: kotlin/Any> androidx.room.paging.util/queryDatabase(androidx.paging/PagingSource.LoadParams<kotlin/Int>, androidx.room/RoomRawQuery, androidx.room/RoomDatabase, kotlin/Int, kotlin/Function2<androidx.sqlite/SQLiteStatement, kotlin/Int, kotlin.collections/List<#A>>): androidx.paging/PagingSource.LoadResult<kotlin/Int, #A> // androidx.room.paging.util/queryDatabase|queryDatabase(androidx.paging.PagingSource.LoadParams<kotlin.Int>;androidx.room.RoomRawQuery;androidx.room.RoomDatabase;kotlin.Int;kotlin.Function2<androidx.sqlite.SQLiteStatement,kotlin.Int,kotlin.collections.List<0:0>>){0§<kotlin.Any>}[0]
+final suspend fun <#A: kotlin/Any> androidx.room.paging.util/queryDatabase(androidx.paging/PagingSource.LoadParams<kotlin/Int>, androidx.room/RoomRawQuery, kotlin/Int, kotlin.coroutines/SuspendFunction2<androidx.room/RoomRawQuery, kotlin/Int, kotlin.collections/List<#A>>): androidx.paging/PagingSource.LoadResult<kotlin/Int, #A> // androidx.room.paging.util/queryDatabase|queryDatabase(androidx.paging.PagingSource.LoadParams<kotlin.Int>;androidx.room.RoomRawQuery;kotlin.Int;kotlin.coroutines.SuspendFunction2<androidx.room.RoomRawQuery,kotlin.Int,kotlin.collections.List<0:0>>){0§<kotlin.Any>}[0]
 final suspend fun androidx.room.paging.util/queryItemCount(androidx.room/RoomRawQuery, androidx.room/RoomDatabase): kotlin/Int // androidx.room.paging.util/queryItemCount|queryItemCount(androidx.room.RoomRawQuery;androidx.room.RoomDatabase){}[0]
diff --git a/room/room-paging/src/androidInstrumentedTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt b/room/room-paging/src/androidInstrumentedTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt
index 9d2384b..34a717a 100644
--- a/room/room-paging/src/androidInstrumentedTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt
+++ b/room/room-paging/src/androidInstrumentedTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt
@@ -29,7 +29,8 @@
 import androidx.room.RoomRawQuery
 import androidx.room.paging.util.getClippedRefreshKey
 import androidx.room.util.getColumnIndexOrThrow
-import androidx.sqlite.SQLiteStatement
+import androidx.room.util.performSuspending
+import androidx.sqlite.use
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -774,15 +775,21 @@
         db = db,
         tables = arrayOf(tableName)
     ) {
-
-    override fun convertRows(statement: SQLiteStatement, itemCount: Int): List<TestItem> {
-        val stmtIndexOfId = getColumnIndexOrThrow(statement, "id")
-        val data = mutableListOf<TestItem>()
-        while (statement.step()) {
-            val tmpId = statement.getInt(stmtIndexOfId)
-            data.add(TestItem(tmpId))
+    override suspend fun convertRows(
+        limitOffsetQuery: RoomRawQuery,
+        itemCount: Int
+    ): List<TestItem> {
+        return performSuspending(db, isReadOnly = true, inTransaction = false) { connection ->
+            connection.prepare(limitOffsetQuery.sql).use { statement ->
+                val stmtIndexOfId = getColumnIndexOrThrow(statement, "id")
+                buildList {
+                    while (statement.step()) {
+                        val tmpId = statement.getInt(stmtIndexOfId)
+                        add(TestItem(tmpId))
+                    }
+                }
+            }
         }
-        return data
     }
 
     override fun convertRows(cursor: Cursor): List<TestItem> {
diff --git a/room/room-paging/src/androidMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.android.kt b/room/room-paging/src/androidMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.android.kt
index 0cdb47b..13120a0 100644
--- a/room/room-paging/src/androidMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.android.kt
+++ b/room/room-paging/src/androidMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.android.kt
@@ -25,8 +25,9 @@
 import androidx.room.RoomSQLiteQuery
 import androidx.room.paging.CommonLimitOffsetImpl.Companion.BUG_LINK
 import androidx.room.paging.util.getClippedRefreshKey
-import androidx.sqlite.SQLiteStatement
+import androidx.room.util.performSuspending
 import androidx.sqlite.db.SupportSQLiteQuery
+import androidx.sqlite.use
 
 /**
  * An implementation of [PagingSource] to perform a LIMIT OFFSET query
@@ -78,7 +79,15 @@
         )
     }
 
-    protected actual open fun convertRows(statement: SQLiteStatement, itemCount: Int): List<Value> {
-        return convertRows(SQLiteStatementCursor(statement, itemCount))
+    protected actual open suspend fun convertRows(
+        limitOffsetQuery: RoomRawQuery,
+        itemCount: Int
+    ): List<Value> {
+        return performSuspending(db, isReadOnly = true, inTransaction = false) { connection ->
+            connection.prepare(limitOffsetQuery.sql).use { statement ->
+                limitOffsetQuery.getBindingFunction().invoke(statement)
+                convertRows(SQLiteStatementCursor(statement, itemCount))
+            }
+        }
     }
 }
diff --git a/room/room-paging/src/commonMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt b/room/room-paging/src/commonMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt
index 7a043aa..32296b1 100644
--- a/room/room-paging/src/commonMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt
+++ b/room/room-paging/src/commonMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.kt
@@ -27,7 +27,6 @@
 import androidx.room.paging.util.queryDatabase
 import androidx.room.paging.util.queryItemCount
 import androidx.room.useReaderConnection
-import androidx.sqlite.SQLiteStatement
 import kotlinx.atomicfu.atomic
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.Job
@@ -52,13 +51,16 @@
 
     public val itemCount: Int
 
-    protected open fun convertRows(statement: SQLiteStatement, itemCount: Int): List<Value>
+    protected open suspend fun convertRows(
+        limitOffsetQuery: RoomRawQuery,
+        itemCount: Int
+    ): List<Value>
 }
 
 internal class CommonLimitOffsetImpl<Value : Any>(
     private val tables: Array<out String>,
     private val pagingSource: LimitOffsetPagingSource<Value>,
-    private val convertRows: (SQLiteStatement, Int) -> List<Value>
+    private val convertRows: suspend (RoomRawQuery, Int) -> List<Value>
 ) {
     private val db = pagingSource.db
     private val sourceQuery = pagingSource.sourceQuery
@@ -116,7 +118,6 @@
                 queryDatabase(
                     params = params,
                     sourceQuery = sourceQuery,
-                    db = db,
                     itemCount = tempCount,
                     convertRows = convertRows,
                 )
@@ -132,7 +133,6 @@
             queryDatabase(
                 params = params,
                 sourceQuery = sourceQuery,
-                db = db,
                 itemCount = tempCount,
                 convertRows = convertRows
             )
diff --git a/room/room-paging/src/commonMain/kotlin/androidx/room/paging/util/RoomPagingUtil.kt b/room/room-paging/src/commonMain/kotlin/androidx/room/paging/util/RoomPagingUtil.kt
index 64c6150..d08a210 100644
--- a/room/room-paging/src/commonMain/kotlin/androidx/room/paging/util/RoomPagingUtil.kt
+++ b/room/room-paging/src/commonMain/kotlin/androidx/room/paging/util/RoomPagingUtil.kt
@@ -29,7 +29,6 @@
 import androidx.room.RoomDatabase
 import androidx.room.RoomRawQuery
 import androidx.room.useReaderConnection
-import androidx.sqlite.SQLiteStatement
 import kotlin.jvm.JvmMultifileClass
 import kotlin.jvm.JvmName
 
@@ -110,9 +109,8 @@
 public suspend fun <Value : Any> queryDatabase(
     params: LoadParams<Int>,
     sourceQuery: RoomRawQuery,
-    db: RoomDatabase,
     itemCount: Int,
-    convertRows: (SQLiteStatement, Int) -> List<Value>
+    convertRows: suspend (RoomRawQuery, Int) -> List<Value>
 ): LoadResult<Int, Value> {
     val key = params.key ?: 0
     val limit = getLimit(params, key)
@@ -123,16 +121,13 @@
         } else {
             limit
         }
-    val limitOffsetQuery = "SELECT * FROM ( ${sourceQuery.sql} ) LIMIT $limit OFFSET $offset"
+    val limitOffsetQuery =
+        RoomRawQuery(
+            sql = "SELECT * FROM ( ${sourceQuery.sql} ) LIMIT $limit OFFSET $offset",
+            onBindStatement = sourceQuery.getBindingFunction()
+        )
 
-    val data: List<Value> =
-        db.useReaderConnection { connection ->
-            connection.usePrepared(limitOffsetQuery) { stmt ->
-                sourceQuery.getBindingFunction().invoke(stmt)
-                convertRows(stmt, rowsCount)
-            }
-        }
-
+    val data: List<Value> = convertRows(limitOffsetQuery, rowsCount)
     val nextPosToLoad = offset + data.size
     val nextKey =
         if (data.isEmpty() || data.size < limit || nextPosToLoad >= itemCount) {
diff --git a/room/room-paging/src/jvmNativeMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.jvmNative.kt b/room/room-paging/src/jvmNativeMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.jvmNative.kt
index 4094d2d..91d075a 100644
--- a/room/room-paging/src/jvmNativeMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.jvmNative.kt
+++ b/room/room-paging/src/jvmNativeMain/kotlin/androidx/room/paging/LimitOffsetPagingSource.jvmNative.kt
@@ -23,7 +23,6 @@
 import androidx.room.RoomRawQuery
 import androidx.room.paging.CommonLimitOffsetImpl.Companion.BUG_LINK
 import androidx.room.paging.util.getClippedRefreshKey
-import androidx.sqlite.SQLiteStatement
 
 /**
  * An implementation of [PagingSource] to perform a LIMIT OFFSET query
@@ -52,7 +51,10 @@
 
     override fun getRefreshKey(state: PagingState<Int, Value>): Int? = state.getClippedRefreshKey()
 
-    protected actual open fun convertRows(statement: SQLiteStatement, itemCount: Int): List<Value> {
+    protected actual open suspend fun convertRows(
+        limitOffsetQuery: RoomRawQuery,
+        itemCount: Int
+    ): List<Value> {
         throw NotImplementedError(
             "Unexpected call to a function with no implementation that Room is suppose to " +
                 "generate. Please file a bug at: $BUG_LINK."