Add new `convertRows()` methods that wrap an SQLiteStatement with a Cursor. This CL refactors LimitOffsetDataSource and LimitOffsetPagingSource for backwards compatibility by adding a new `convertRows` method in each API that allows generating code using the new SQLite driver API for Paging2 sources. Bug: 319660042 Test: DaoKotlinCodeGenTest.kt Change-Id: I526504dd4760f8ada561d6ef38b99e830f666ae6
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/paging/LimitOffsetDataSourceTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/paging/LimitOffsetDataSourceTest.java index cf95bce..94984a6 100644 --- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/paging/LimitOffsetDataSourceTest.java +++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/paging/LimitOffsetDataSourceTest.java
@@ -16,10 +16,9 @@ package androidx.room.integration.testapp.paging; -import static junit.framework.Assert.assertFalse; - import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertFalse; import androidx.annotation.NonNull; import androidx.room.integration.testapp.test.TestDatabaseTest;
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt index 07a1f63..0ce5c3f 100644 --- a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt +++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
@@ -303,6 +303,7 @@ val DB_UTIL_DROP_FTS_SYNC_TRIGGERS = RoomTypeNames.DB_UTIL.packageMember("dropFtsSyncTriggers") val DB_UTIL_PERFORM_SUSPENDING = RoomTypeNames.DB_UTIL.packageMember("performSuspending") val DB_UTIL_PERFORM_BLOCKING = RoomTypeNames.DB_UTIL.packageMember("performBlocking") + val DB_UTIL_SUPPORT_DB_TO_CONNECTION = RoomTypeNames.DB_UTIL.packageMember("toSQLiteConnection") val DB_UTIL_PERFORM_IN_TRANSACTION_SUSPENDING = RoomTypeNames.DB_UTIL.packageMember("performInTransactionSuspending") val CURSOR_UTIL_GET_COLUMN_INDEX = RoomTypeNames.CURSOR_UTIL.packageMember("getColumnIndex")
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 ebc088f..de31dcf 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
@@ -31,6 +31,7 @@ import androidx.room.solver.TypeAdapterExtras import androidx.room.solver.query.result.ListQueryResultAdapter import androidx.room.solver.query.result.MultiTypedPagingSourceQueryResultBinder +import androidx.room.solver.query.result.Paging3PagingSourceQueryResultBinder import androidx.room.solver.query.result.QueryResultBinder class MultiTypedPagingSourceQueryResultBinderProvider( @@ -70,8 +71,8 @@ ((listAdapter?.accessedTableNames() ?: emptyList()) + query.tables.map { it.name }) .toSet() - val convertRowsOverrideInfo = - if (pagingSourceTypeName == PagingTypeNames.PAGING_SOURCE) { + return if (pagingSourceTypeName == PagingTypeNames.PAGING_SOURCE) { + val convertRowsOverrideInfo = ConvertRowsOverrideInfo( method = convertExecutableElement, continuationParamName = convertExecutableElement.parameters.last().name, @@ -79,16 +80,19 @@ context.processingEnv.getDeclaredType(roomPagingSourceTypeElement, typeArg), returnTypeName = LIST.parametrizedBy(typeArg.asTypeName()) ) - } else { - null - } - - return MultiTypedPagingSourceQueryResultBinder( - listAdapter = listAdapter, - tableNames = tableNames, - className = roomPagingClassName, - convertRowsOverrideInfo = convertRowsOverrideInfo - ) + Paging3PagingSourceQueryResultBinder( + listAdapter = listAdapter, + tableNames = tableNames, + className = roomPagingClassName, + convertRowsOverrideInfo = convertRowsOverrideInfo + ) + } else { + MultiTypedPagingSourceQueryResultBinder( + listAdapter = listAdapter, + tableNames = tableNames, + className = roomPagingClassName + ) + } } override fun matches(declared: XType): Boolean {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt index 1d2de07..258d8f6 100644 --- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt +++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt
@@ -17,14 +17,19 @@ package androidx.room.solver.query.result import androidx.room.compiler.codegen.XPropertySpec +import androidx.room.compiler.codegen.XTypeName import androidx.room.ext.AndroidTypeNames import androidx.room.solver.CodeGenScope class CursorQueryResultBinder : QueryResultBinder(NO_OP_RESULT_ADAPTER) { + + override val usesCompatQueryWriter = true + override fun convertAndReturn( - roomSQLiteQueryVar: String, - canReleaseQuery: Boolean, + sqlQueryVar: String, dbProperty: XPropertySpec, + bindStatement: (CodeGenScope.(String) -> Unit)?, + returnTypeName: XTypeName, inTransaction: Boolean, scope: CodeGenScope ) { @@ -42,7 +47,7 @@ AndroidTypeNames.CURSOR, "%N.query(%L)", dbProperty, - roomSQLiteQueryVar + sqlQueryVar ) transactionWrapper?.commitTransaction() addStatement("return %L", resultName) @@ -50,6 +55,19 @@ } } + override fun convertAndReturn( + roomSQLiteQueryVar: String, + canReleaseQuery: Boolean, + dbProperty: XPropertySpec, + inTransaction: Boolean, + scope: CodeGenScope + ) { + error( + "This convertAndReturn() should never be invoked, it will be removed once " + + "migration to drivers is completed." + ) + } + companion object { private val NO_OP_RESULT_ADAPTER = object : QueryResultAdapter(emptyList()) {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/DataSourceFactoryQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/DataSourceFactoryQueryResultBinder.kt index 2204dac..89ab607 100644 --- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/DataSourceFactoryQueryResultBinder.kt +++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/DataSourceFactoryQueryResultBinder.kt
@@ -30,10 +30,15 @@ val typeName: XTypeName = positionalDataSourceQueryResultBinder.itemTypeName + override val usesCompatQueryWriter = true + + override fun isMigratedToDriver(): Boolean = true + override fun convertAndReturn( - roomSQLiteQueryVar: String, - canReleaseQuery: Boolean, + sqlQueryVar: String, dbProperty: XPropertySpec, + bindStatement: (CodeGenScope.(String) -> Unit)?, + returnTypeName: XTypeName, inTransaction: Boolean, scope: CodeGenScope ) { @@ -48,8 +53,10 @@ ) ) addCreateMethod( - roomSQLiteQueryVar = roomSQLiteQueryVar, + roomSQLiteQueryVar = sqlQueryVar, dbProperty = dbProperty, + bindStatement = bindStatement, + returnTypeName = returnTypeName, inTransaction = inTransaction, scope = scope ) @@ -59,11 +66,26 @@ } } + override fun convertAndReturn( + roomSQLiteQueryVar: String, + canReleaseQuery: Boolean, + dbProperty: XPropertySpec, + inTransaction: Boolean, + scope: CodeGenScope + ) { + error( + "This convertAndReturn() should never be invoked, it will be removed once " + + "migration to drivers is completed." + ) + } + private fun XTypeSpec.Builder.addCreateMethod( roomSQLiteQueryVar: String, dbProperty: XPropertySpec, inTransaction: Boolean, - scope: CodeGenScope + scope: CodeGenScope, + bindStatement: (CodeGenScope.(String) -> Unit)?, + returnTypeName: XTypeName ) { addFunction( XFunSpec.builder( @@ -75,9 +97,10 @@ returns(positionalDataSourceQueryResultBinder.typeName) val countedBinderScope = scope.fork() positionalDataSourceQueryResultBinder.convertAndReturn( - roomSQLiteQueryVar = roomSQLiteQueryVar, - canReleaseQuery = true, + sqlQueryVar = roomSQLiteQueryVar, dbProperty = dbProperty, + bindStatement = bindStatement, + returnTypeName = returnTypeName, inTransaction = inTransaction, scope = countedBinderScope )
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 33f05e1..b0ac1de4 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
@@ -16,44 +16,33 @@ package androidx.room.solver.query.result -import androidx.room.compiler.codegen.CodeLanguage 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.applyTo import androidx.room.compiler.codegen.XFunSpec import androidx.room.compiler.codegen.XPropertySpec import androidx.room.compiler.codegen.XTypeName import androidx.room.compiler.codegen.XTypeSpec -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. - * [androidx.room.paging.LimitOffsetPagingSource]) or its subclasses such as - * [androidx.room.paging.guava.LimitOffsetListenableFuturePagingSource]. Used solely by Paging3. + * This Binder binds queries directly to Non-KMP compatible Paging3 PagingSource Binders. Used + * solely by Non-KMP Paging3. */ class MultiTypedPagingSourceQueryResultBinder( private val listAdapter: ListQueryResultAdapter?, private val tableNames: Set<String>, - className: XClassName, - val convertRowsOverrideInfo: ConvertRowsOverrideInfo? + className: XClassName ) : 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 = convertRowsOverrideInfo != null + override val usesCompatQueryWriter = true + + override fun isMigratedToDriver(): Boolean = true override fun convertAndReturn( roomSQLiteQueryVar: String, @@ -62,12 +51,26 @@ inTransaction: Boolean, scope: CodeGenScope ) { + error( + "This convertAndReturn() should never be invoked, it will be removed once " + + "migration to drivers is completed." + ) + } + + override fun convertAndReturn( + sqlQueryVar: String, + dbProperty: XPropertySpec, + bindStatement: (CodeGenScope.(String) -> Unit)?, + returnTypeName: XTypeName, + inTransaction: Boolean, + scope: CodeGenScope + ) { scope.builder.apply { val tableNamesList = tableNames.joinToString(", ") { "\"$it\"" } val pagingSourceSpec = XTypeSpec.anonymousClassBuilder( argsFormat = "%L, %N, %L", - roomSQLiteQueryVar, + sqlQueryVar, dbProperty, tableNamesList ) @@ -81,11 +84,11 @@ ) .apply { val rowsScope = scope.fork() - val cursorParamName = "cursor" + val cursorParamName = "statement" val resultVar = scope.getTmpVar("_result") returns(CommonTypeNames.LIST.parametrizedBy(itemTypeName)) addParameter( - typeName = AndroidTypeNames.CURSOR, + typeName = SQLiteDriverTypeNames.STATEMENT, name = cursorParamName ) listAdapter?.convert(resultVar, cursorParamName, rowsScope) @@ -99,203 +102,4 @@ addStatement("return %L", pagingSourceSpec) } } - - override fun convertAndReturn( - sqlQueryVar: String, - dbProperty: XPropertySpec, - bindStatement: (CodeGenScope.(String) -> Unit)?, - returnTypeName: XTypeName, - inTransaction: Boolean, - scope: CodeGenScope - ) { - checkNotNull(convertRowsOverrideInfo) { - "This version of `convertAndReturn` should only be called when the binder is for the " + - "base PagingSource. " - } - val rawQueryVarName = scope.getTmpVar("_rawQuery") - val stmtVarName = scope.getTmpVar("_stmt") - - when (scope.language) { - CodeLanguage.JAVA -> { - val assignExpr = - if (bindStatement != null) { - XCodeBlock.ofNewInstance( - typeName = RAW_QUERY, - "%L, %L", - sqlQueryVar, - Function1TypeSpec( - parameterTypeName = SQLiteDriverTypeNames.STATEMENT, - parameterName = stmtVarName, - returnTypeName = KotlinTypeNames.UNIT - ) { - val functionScope = scope.fork() - functionScope.builder - .apply { bindStatement.invoke(functionScope, stmtVarName) } - .build() - addCode(functionScope.generate()) - addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT) - } - ) - } else { - XCodeBlock.ofNewInstance(typeName = RAW_QUERY, "%L", sqlQueryVar) - } - 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 -> { - scope.builder.apply { - if (bindStatement != null) { - beginControlFlow( - "val %L: %T = %T(%N) { %L ->", - rawQueryVarName, - RAW_QUERY, - RAW_QUERY, - sqlQueryVar, - stmtVarName - ) - bindStatement.invoke(scope, stmtVarName) - endControlFlow() - } else { - addLocalVariable( - name = rawQueryVarName, - typeName = RAW_QUERY, - assignExpr = - XCodeBlock.ofNewInstance( - typeName = RAW_QUERY, - argsFormat = "%N", - sqlQueryVar - ) - ) - } - addStatement( - "return %L", - createPagingSourceSpec( - scope = scope, - rawQueryVarName = rawQueryVarName, - stmtVarName = stmtVarName, - dbProperty = dbProperty, - inTransaction = false - ) - ) - } - } - } - } - - private fun XCodeBlock.Builder.createPagingSourceSpec( - scope: CodeGenScope, - rawQueryVarName: String, - stmtVarName: String, - dbProperty: XPropertySpec, - inTransaction: Boolean - ): XTypeSpec { - val tableNamesList = tableNames.joinToString(", ") { "\"$it\"" } - return XTypeSpec.anonymousClassBuilder( - 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, - dbProperty: XPropertySpec, - stmtVarName: String, - inTransaction: Boolean - ): XFunSpec { - val resultVar = scope.getTmpVar("_result") - checkNotNull(convertRowsOverrideInfo) - return XFunSpec.overridingBuilder( - element = convertRowsOverrideInfo.method, - owner = convertRowsOverrideInfo.owner - ) - .apply { - val limitRawQueryParamName = "limitOffsetQuery" - val rowsScope = scope.fork() - 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) { - applyTo { language -> - 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) - applyTo { language -> - val returnPrefix = - when (language) { - CodeLanguage.JAVA -> "return " - CodeLanguage.KOTLIN -> "" - } - addStatement("$returnPrefix%L", resultVar) - } - } - nextControlFlow("finally") - addStatement("%L.close()", stmtVarName) - endControlFlow() - } - } - ) - rowsScope.builder.add("return %L", performBlock) - addCode(rowsScope.generate()) - } - .build() - } }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/Paging3SourceQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/Paging3SourceQueryResultBinder.kt new file mode 100644 index 0000000..d05aa69 --- /dev/null +++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/Paging3SourceQueryResultBinder.kt
@@ -0,0 +1,260 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.room.solver.query.result + +import androidx.room.compiler.codegen.CodeLanguage +import androidx.room.compiler.codegen.XClassName +import androidx.room.compiler.codegen.XCodeBlock +import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.applyTo +import androidx.room.compiler.codegen.XFunSpec +import androidx.room.compiler.codegen.XPropertySpec +import androidx.room.compiler.codegen.XTypeName +import androidx.room.compiler.codegen.XTypeSpec +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 KMP Compatible Paging3 PagingSource (i.e. + * [androidx.room.paging.LimitOffsetPagingSource]). Used solely by KMP Paging3. + */ +class Paging3PagingSourceQueryResultBinder( + private val listAdapter: ListQueryResultAdapter?, + private val tableNames: Set<String>, + private val convertRowsOverrideInfo: ConvertRowsOverrideInfo, + className: XClassName +) : 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 = true + + override fun convertAndReturn( + roomSQLiteQueryVar: String, + canReleaseQuery: Boolean, + dbProperty: XPropertySpec, + inTransaction: Boolean, + scope: CodeGenScope + ) { + error( + "This convertAndReturn() should never be invoked, it will be removed once " + + "migration to drivers is completed." + ) + } + + override fun convertAndReturn( + sqlQueryVar: String, + dbProperty: XPropertySpec, + bindStatement: (CodeGenScope.(String) -> Unit)?, + returnTypeName: XTypeName, + inTransaction: Boolean, + scope: CodeGenScope + ) { + val rawQueryVarName = scope.getTmpVar("_rawQuery") + val stmtVarName = scope.getTmpVar("_stmt") + + when (scope.language) { + CodeLanguage.JAVA -> { + val assignExpr = + if (bindStatement != null) { + XCodeBlock.ofNewInstance( + typeName = RAW_QUERY, + "%L, %L", + sqlQueryVar, + Function1TypeSpec( + parameterTypeName = SQLiteDriverTypeNames.STATEMENT, + parameterName = stmtVarName, + returnTypeName = KotlinTypeNames.UNIT + ) { + val functionScope = scope.fork() + functionScope.builder + .apply { bindStatement.invoke(functionScope, stmtVarName) } + .build() + addCode(functionScope.generate()) + addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT) + } + ) + } else { + XCodeBlock.ofNewInstance(typeName = RAW_QUERY, "%L", sqlQueryVar) + } + scope.builder.apply { + addLocalVariable( + name = rawQueryVarName, + typeName = RAW_QUERY, + assignExpr = assignExpr + ) + addStatement( + "return %L", + createPagingSourceSpec( + scope = scope, + rawQueryVarName = rawQueryVarName, + stmtVarName = stmtVarName, + dbProperty = dbProperty, + inTransaction = inTransaction + ) + ) + } + } + CodeLanguage.KOTLIN -> { + scope.builder.apply { + if (bindStatement != null) { + beginControlFlow( + "val %L: %T = %T(%N) { %L ->", + rawQueryVarName, + RAW_QUERY, + RAW_QUERY, + sqlQueryVar, + stmtVarName + ) + bindStatement.invoke(scope, stmtVarName) + endControlFlow() + } else { + addLocalVariable( + name = rawQueryVarName, + typeName = RAW_QUERY, + assignExpr = + XCodeBlock.ofNewInstance( + typeName = RAW_QUERY, + argsFormat = "%N", + sqlQueryVar + ) + ) + } + addStatement( + "return %L", + createPagingSourceSpec( + scope = scope, + rawQueryVarName = rawQueryVarName, + stmtVarName = stmtVarName, + dbProperty = dbProperty, + inTransaction = false + ) + ) + } + } + } + } + + private fun createPagingSourceSpec( + scope: CodeGenScope, + rawQueryVarName: String, + stmtVarName: String, + dbProperty: XPropertySpec, + inTransaction: Boolean + ): XTypeSpec { + val tableNamesList = tableNames.joinToString(", ") { "\"$it\"" } + return XTypeSpec.anonymousClassBuilder( + 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, + dbProperty: XPropertySpec, + stmtVarName: String, + inTransaction: Boolean + ): XFunSpec { + val resultVar = scope.getTmpVar("_result") + return XFunSpec.overridingBuilder( + element = convertRowsOverrideInfo.method, + owner = convertRowsOverrideInfo.owner + ) + .apply { + val limitRawQueryParamName = "limitOffsetQuery" + val rowsScope = scope.fork() + 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) { + applyTo { language -> + 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) + applyTo { language -> + val returnPrefix = + when (language) { + CodeLanguage.JAVA -> "return " + CodeLanguage.KOTLIN -> "" + } + addStatement("$returnPrefix%L", resultVar) + } + } + nextControlFlow("finally") + addStatement("%L.close()", stmtVarName) + endControlFlow() + } + } + ) + rowsScope.builder.add("return %L", performBlock) + addCode(rowsScope.generate()) + } + .build() + } +}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt index 360af30..ea76a1c 100644 --- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt +++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt
@@ -16,14 +16,18 @@ package androidx.room.solver.query.result +import androidx.room.compiler.codegen.CodeLanguage import androidx.room.compiler.codegen.VisibilityModifier +import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.applyTo import androidx.room.compiler.codegen.XFunSpec import androidx.room.compiler.codegen.XPropertySpec import androidx.room.compiler.codegen.XTypeName import androidx.room.compiler.codegen.XTypeSpec -import androidx.room.ext.AndroidTypeNames.CURSOR import androidx.room.ext.CommonTypeNames.LIST +import androidx.room.ext.RoomMemberNames.DB_UTIL_SUPPORT_DB_TO_CONNECTION import androidx.room.ext.RoomTypeNames +import androidx.room.ext.SQLiteDriverTypeNames +import androidx.room.ext.SQLiteDriverTypeNames.CONNECTION import androidx.room.solver.CodeGenScope /** Used by Paging2 pipeline */ @@ -35,10 +39,15 @@ listAdapter?.rowAdapters?.firstOrNull()?.out?.asTypeName() ?: XTypeName.ANY_OBJECT val typeName: XTypeName = RoomTypeNames.LIMIT_OFFSET_DATA_SOURCE.parametrizedBy(itemTypeName) + override val usesCompatQueryWriter = true + + override fun isMigratedToDriver(): Boolean = true + override fun convertAndReturn( - roomSQLiteQueryVar: String, - canReleaseQuery: Boolean, + sqlQueryVar: String, dbProperty: XPropertySpec, + bindStatement: (CodeGenScope.(String) -> Unit)?, + returnTypeName: XTypeName, inTransaction: Boolean, scope: CodeGenScope ) { @@ -50,7 +59,7 @@ XTypeSpec.anonymousClassBuilder( "%N, %L, %L, %L%L", dbProperty, - roomSQLiteQueryVar, + sqlQueryVar, inTransaction, true, tableNamesList @@ -60,9 +69,41 @@ addConvertRowsMethod(scope) } .build() + val rowAdapter = listAdapter?.rowAdapters?.first() + if (rowAdapter is PojoRowAdapter && rowAdapter.relationCollectors.isNotEmpty()) { + // @Relation use found, initialize a connection. + val connectionVar = scope.getTmpVar("_connection") + scope.builder.applyTo { language -> + val assignExprFormat = + when (language) { + CodeLanguage.JAVA -> "%M(%L.getOpenHelper().getWritableDatabase())" + CodeLanguage.KOTLIN -> "%M(%L.openHelper.writableDatabase)" + } + addLocalVal( + name = connectionVar, + typeName = CONNECTION, + assignExprFormat = assignExprFormat, + DB_UTIL_SUPPORT_DB_TO_CONNECTION, + dbProperty.name + ) + } + } scope.builder.addStatement("return %L", spec) } + override fun convertAndReturn( + roomSQLiteQueryVar: String, + canReleaseQuery: Boolean, + dbProperty: XPropertySpec, + inTransaction: Boolean, + scope: CodeGenScope + ) { + error( + "This convertAndReturn() should never be invoked, it will be removed once " + + "migration to drivers is completed." + ) + } + private fun XTypeSpec.Builder.addConvertRowsMethod(scope: CodeGenScope) { addFunction( XFunSpec.builder( @@ -72,8 +113,8 @@ ) .apply { returns(LIST.parametrizedBy(itemTypeName)) - val cursorParamName = "cursor" - addParameter(cursorParamName, CURSOR) + val cursorParamName = "statement" + addParameter(cursorParamName, SQLiteDriverTypeNames.STATEMENT) val resultVar = scope.getTmpVar("_res") val rowsScope = scope.fork() listAdapter?.convert(resultVar, cursorParamName, rowsScope)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt index 286394d..a1d11a6 100644 --- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt +++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt
@@ -44,6 +44,8 @@ // TODO(b/319660042): Remove once migration to driver API is done. open fun isMigratedToDriver(): Boolean = false + open val usesCompatQueryWriter: Boolean = false + /** * Receives the SQL and a function to bind args into a statement, it must then generate the code * that steps on the query, reads its columns and returns the result. @@ -56,6 +58,6 @@ inTransaction: Boolean, scope: CodeGenScope ) { - error("Result binder has not been migrated to use driver API.") + error("Result binder has not been migrated to use driver API. ") } }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt index 5fd0551..ed4c8f76 100644 --- a/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt +++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
@@ -316,19 +316,24 @@ private fun createRawQueryMethod(method: RawQueryMethod): XFunSpec { return overrideWithoutAnnotations(method.element, declaredDao) - .addCode(createRawQueryMethodBody(method)) + .addCode( + if ( + method.runtimeQueryParam == null || + method.queryResultBinder.usesCompatQueryWriter + ) { + compatCreateRawQueryMethodBody(method) + } else { + createRawQueryMethodBody(method) + } + ) .build() } private fun createRawQueryMethodBody(method: RawQueryMethod): XCodeBlock { - if (method.runtimeQueryParam == null || !method.queryResultBinder.isMigratedToDriver()) { - return compatCreateRawQueryMethodBody(method) - } - val scope = CodeGenScope(this@DaoWriter, useDriverApi = true) val sqlQueryVar = scope.getTmpVar("_sql") val rawQueryParamName = - if (method.runtimeQueryParam.isSupportQuery()) { + if (method.runtimeQueryParam!!.isSupportQuery()) { val rawQueryVar = scope.getTmpVar("_rawQuery") scope.builder.addLocalVariable( name = rawQueryVar, @@ -352,6 +357,7 @@ rawQueryParamName, XCodeBlock.ofString(java = "getSql()", kotlin = "sql") ) + if (method.returnsValue) { method.queryResultBinder.convertAndReturn( sqlQueryVar = sqlQueryVar, @@ -371,19 +377,17 @@ return scope.generate() } + /** Used by the Non-KMP Paging3 binders and the Paging2 binders. */ private fun compatCreateRawQueryMethodBody(method: RawQueryMethod): XCodeBlock = XCodeBlock.builder() .apply { - val scope = CodeGenScope(this@DaoWriter) + val scope = CodeGenScope(this@DaoWriter, useDriverApi = true) val roomSQLiteQueryVar: String val queryParam = method.runtimeQueryParam - val shouldReleaseQuery: Boolean if (queryParam?.isSupportQuery() == true) { - roomSQLiteQueryVar = queryParam.paramName - shouldReleaseQuery = false + queryParam.paramName } else if (queryParam?.isString() == true) { roomSQLiteQueryVar = scope.getTmpVar("_statement") - shouldReleaseQuery = true addLocalVariable( name = roomSQLiteQueryVar, typeName = ROOM_SQL_QUERY, @@ -397,7 +401,6 @@ } else { // try to generate compiling code. we would've already reported this error roomSQLiteQueryVar = scope.getTmpVar("_statement") - shouldReleaseQuery = false addLocalVariable( name = roomSQLiteQueryVar, typeName = ROOM_SQL_QUERY, @@ -409,16 +412,24 @@ ), ) } - if (method.returnsValue) { - // don't generate code because it will create 1 more error. The original - // error is already reported by the processor. - method.queryResultBinder.convertAndReturn( - roomSQLiteQueryVar = roomSQLiteQueryVar, - canReleaseQuery = shouldReleaseQuery, - dbProperty = dbProperty, - inTransaction = method.inTransaction, - scope = scope - ) + val rawQueryParamName = method.runtimeQueryParam?.paramName + if (rawQueryParamName != null) { + if (method.returnsValue) { + method.queryResultBinder.convertAndReturn( + sqlQueryVar = rawQueryParamName, + dbProperty = dbProperty, + bindStatement = { stmtVar -> + this.builder.addStatement( + "%L.getBindingFunction().invoke(%L)", + rawQueryParamName, + stmtVar + ) + }, + returnTypeName = method.returnType.asTypeName(), + inTransaction = method.inTransaction, + scope = scope + ) + } } add(scope.generate()) } @@ -630,7 +641,6 @@ if (!method.preparedQueryResultBinder.isMigratedToDriver()) { return compatCreatePreparedQueryMethodBody(method) } - val scope = CodeGenScope(this, useDriverApi = true) val queryWriter = QueryWriter(method) val sqlVar = scope.getTmpVar("_sql") @@ -671,27 +681,35 @@ } private fun createQueryMethodBody(method: ReadQueryMethod): XCodeBlock { - if (!method.queryResultBinder.isMigratedToDriver()) { - return compatCreateQueryMethodBody(method) - } - val scope = CodeGenScope(this, useDriverApi = true) val queryWriter = QueryWriter(method) - val sqlVar = scope.getTmpVar("_sql") - val listSizeArgs = queryWriter.prepareQuery(sqlVar, scope) + val sqlStringVar = scope.getTmpVar("_sql") + + val (sqlVar, listSizeArgs) = + if (method.queryResultBinder.usesCompatQueryWriter) { + val roomSQLiteQueryVar = scope.getTmpVar("_statement") + queryWriter.prepareReadAndBind(sqlStringVar, roomSQLiteQueryVar, scope) + roomSQLiteQueryVar to emptyList() + } else { + sqlStringVar to queryWriter.prepareQuery(sqlStringVar, scope) + } + + val bindStatement: (CodeGenScope.(String) -> Unit)? = + if (queryWriter.parameters.isNotEmpty()) { + { stmtVar -> queryWriter.bindArgs(stmtVar, listSizeArgs, this) } + } else { + null + } + method.queryResultBinder.convertAndReturn( sqlQueryVar = sqlVar, dbProperty = dbProperty, - bindStatement = - if (queryWriter.parameters.isNotEmpty()) { - { stmtVar -> queryWriter.bindArgs(stmtVar, listSizeArgs, this) } - } else { - null - }, + bindStatement = bindStatement, returnTypeName = method.returnType.asTypeName(), inTransaction = method.inTransaction, scope = scope ) + return scope.generate() }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt index d95c391..4ecbbf4 100644 --- a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt +++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
@@ -61,6 +61,7 @@ import androidx.room.solver.binderprovider.RxQueryResultBinderProvider import androidx.room.solver.query.parameter.CollectionQueryParameterAdapter import androidx.room.solver.query.result.MultiTypedPagingSourceQueryResultBinder +import androidx.room.solver.query.result.Paging3PagingSourceQueryResultBinder import androidx.room.solver.shortcut.binderprovider.GuavaListenableFutureDeleteOrUpdateMethodBinderProvider import androidx.room.solver.shortcut.binderprovider.GuavaListenableFutureInsertOrUpsertMethodBinderProvider import androidx.room.solver.shortcut.binderprovider.RxCallableDeleteOrUpdateMethodBinderProvider @@ -1428,7 +1429,7 @@ val parsedDao = parser.process() val binder = parsedDao.queryMethods.filterIsInstance<ReadQueryMethod>().first().queryResultBinder - assertThat(binder is MultiTypedPagingSourceQueryResultBinder).isTrue() + assertThat(binder is Paging3PagingSourceQueryResultBinder).isTrue() val pagingSourceXRawType: XRawType? = invocation.context.processingEnv
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt index 71326b5..6c29224 100644 --- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt +++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
@@ -800,7 +800,7 @@ @Entity data class MyEntity( @PrimaryKey - val pk: Int, + val pk: String, ) """ .trimIndent() @@ -1938,6 +1938,9 @@ abstract class MyDao { @Query("SELECT * from MyEntity") abstract fun getDataSourceFactory(): DataSource.Factory<Int, MyEntity> + + @Query("SELECT * FROM MyEntity WHERE pk > :gt ORDER BY pk ASC") + abstract fun getDataSourceFactoryWithArgs(gt: Long): DataSource.Factory<Int, MyEntity> } @Entity
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoRelationshipKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoRelationshipKotlinCodeGenTest.kt index 27b0618..26ec990 100644 --- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoRelationshipKotlinCodeGenTest.kt +++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoRelationshipKotlinCodeGenTest.kt
@@ -465,4 +465,95 @@ ) runTest(sources = listOf(src), expectedFilePath = getTestGoldenPath(testName.methodName)) } + + @Test + fun relations_dataSource() { + val src = + Source.kotlin( + "MyDao.kt", + """ + import androidx.room.* + import androidx.paging.DataSource + + @Dao + @Suppress( + RoomWarnings.RELATION_QUERY_WITHOUT_TRANSACTION, + RoomWarnings.MISSING_INDEX_ON_JUNCTION + ) + interface MyDao { + // 1 to 1 + @Query("SELECT * FROM Song") + fun getSongsWithArtist(): DataSource.Factory<Int, SongWithArtist> + + // 1 to many + @Query("SELECT * FROM Artist") + fun getArtistAndSongs(): DataSource.Factory<Int, ArtistAndSongs> + + // many to many + @Query("SELECT * FROM Playlist") + fun getPlaylistAndSongs(): DataSource.Factory<Int, PlaylistAndSongs> + } + + data class SongWithArtist( + @Embedded + val song: Song, + @Relation(parentColumn = "artistKey", entityColumn = "artistId") + val artist: Artist + ) + + data class ArtistAndSongs( + @Embedded + val artist: Artist, + @Relation(parentColumn = "artistId", entityColumn = "artistKey") + val songs: List<Song> + ) + + data class PlaylistAndSongs( + @Embedded + val playlist: Playlist, + @Relation( + parentColumn = "playlistId", + entityColumn = "songId", + associateBy = Junction( + value = PlaylistSongXRef::class, + parentColumn = "playlistKey", + entityColumn = "songKey", + ) + ) + val songs: List<Song> + ) + + @Entity + data class Artist( + @PrimaryKey + val artistId: Long + ) + + @Entity + data class Song( + @PrimaryKey + val songId: Long, + val artistKey: Long + ) + + @Entity + data class Playlist( + @PrimaryKey + val playlistId: Long, + ) + + @Entity(primaryKeys = ["playlistKey", "songKey"]) + data class PlaylistSongXRef( + val playlistKey: Long, + val songKey: Long, + ) + """ + .trimIndent() + ) + runTest( + sources = + listOf(src, databaseSrc, COMMON.DATA_SOURCE_FACTORY, COMMON.POSITIONAL_DATA_SOURCE), + expectedFilePath = getTestGoldenPath(testName.methodName) + ) + } }
diff --git a/room/room-compiler/src/test/test-data/common/input/LimitOffsetListenableFuturePagingSource.kt b/room/room-compiler/src/test/test-data/common/input/LimitOffsetListenableFuturePagingSource.kt index 4a8eaed..cecbc5c 100644 --- a/room/room-compiler/src/test/test-data/common/input/LimitOffsetListenableFuturePagingSource.kt +++ b/room/room-compiler/src/test/test-data/common/input/LimitOffsetListenableFuturePagingSource.kt
@@ -15,9 +15,9 @@ */ package androidx.room.paging.guava -import android.database.Cursor import androidx.room.RoomDatabase import androidx.room.RoomSQLiteQuery +import androidx.sqlite.SQLiteStatement abstract class LimitOffsetListenableFuturePagingSource<T : Any>( private val sourceQuery: RoomSQLiteQuery, @@ -28,5 +28,5 @@ db, *tables ) { - protected abstract override fun convertRows(cursor: Cursor): List<T> + protected override abstract fun convertRows(statement: SQLiteStatement): List<T> } \ No newline at end of file
diff --git a/room/room-compiler/src/test/test-data/common/input/LimitOffsetRx2PagingSource.kt b/room/room-compiler/src/test/test-data/common/input/LimitOffsetRx2PagingSource.kt index 1eef542..a3184f0 100644 --- a/room/room-compiler/src/test/test-data/common/input/LimitOffsetRx2PagingSource.kt +++ b/room/room-compiler/src/test/test-data/common/input/LimitOffsetRx2PagingSource.kt
@@ -15,9 +15,9 @@ */ package androidx.room.paging.rxjava2 -import android.database.Cursor import androidx.room.RoomDatabase import androidx.room.RoomSQLiteQuery +import androidx.sqlite.SQLiteStatement abstract class LimitOffsetRxPagingSource<T : Any>( private val sourceQuery: RoomSQLiteQuery, @@ -28,5 +28,5 @@ db, *tables ) { - protected abstract override fun convertRows(cursor: Cursor): List<T> + protected override abstract fun convertRows(statement: SQLiteStatement): List<T> } \ No newline at end of file
diff --git a/room/room-compiler/src/test/test-data/common/input/LimitOffsetRx3PagingSource.kt b/room/room-compiler/src/test/test-data/common/input/LimitOffsetRx3PagingSource.kt index bfb1294..7b2b8b6 100644 --- a/room/room-compiler/src/test/test-data/common/input/LimitOffsetRx3PagingSource.kt +++ b/room/room-compiler/src/test/test-data/common/input/LimitOffsetRx3PagingSource.kt
@@ -15,9 +15,9 @@ */ package androidx.room.paging.rxjava3 -import android.database.Cursor import androidx.room.RoomDatabase import androidx.room.RoomSQLiteQuery +import androidx.sqlite.SQLiteStatement abstract class LimitOffsetRxPagingSource<T : Any>( private val sourceQuery: RoomSQLiteQuery, @@ -28,5 +28,5 @@ db, *tables ) { - protected abstract override fun convertRows(cursor: Cursor): List<T> + protected override abstract fun convertRows(statement: SQLiteStatement): List<T> } \ No newline at end of file
diff --git a/room/room-compiler/src/test/test-data/common/input/ListenableFuturePagingSource.kt b/room/room-compiler/src/test/test-data/common/input/ListenableFuturePagingSource.kt index 1e84ae6..042e6f1 100644 --- a/room/room-compiler/src/test/test-data/common/input/ListenableFuturePagingSource.kt +++ b/room/room-compiler/src/test/test-data/common/input/ListenableFuturePagingSource.kt
@@ -15,10 +15,10 @@ */ package androidx.paging -import android.database.Cursor import androidx.paging.PagingState import androidx.room.RoomDatabase import androidx.room.RoomSQLiteQuery +import androidx.sqlite.SQLiteStatement @Suppress("UNUSED_PARAMETER") abstract class ListenableFuturePagingSource<K : Any, T : Any>( @@ -33,5 +33,6 @@ override public suspend fun load(params: LoadParams<K>): LoadResult<K, T> { return LoadResult.Invalid() } - protected abstract fun convertRows(cursor: Cursor): List<T> + + protected abstract fun convertRows(statement: SQLiteStatement): List<T> } \ No newline at end of file
diff --git a/room/room-compiler/src/test/test-data/common/input/Rx2PagingSource.kt b/room/room-compiler/src/test/test-data/common/input/Rx2PagingSource.kt index a98e980..e673d19 100644 --- a/room/room-compiler/src/test/test-data/common/input/Rx2PagingSource.kt +++ b/room/room-compiler/src/test/test-data/common/input/Rx2PagingSource.kt
@@ -19,6 +19,8 @@ import androidx.paging.PagingState import androidx.room.RoomDatabase import androidx.room.RoomSQLiteQuery +import androidx.room.paging.CursorSQLiteStatement +import androidx.sqlite.SQLiteStatement @Suppress("UNUSED_PARAMETER") abstract class RxPagingSource<K : Any, T : Any>( @@ -34,5 +36,14 @@ return LoadResult.Invalid() } - protected abstract fun convertRows(cursor: Cursor): List<T> + protected open fun convertRows(cursor: Cursor): List<T> { + return convertRows(CursorSQLiteStatement(cursor)) + } + + protected open fun convertRows(statement: SQLiteStatement): List<T> { + throw NotImplementedError( + "Unexpected call to a function with no implementation that Room is suppose to " + + "generate." + ) + } } \ No newline at end of file
diff --git a/room/room-compiler/src/test/test-data/common/input/Rx3PagingSource.kt b/room/room-compiler/src/test/test-data/common/input/Rx3PagingSource.kt index 212cd77..2b16445 100644 --- a/room/room-compiler/src/test/test-data/common/input/Rx3PagingSource.kt +++ b/room/room-compiler/src/test/test-data/common/input/Rx3PagingSource.kt
@@ -19,6 +19,8 @@ import androidx.paging.PagingState import androidx.room.RoomDatabase import androidx.room.RoomSQLiteQuery +import androidx.room.paging.CursorSQLiteStatement +import androidx.sqlite.SQLiteStatement @Suppress("UNUSED_PARAMETER") abstract class RxPagingSource<K : Any, T : Any>( @@ -34,5 +36,14 @@ return LoadResult.Invalid() } - protected abstract fun convertRows(cursor: Cursor): List<T> + protected open fun convertRows(cursor: Cursor): List<T> { + return convertRows(CursorSQLiteStatement(cursor)) + } + + protected open fun convertRows(statement: SQLiteStatement): List<T> { + throw NotImplementedError( + "Unexpected call to a function with no implementation that Room is suppose to " + + "generate." + ) + } } \ No newline at end of file
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 fb9b14c..4615252 100644 --- a/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt +++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/multiTypedPagingSourceResultBinder.kt
@@ -1,4 +1,3 @@ -import android.database.Cursor import androidx.paging.ListenableFuturePagingSource import androidx.paging.PagingSource import androidx.room.RoomDatabase @@ -47,8 +46,8 @@ val _result: MutableList<MyEntity> = mutableListOf() while (_stmt.step()) { val _item: MyEntity - val _tmpPk: Int - _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt() + val _tmpPk: String + _tmpPk = _stmt.getText(_cursorIndexOfPk) _item = MyEntity(_tmpPk) _result.add(_item) } @@ -76,8 +75,8 @@ val _result: MutableList<MyEntity> = mutableListOf() while (_stmt.step()) { val _item: MyEntity - val _tmpPk: Int - _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt() + val _tmpPk: String + _tmpPk = _stmt.getText(_cursorIndexOfPk) _item = MyEntity(_tmpPk) _result.add(_item) } @@ -93,13 +92,13 @@ val _sql: String = "SELECT pk FROM MyEntity" val _statement: RoomSQLiteQuery = acquire(_sql, 0) return object : Rxjava2LimitOffsetRxPagingSource<MyEntity>(_statement, __db, "MyEntity") { - protected override fun convertRows(cursor: Cursor): List<MyEntity> { + protected override fun convertRows(statement: SQLiteStatement): List<MyEntity> { val _cursorIndexOfPk: Int = 0 val _result: MutableList<MyEntity> = mutableListOf() - while (cursor.moveToNext()) { + while (statement.step()) { val _item: MyEntity - val _tmpPk: Int - _tmpPk = cursor.getInt(_cursorIndexOfPk) + val _tmpPk: String + _tmpPk = statement.getText(_cursorIndexOfPk) _item = MyEntity(_tmpPk) _result.add(_item) } @@ -112,13 +111,13 @@ val _sql: String = "SELECT pk FROM MyEntity" val _statement: RoomSQLiteQuery = acquire(_sql, 0) return object : Rxjava3LimitOffsetRxPagingSource<MyEntity>(_statement, __db, "MyEntity") { - protected override fun convertRows(cursor: Cursor): List<MyEntity> { + protected override fun convertRows(statement: SQLiteStatement): List<MyEntity> { val _cursorIndexOfPk: Int = 0 val _result: MutableList<MyEntity> = mutableListOf() - while (cursor.moveToNext()) { + while (statement.step()) { val _item: MyEntity - val _tmpPk: Int - _tmpPk = cursor.getInt(_cursorIndexOfPk) + val _tmpPk: String + _tmpPk = statement.getText(_cursorIndexOfPk) _item = MyEntity(_tmpPk) _result.add(_item) } @@ -132,13 +131,13 @@ val _statement: RoomSQLiteQuery = acquire(_sql, 0) return object : LimitOffsetListenableFuturePagingSource<MyEntity>(_statement, __db, "MyEntity") { - protected override fun convertRows(cursor: Cursor): List<MyEntity> { + protected override fun convertRows(statement: SQLiteStatement): List<MyEntity> { val _cursorIndexOfPk: Int = 0 val _result: MutableList<MyEntity> = mutableListOf() - while (cursor.moveToNext()) { + while (statement.step()) { val _item: MyEntity - val _tmpPk: Int - _tmpPk = cursor.getInt(_cursorIndexOfPk) + val _tmpPk: String + _tmpPk = statement.getText(_cursorIndexOfPk) _item = MyEntity(_tmpPk) _result.add(_item) }
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/paging_dataSource.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/paging_dataSource.kt index 2730d8f..f8e49bc 100644 --- a/room/room-compiler/src/test/test-data/kotlinCodeGen/paging_dataSource.kt +++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/paging_dataSource.kt
@@ -1,12 +1,13 @@ -import android.database.Cursor import androidx.paging.DataSource import androidx.room.RoomDatabase import androidx.room.RoomSQLiteQuery import androidx.room.RoomSQLiteQuery.Companion.acquire import androidx.room.paging.LimitOffsetDataSource import androidx.room.util.getColumnIndexOrThrow +import androidx.sqlite.SQLiteStatement import javax.`annotation`.processing.Generated import kotlin.Int +import kotlin.Long import kotlin.String import kotlin.Suppress import kotlin.collections.List @@ -30,16 +31,43 @@ return object : DataSource.Factory<Int, MyEntity>() { public override fun create(): LimitOffsetDataSource<MyEntity> = object : LimitOffsetDataSource<MyEntity>(__db, _statement, false, true, "MyEntity") { - protected override fun convertRows(cursor: Cursor): List<MyEntity> { - val _cursorIndexOfPk: Int = getColumnIndexOrThrow(cursor, "pk") - val _cursorIndexOfOther: Int = getColumnIndexOrThrow(cursor, "other") + protected override fun convertRows(statement: SQLiteStatement): List<MyEntity> { + val _cursorIndexOfPk: Int = getColumnIndexOrThrow(statement, "pk") + val _cursorIndexOfOther: Int = getColumnIndexOrThrow(statement, "other") val _res: MutableList<MyEntity> = mutableListOf() - while (cursor.moveToNext()) { + while (statement.step()) { val _item: MyEntity val _tmpPk: Int - _tmpPk = cursor.getInt(_cursorIndexOfPk) + _tmpPk = statement.getLong(_cursorIndexOfPk).toInt() val _tmpOther: String - _tmpOther = cursor.getString(_cursorIndexOfOther) + _tmpOther = statement.getText(_cursorIndexOfOther) + _item = MyEntity(_tmpPk,_tmpOther) + _res.add(_item) + } + return _res + } + } + } + } + + public override fun getDataSourceFactoryWithArgs(gt: Long): DataSource.Factory<Int, MyEntity> { + val _sql: String = "SELECT * FROM MyEntity WHERE pk > ? ORDER BY pk ASC" + val _statement: RoomSQLiteQuery = acquire(_sql, 1) + var _argIndex: Int = 1 + _statement.bindLong(_argIndex, gt) + return object : DataSource.Factory<Int, MyEntity>() { + public override fun create(): LimitOffsetDataSource<MyEntity> = object : + LimitOffsetDataSource<MyEntity>(__db, _statement, false, true, "MyEntity") { + protected override fun convertRows(statement: SQLiteStatement): List<MyEntity> { + val _cursorIndexOfPk: Int = getColumnIndexOrThrow(statement, "pk") + val _cursorIndexOfOther: Int = getColumnIndexOrThrow(statement, "other") + val _res: MutableList<MyEntity> = mutableListOf() + while (statement.step()) { + val _item: MyEntity + val _tmpPk: Int + _tmpPk = statement.getLong(_cursorIndexOfPk).toInt() + val _tmpOther: String + _tmpOther = statement.getText(_cursorIndexOfOther) _item = MyEntity(_tmpPk,_tmpOther) _res.add(_item) }
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_dataSource.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_dataSource.kt new file mode 100644 index 0000000..68783b0 --- /dev/null +++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_dataSource.kt
@@ -0,0 +1,314 @@ +import androidx.paging.DataSource +import androidx.room.RoomDatabase +import androidx.room.RoomSQLiteQuery +import androidx.room.RoomSQLiteQuery.Companion.acquire +import androidx.room.paging.LimitOffsetDataSource +import androidx.room.util.appendPlaceholders +import androidx.room.util.getColumnIndex +import androidx.room.util.getColumnIndexOrThrow +import androidx.room.util.recursiveFetchMap +import androidx.room.util.toSQLiteConnection +import androidx.sqlite.SQLiteConnection +import androidx.sqlite.SQLiteStatement +import javax.`annotation`.processing.Generated +import kotlin.Int +import kotlin.Long +import kotlin.String +import kotlin.Suppress +import kotlin.collections.List +import kotlin.collections.MutableList +import kotlin.collections.MutableMap +import kotlin.collections.Set +import kotlin.collections.mutableListOf +import kotlin.collections.mutableMapOf +import kotlin.reflect.KClass +import kotlin.text.StringBuilder + +@Generated(value = ["androidx.room.RoomProcessor"]) +@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION", "REDUNDANT_PROJECTION", "REMOVAL"]) +public class MyDao_Impl( + __db: RoomDatabase, +) : MyDao { + private val __db: RoomDatabase + init { + this.__db = __db + } + + public override fun getSongsWithArtist(): DataSource.Factory<Int, SongWithArtist> { + val _sql: String = "SELECT * FROM Song" + val _statement: RoomSQLiteQuery = acquire(_sql, 0) + return object : DataSource.Factory<Int, SongWithArtist>() { + public override fun create(): LimitOffsetDataSource<SongWithArtist> { + val _connection: SQLiteConnection = toSQLiteConnection(__db.openHelper.writableDatabase) + return object : LimitOffsetDataSource<SongWithArtist>(__db, _statement, false, true, + "Artist", "Song") { + protected override fun convertRows(statement: SQLiteStatement): List<SongWithArtist> { + val _cursorIndexOfSongId: Int = getColumnIndexOrThrow(statement, "songId") + val _cursorIndexOfArtistKey: Int = getColumnIndexOrThrow(statement, "artistKey") + val _collectionArtist: MutableMap<Long, Artist?> = mutableMapOf() + while (statement.step()) { + val _tmpKey: Long + _tmpKey = statement.getLong(_cursorIndexOfArtistKey) + _collectionArtist.put(_tmpKey, null) + } + statement.reset() + __fetchRelationshipArtistAsArtist(_connection, _collectionArtist) + val _res: MutableList<SongWithArtist> = mutableListOf() + while (statement.step()) { + val _item: SongWithArtist + val _tmpSong: Song + val _tmpSongId: Long + _tmpSongId = statement.getLong(_cursorIndexOfSongId) + val _tmpArtistKey: Long + _tmpArtistKey = statement.getLong(_cursorIndexOfArtistKey) + _tmpSong = Song(_tmpSongId,_tmpArtistKey) + val _tmpArtist: Artist? + val _tmpKey_1: Long + _tmpKey_1 = statement.getLong(_cursorIndexOfArtistKey) + _tmpArtist = _collectionArtist.get(_tmpKey_1) + if (_tmpArtist == null) { + error("Relationship item 'artist' was expected to be NON-NULL but is NULL in @Relation involving a parent column named 'artistKey' and entityColumn named 'artistId'.") + } + _item = SongWithArtist(_tmpSong,_tmpArtist) + _res.add(_item) + } + return _res + } + } + } + } + } + + public override fun getArtistAndSongs(): DataSource.Factory<Int, ArtistAndSongs> { + val _sql: String = "SELECT * FROM Artist" + val _statement: RoomSQLiteQuery = acquire(_sql, 0) + return object : DataSource.Factory<Int, ArtistAndSongs>() { + public override fun create(): LimitOffsetDataSource<ArtistAndSongs> { + val _connection: SQLiteConnection = toSQLiteConnection(__db.openHelper.writableDatabase) + return object : LimitOffsetDataSource<ArtistAndSongs>(__db, _statement, false, true, "Song", + "Artist") { + protected override fun convertRows(statement: SQLiteStatement): List<ArtistAndSongs> { + val _cursorIndexOfArtistId: Int = getColumnIndexOrThrow(statement, "artistId") + val _collectionSongs: MutableMap<Long, MutableList<Song>> = mutableMapOf() + while (statement.step()) { + val _tmpKey: Long + _tmpKey = statement.getLong(_cursorIndexOfArtistId) + if (!_collectionSongs.containsKey(_tmpKey)) { + _collectionSongs.put(_tmpKey, mutableListOf()) + } + } + statement.reset() + __fetchRelationshipSongAsSong(_connection, _collectionSongs) + val _res: MutableList<ArtistAndSongs> = mutableListOf() + while (statement.step()) { + val _item: ArtistAndSongs + val _tmpArtist: Artist + val _tmpArtistId: Long + _tmpArtistId = statement.getLong(_cursorIndexOfArtistId) + _tmpArtist = Artist(_tmpArtistId) + val _tmpSongsCollection: MutableList<Song> + val _tmpKey_1: Long + _tmpKey_1 = statement.getLong(_cursorIndexOfArtistId) + _tmpSongsCollection = _collectionSongs.getValue(_tmpKey_1) + _item = ArtistAndSongs(_tmpArtist,_tmpSongsCollection) + _res.add(_item) + } + return _res + } + } + } + } + } + + public override fun getPlaylistAndSongs(): DataSource.Factory<Int, PlaylistAndSongs> { + val _sql: String = "SELECT * FROM Playlist" + val _statement: RoomSQLiteQuery = acquire(_sql, 0) + return object : DataSource.Factory<Int, PlaylistAndSongs>() { + public override fun create(): LimitOffsetDataSource<PlaylistAndSongs> { + val _connection: SQLiteConnection = toSQLiteConnection(__db.openHelper.writableDatabase) + return object : LimitOffsetDataSource<PlaylistAndSongs>(__db, _statement, false, true, + "PlaylistSongXRef", "Song", "Playlist") { + protected override fun convertRows(statement: SQLiteStatement): List<PlaylistAndSongs> { + val _cursorIndexOfPlaylistId: Int = getColumnIndexOrThrow(statement, "playlistId") + val _collectionSongs: MutableMap<Long, MutableList<Song>> = mutableMapOf() + while (statement.step()) { + val _tmpKey: Long + _tmpKey = statement.getLong(_cursorIndexOfPlaylistId) + if (!_collectionSongs.containsKey(_tmpKey)) { + _collectionSongs.put(_tmpKey, mutableListOf()) + } + } + statement.reset() + __fetchRelationshipSongAsSong_1(_connection, _collectionSongs) + val _res: MutableList<PlaylistAndSongs> = mutableListOf() + while (statement.step()) { + val _item: PlaylistAndSongs + val _tmpPlaylist: Playlist + val _tmpPlaylistId: Long + _tmpPlaylistId = statement.getLong(_cursorIndexOfPlaylistId) + _tmpPlaylist = Playlist(_tmpPlaylistId) + val _tmpSongsCollection: MutableList<Song> + val _tmpKey_1: Long + _tmpKey_1 = statement.getLong(_cursorIndexOfPlaylistId) + _tmpSongsCollection = _collectionSongs.getValue(_tmpKey_1) + _item = PlaylistAndSongs(_tmpPlaylist,_tmpSongsCollection) + _res.add(_item) + } + return _res + } + } + } + } + } + + private fun __fetchRelationshipArtistAsArtist(_connection: SQLiteConnection, + _map: MutableMap<Long, Artist?>) { + val __mapKeySet: Set<Long> = _map.keys + if (__mapKeySet.isEmpty()) { + return + } + if (_map.size > 999) { + recursiveFetchMap(_map, false) { _tmpMap -> + __fetchRelationshipArtistAsArtist(_connection, _tmpMap) + } + return + } + val _stringBuilder: StringBuilder = StringBuilder() + _stringBuilder.append("SELECT `artistId` FROM `Artist` WHERE `artistId` IN (") + val _inputSize: Int = __mapKeySet.size + appendPlaceholders(_stringBuilder, _inputSize) + _stringBuilder.append(")") + val _sql: String = _stringBuilder.toString() + val _stmt: SQLiteStatement = _connection.prepare(_sql) + var _argIndex: Int = 1 + for (_item: Long in __mapKeySet) { + _stmt.bindLong(_argIndex, _item) + _argIndex++ + } + try { + val _itemKeyIndex: Int = getColumnIndex(_stmt, "artistId") + if (_itemKeyIndex == -1) { + return + } + val _cursorIndexOfArtistId: Int = 0 + while (_stmt.step()) { + val _tmpKey: Long + _tmpKey = _stmt.getLong(_itemKeyIndex) + if (_map.containsKey(_tmpKey)) { + val _item_1: Artist + val _tmpArtistId: Long + _tmpArtistId = _stmt.getLong(_cursorIndexOfArtistId) + _item_1 = Artist(_tmpArtistId) + _map.put(_tmpKey, _item_1) + } + } + } finally { + _stmt.close() + } + } + + private fun __fetchRelationshipSongAsSong(_connection: SQLiteConnection, + _map: MutableMap<Long, MutableList<Song>>) { + val __mapKeySet: Set<Long> = _map.keys + if (__mapKeySet.isEmpty()) { + return + } + if (_map.size > 999) { + recursiveFetchMap(_map, true) { _tmpMap -> + __fetchRelationshipSongAsSong(_connection, _tmpMap) + } + return + } + val _stringBuilder: StringBuilder = StringBuilder() + _stringBuilder.append("SELECT `songId`,`artistKey` FROM `Song` WHERE `artistKey` IN (") + val _inputSize: Int = __mapKeySet.size + appendPlaceholders(_stringBuilder, _inputSize) + _stringBuilder.append(")") + val _sql: String = _stringBuilder.toString() + val _stmt: SQLiteStatement = _connection.prepare(_sql) + var _argIndex: Int = 1 + for (_item: Long in __mapKeySet) { + _stmt.bindLong(_argIndex, _item) + _argIndex++ + } + try { + val _itemKeyIndex: Int = getColumnIndex(_stmt, "artistKey") + if (_itemKeyIndex == -1) { + return + } + val _cursorIndexOfSongId: Int = 0 + val _cursorIndexOfArtistKey: Int = 1 + while (_stmt.step()) { + val _tmpKey: Long + _tmpKey = _stmt.getLong(_itemKeyIndex) + val _tmpRelation: MutableList<Song>? = _map.get(_tmpKey) + if (_tmpRelation != null) { + val _item_1: Song + val _tmpSongId: Long + _tmpSongId = _stmt.getLong(_cursorIndexOfSongId) + val _tmpArtistKey: Long + _tmpArtistKey = _stmt.getLong(_cursorIndexOfArtistKey) + _item_1 = Song(_tmpSongId,_tmpArtistKey) + _tmpRelation.add(_item_1) + } + } + } finally { + _stmt.close() + } + } + + private fun __fetchRelationshipSongAsSong_1(_connection: SQLiteConnection, + _map: MutableMap<Long, MutableList<Song>>) { + val __mapKeySet: Set<Long> = _map.keys + if (__mapKeySet.isEmpty()) { + return + } + if (_map.size > 999) { + recursiveFetchMap(_map, true) { _tmpMap -> + __fetchRelationshipSongAsSong_1(_connection, _tmpMap) + } + return + } + val _stringBuilder: StringBuilder = StringBuilder() + _stringBuilder.append("SELECT `Song`.`songId` AS `songId`,`Song`.`artistKey` AS `artistKey`,_junction.`playlistKey` FROM `PlaylistSongXRef` AS _junction INNER JOIN `Song` ON (_junction.`songKey` = `Song`.`songId`) WHERE _junction.`playlistKey` IN (") + val _inputSize: Int = __mapKeySet.size + appendPlaceholders(_stringBuilder, _inputSize) + _stringBuilder.append(")") + val _sql: String = _stringBuilder.toString() + val _stmt: SQLiteStatement = _connection.prepare(_sql) + var _argIndex: Int = 1 + for (_item: Long in __mapKeySet) { + _stmt.bindLong(_argIndex, _item) + _argIndex++ + } + try { + // _junction.playlistKey + val _itemKeyIndex: Int = 2 + if (_itemKeyIndex == -1) { + return + } + val _cursorIndexOfSongId: Int = 0 + val _cursorIndexOfArtistKey: Int = 1 + while (_stmt.step()) { + val _tmpKey: Long + _tmpKey = _stmt.getLong(_itemKeyIndex) + val _tmpRelation: MutableList<Song>? = _map.get(_tmpKey) + if (_tmpRelation != null) { + val _item_1: Song + val _tmpSongId: Long + _tmpSongId = _stmt.getLong(_cursorIndexOfSongId) + val _tmpArtistKey: Long + _tmpArtistKey = _stmt.getLong(_cursorIndexOfArtistKey) + _item_1 = Song(_tmpSongId,_tmpArtistKey) + _tmpRelation.add(_item_1) + } + } + } finally { + _stmt.close() + } + } + + public companion object { + public fun getRequiredConverters(): List<KClass<*>> = emptyList() + } +}
diff --git a/room/room-paging-guava/src/main/java/androidx/room/paging/guava/LimitOffsetListenableFuturePagingSource.kt b/room/room-paging-guava/src/main/java/androidx/room/paging/guava/LimitOffsetListenableFuturePagingSource.kt index 0e5309c..2022ec2 100644 --- a/room/room-paging-guava/src/main/java/androidx/room/paging/guava/LimitOffsetListenableFuturePagingSource.kt +++ b/room/room-paging-guava/src/main/java/androidx/room/paging/guava/LimitOffsetListenableFuturePagingSource.kt
@@ -18,7 +18,6 @@ import android.database.Cursor import android.os.CancellationSignal -import androidx.annotation.NonNull import androidx.annotation.RestrictTo import androidx.annotation.VisibleForTesting import androidx.paging.ListenableFuturePagingSource @@ -26,12 +25,14 @@ import androidx.room.RoomDatabase import androidx.room.RoomSQLiteQuery import androidx.room.guava.createListenableFuture +import androidx.room.paging.CursorSQLiteStatement import androidx.room.paging.util.INITIAL_ITEM_COUNT import androidx.room.paging.util.INVALID import androidx.room.paging.util.ThreadSafeInvalidationObserver import androidx.room.paging.util.getClippedRefreshKey import androidx.room.paging.util.queryDatabase import androidx.room.paging.util.queryItemCount +import androidx.sqlite.SQLiteStatement import androidx.sqlite.db.SupportSQLiteQuery import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture @@ -160,7 +161,16 @@ ) } - @NonNull protected abstract fun convertRows(cursor: Cursor): List<Value> + protected open fun convertRows(cursor: Cursor): List<Value> { + return convertRows(CursorSQLiteStatement(cursor)) + } + + protected open fun convertRows(statement: SQLiteStatement): 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." + ) + } override val jumpingSupported: Boolean get() = true @@ -168,4 +178,9 @@ override fun getRefreshKey(state: PagingState<Int, Value>): Int? { return state.getClippedRefreshKey() } + + companion object { + const val BUG_LINK = + "https://issuetracker.google.com/issues/new?component=413107&template=1096568" + } }
diff --git a/room/room-paging-rxjava2/src/main/java/androidx/room/paging/rxjava2/LimitOffsetRxPagingSource.kt b/room/room-paging-rxjava2/src/main/java/androidx/room/paging/rxjava2/LimitOffsetRxPagingSource.kt index 7bd8f30..0f4f9e0 100644 --- a/room/room-paging-rxjava2/src/main/java/androidx/room/paging/rxjava2/LimitOffsetRxPagingSource.kt +++ b/room/room-paging-rxjava2/src/main/java/androidx/room/paging/rxjava2/LimitOffsetRxPagingSource.kt
@@ -25,12 +25,14 @@ import androidx.room.RoomDatabase import androidx.room.RoomSQLiteQuery import androidx.room.RxRoom +import androidx.room.paging.CursorSQLiteStatement import androidx.room.paging.util.INITIAL_ITEM_COUNT import androidx.room.paging.util.INVALID import androidx.room.paging.util.ThreadSafeInvalidationObserver import androidx.room.paging.util.getClippedRefreshKey import androidx.room.paging.util.queryDatabase import androidx.room.paging.util.queryItemCount +import androidx.sqlite.SQLiteStatement import androidx.sqlite.db.SupportSQLiteQuery import io.reactivex.Single import io.reactivex.schedulers.Schedulers @@ -100,7 +102,17 @@ return if (invalid) INVALID as LoadResult.Invalid<Int, Value> else result } - @NonNull protected abstract fun convertRows(cursor: Cursor): List<Value> + @NonNull + protected open fun convertRows(cursor: Cursor): List<Value> { + return convertRows(CursorSQLiteStatement(cursor)) + } + + protected open fun convertRows(statement: SQLiteStatement): 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." + ) + } override fun getRefreshKey(state: PagingState<Int, Value>): Int? { return state.getClippedRefreshKey() @@ -108,4 +120,9 @@ override val jumpingSupported: Boolean get() = true + + companion object { + const val BUG_LINK = + "https://issuetracker.google.com/issues/new?component=413107&template=1096568" + } }
diff --git a/room/room-paging-rxjava3/src/main/java/androidx/room/paging/rxjava3/LimitOffsetRxPagingSource.kt b/room/room-paging-rxjava3/src/main/java/androidx/room/paging/rxjava3/LimitOffsetRxPagingSource.kt index a2792a0..b602e0f 100644 --- a/room/room-paging-rxjava3/src/main/java/androidx/room/paging/rxjava3/LimitOffsetRxPagingSource.kt +++ b/room/room-paging-rxjava3/src/main/java/androidx/room/paging/rxjava3/LimitOffsetRxPagingSource.kt
@@ -17,13 +17,13 @@ package androidx.room.paging.rxjava3 import android.database.Cursor -import androidx.annotation.NonNull import androidx.annotation.RestrictTo import androidx.annotation.VisibleForTesting import androidx.paging.PagingState import androidx.paging.rxjava3.RxPagingSource import androidx.room.RoomDatabase import androidx.room.RoomSQLiteQuery +import androidx.room.paging.CursorSQLiteStatement import androidx.room.paging.util.INITIAL_ITEM_COUNT import androidx.room.paging.util.INVALID import androidx.room.paging.util.ThreadSafeInvalidationObserver @@ -31,6 +31,7 @@ import androidx.room.paging.util.queryDatabase import androidx.room.paging.util.queryItemCount import androidx.room.rxjava3.createSingle +import androidx.sqlite.SQLiteStatement import androidx.sqlite.db.SupportSQLiteQuery import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers @@ -100,7 +101,16 @@ return if (invalid) INVALID as LoadResult.Invalid<Int, Value> else result } - @NonNull protected abstract fun convertRows(cursor: Cursor): List<Value> + protected open fun convertRows(cursor: Cursor): List<Value> { + return convertRows(CursorSQLiteStatement(cursor)) + } + + protected open fun convertRows(statement: SQLiteStatement): 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." + ) + } override fun getRefreshKey(state: PagingState<Int, Value>): Int? { return state.getClippedRefreshKey() @@ -108,4 +118,9 @@ override val jumpingSupported: Boolean get() = true + + companion object { + const val BUG_LINK = + "https://issuetracker.google.com/issues/new?component=413107&template=1096568" + } }
diff --git a/room/room-paging/src/androidMain/kotlin/androidx/room/paging/CursorSQLiteStatement.android.kt b/room/room-paging/src/androidMain/kotlin/androidx/room/paging/SQLiteStatementCursor.android.kt similarity index 94% rename from room/room-paging/src/androidMain/kotlin/androidx/room/paging/CursorSQLiteStatement.android.kt rename to room/room-paging/src/androidMain/kotlin/androidx/room/paging/SQLiteStatementCursor.android.kt index d9389a0..1fe7955 100644 --- a/room/room-paging/src/androidMain/kotlin/androidx/room/paging/CursorSQLiteStatement.android.kt +++ b/room/room-paging/src/androidMain/kotlin/androidx/room/paging/SQLiteStatementCursor.android.kt
@@ -19,7 +19,7 @@ import android.database.AbstractCursor import androidx.sqlite.SQLiteStatement -/** Wrapper class for backwards compatibility in room-paging. */ +/** Cursor backed by a SQLiteStatement used for backwards compatibility of Paging APIs. */ internal class SQLiteStatementCursor( private val statement: SQLiteStatement, private val rowCount: Int
diff --git a/room/room-runtime/api/restricted_current.txt b/room/room-runtime/api/restricted_current.txt index a63957d..df44066 100644 --- a/room/room-runtime/api/restricted_current.txt +++ b/room/room-runtime/api/restricted_current.txt
@@ -485,7 +485,8 @@ ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase, androidx.room.RoomSQLiteQuery, boolean, java.lang.String!...); ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase, androidx.sqlite.db.SupportSQLiteQuery, boolean, boolean, java.lang.String!...); ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase, androidx.sqlite.db.SupportSQLiteQuery, boolean, java.lang.String!...); - method protected abstract java.util.List<T!> convertRows(android.database.Cursor); + method protected java.util.List<T!> convertRows(android.database.Cursor); + method protected java.util.List<T!> convertRows(androidx.sqlite.SQLiteStatement); method public void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T!>); method public void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T!>); } @@ -521,6 +522,7 @@ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.database.Cursor query(androidx.room.RoomDatabase db, androidx.sqlite.db.SupportSQLiteQuery sqLiteQuery, boolean maybeCopy); method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.database.Cursor query(androidx.room.RoomDatabase db, androidx.sqlite.db.SupportSQLiteQuery sqLiteQuery, boolean maybeCopy, android.os.CancellationSignal? signal); method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @kotlin.jvm.Throws(exceptionClasses=IOException::class) public static int readVersion(java.io.File databaseFile) throws java.io.IOException; + method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.sqlite.SQLiteConnection toSQLiteConnection(androidx.sqlite.db.SupportSQLiteDatabase db); } @RestrictTo({androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX}) public final class FileUtil {
diff --git a/room/room-runtime/src/androidMain/java/androidx/room/paging/CursorSQLiteStatement.android.kt b/room/room-runtime/src/androidMain/java/androidx/room/paging/CursorSQLiteStatement.android.kt new file mode 100644 index 0000000..75b740c --- /dev/null +++ b/room/room-runtime/src/androidMain/java/androidx/room/paging/CursorSQLiteStatement.android.kt
@@ -0,0 +1,93 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.room.paging + +import android.database.Cursor +import android.database.Cursor.FIELD_TYPE_BLOB +import android.database.Cursor.FIELD_TYPE_FLOAT +import android.database.Cursor.FIELD_TYPE_INTEGER +import android.database.Cursor.FIELD_TYPE_NULL +import android.database.Cursor.FIELD_TYPE_STRING +import androidx.annotation.RestrictTo +import androidx.sqlite.SQLITE_DATA_BLOB +import androidx.sqlite.SQLITE_DATA_FLOAT +import androidx.sqlite.SQLITE_DATA_INTEGER +import androidx.sqlite.SQLITE_DATA_NULL +import androidx.sqlite.SQLITE_DATA_TEXT +import androidx.sqlite.SQLiteStatement + +/** SQLiteStatement backed by a Cursor used for backwards compatibility of Paging2 APIs. */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +class CursorSQLiteStatement(private val cursor: Cursor) : SQLiteStatement { + override fun getBlob(index: Int): ByteArray = cursor.getBlob(index) + + override fun getDouble(index: Int): Double = cursor.getDouble(index) + + override fun getLong(index: Int): Long = cursor.getLong(index) + + override fun getText(index: Int): String = cursor.getString(index) + + override fun isNull(index: Int): Boolean = cursor.isNull(index) + + override fun getColumnCount(): Int = cursor.columnCount + + override fun getColumnName(index: Int): String = cursor.getColumnName(index) + + override fun getColumnType(index: Int): Int = cursor.getDataType(index) + + override fun step(): Boolean = cursor.moveToNext() + + override fun reset() { + cursor.moveToPosition(-1) + } + + override fun close() { + cursor.close() + } + + override fun bindBlob(index: Int, value: ByteArray) = + error("Only get*() calls are allowed on a Cursor backed SQLiteStatement") + + override fun bindDouble(index: Int, value: Double) = + error("Only get*() calls are allowed on a Cursor backed SQLiteStatement") + + override fun bindLong(index: Int, value: Long) = + error("Only get*() calls are allowed on a Cursor backed SQLiteStatement") + + override fun bindText(index: Int, value: String) = + error("Only get*() calls are allowed on a Cursor backed SQLiteStatement") + + override fun bindNull(index: Int) = + error("Only get*() calls are allowed on a Cursor backed SQLiteStatement") + + override fun clearBindings() = + error("Only get*() calls are allowed on a Cursor backed SQLiteStatement") + + companion object { + private fun Cursor.getDataType(index: Int): Int { + val fieldType = this.getType(index) + return when (this.getType(index)) { + FIELD_TYPE_NULL -> SQLITE_DATA_NULL + FIELD_TYPE_INTEGER -> SQLITE_DATA_INTEGER + FIELD_TYPE_FLOAT -> SQLITE_DATA_FLOAT + FIELD_TYPE_STRING -> SQLITE_DATA_TEXT + FIELD_TYPE_BLOB -> SQLITE_DATA_BLOB + else -> error("Unknown field type: $fieldType") + } + } + } +}
diff --git a/room/room-runtime/src/androidMain/java/androidx/room/paging/LimitOffsetDataSource.java b/room/room-runtime/src/androidMain/java/androidx/room/paging/LimitOffsetDataSource.java index 2b5c391..3cdc6c0 100644 --- a/room/room-runtime/src/androidMain/java/androidx/room/paging/LimitOffsetDataSource.java +++ b/room/room-runtime/src/androidMain/java/androidx/room/paging/LimitOffsetDataSource.java
@@ -23,6 +23,7 @@ import androidx.room.InvalidationTracker; import androidx.room.RoomDatabase; import androidx.room.RoomSQLiteQuery; +import androidx.sqlite.SQLiteStatement; import androidx.sqlite.db.SupportSQLiteQuery; import java.util.Collections; @@ -145,7 +146,21 @@ @NonNull @SuppressWarnings("WeakerAccess") - protected abstract List<T> convertRows(@NonNull Cursor cursor); + protected List<T> convertRows(@NonNull Cursor cursor) { + return convertRows(new CursorSQLiteStatement(cursor)); + } + + @NonNull + protected List<T> convertRows(@NonNull SQLiteStatement statement) { + throw new UnsupportedOperationException( + "Unexpected call to a function with no implementation that Room is supposed to " + + "generate. Please file a bug at: " + + BUG_LINK + "." + ); + } + + private static final String BUG_LINK = + "https://issuetracker.google.com/issues/new?component=413107&template=1096568"; @SuppressWarnings("deprecation") @Override
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 8cb5d70..7faeb6a 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
@@ -234,3 +234,8 @@ fun createCancellationSignal(): CancellationSignal { return CancellationSignal() } + +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) +fun toSQLiteConnection(db: SupportSQLiteDatabase): SQLiteConnection { + return SupportSQLiteConnection(db) +}