diff --git a/extensions/android-paging3/android-test/src/test/java/app/cash/sqldelight/paging3/OffsetQueryPagingSourceTest.kt b/extensions/android-paging3/android-test/src/test/java/app/cash/sqldelight/paging3/OffsetQueryPagingSourceTest.kt index f72725eb3a5..95eb76fc0a8 100644 --- a/extensions/android-paging3/android-test/src/test/java/app/cash/sqldelight/paging3/OffsetQueryPagingSourceTest.kt +++ b/extensions/android-paging3/android-test/src/test/java/app/cash/sqldelight/paging3/OffsetQueryPagingSourceTest.kt @@ -629,14 +629,40 @@ class OffsetQueryPagingSourceTest { assertTrue(pagingSource.jumpingSupported) } - private fun query(limit: Int, offset: Int) = object : Query( + @Test + fun load_initialEmptyLoad_QueryPagingSourceLong() = runTest { + val pagingSource = QueryPagingSource( + countQueryLong(), + transacter, + EmptyCoroutineContext, + ::queryLong, + ) + val result = pagingSource.refresh() as LoadResult.Page + + assertTrue(result.data.isEmpty()) + + // now add items + insertItems(ITEMS_LIST) + + // invalidate pagingSource to imitate invalidation from running refreshVersionSync + pagingSource.invalidate() + assertTrue(pagingSource.invalid) + + // this refresh should check pagingSource's invalid status, realize it is invalid, and + // return a LoadResult.Invalid + assertThat(pagingSource.refresh()).isInstanceOf(LoadResult.Invalid::class.java) + } + + private fun query(limit: Int, offset: Int) = queryLong(limit.toLong(), offset.toLong()) + + private fun queryLong(limit: Long, offset: Long) = object : Query( { cursor -> TestItem(cursor.getLong(0)!!) }, ) { override fun execute(mapper: (SqlCursor) -> R) = driver.executeQuery(1, "SELECT id FROM TestItem LIMIT ? OFFSET ?", mapper, 2) { - bindLong(0, limit.toLong()) - bindLong(1, offset.toLong()) + bindLong(0, limit) + bindLong(1, offset) } override fun addListener(listener: Listener) = driver.addListener(listener, arrayOf("TestItem")) @@ -653,6 +679,16 @@ class OffsetQueryPagingSourceTest { { it.getLong(0)!!.toInt() }, ) + private fun countQueryLong() = Query( + 2, + arrayOf("TestItem"), + driver, + "Test.sq", + "count", + "SELECT count(*) FROM TestItem", + { it.getLong(0)!! }, + ) + private fun insertItems(items: List) { items.forEach { driver.execute(0, "INSERT INTO TestItem (id) VALUES (?)", 1) { diff --git a/extensions/android-paging3/src/main/java/app/cash/sqldelight/paging3/QueryPagingSource.kt b/extensions/android-paging3/src/main/java/app/cash/sqldelight/paging3/QueryPagingSource.kt index ec90db5fcec..19936d89e18 100644 --- a/extensions/android-paging3/src/main/java/app/cash/sqldelight/paging3/QueryPagingSource.kt +++ b/extensions/android-paging3/src/main/java/app/cash/sqldelight/paging3/QueryPagingSource.kt @@ -19,6 +19,7 @@ import androidx.paging.PagingConfig import androidx.paging.PagingSource import app.cash.sqldelight.Query import app.cash.sqldelight.Transacter +import app.cash.sqldelight.db.SqlCursor import kotlinx.coroutines.Dispatchers import kotlin.coroutines.CoroutineContext import kotlin.properties.Delegates @@ -58,6 +59,7 @@ internal abstract class QueryPagingSource : * Queries will be executed on [context]. */ @Suppress("FunctionName") +@JvmName("QueryPagingSourceInt") fun QueryPagingSource( countQuery: Query, transacter: Transacter, @@ -70,6 +72,36 @@ fun QueryPagingSource( context, ) +/** + * Variant of [QueryPagingSource] that accepts a [Long] instead of an [Int] for [countQuery] + * and [queryProvider]. + * + * If the result of [countQuery] exceeds [Int.MAX_VALUE], then the count will be truncated + * to the least significant 32 bits of this [Long] value. + * + * @see toInt + */ +@Suppress("FunctionName") +@JvmName("QueryPagingSourceLong") +fun QueryPagingSource( + countQuery: Query, + transacter: Transacter, + context: CoroutineContext = Dispatchers.IO, + queryProvider: (limit: Long, offset: Long) -> Query, +): PagingSource = OffsetQueryPagingSource( + { limit, offset -> queryProvider(limit.toLong(), offset.toLong()) }, + countQuery.toInt(), + transacter, + context, +) + +private fun Query.toInt(): Query = + object : Query({ cursor -> mapper(cursor).toInt() }) { + override fun execute(mapper: (SqlCursor) -> R) = this@toInt.execute(mapper) + override fun addListener(listener: Listener) = this@toInt.addListener(listener) + override fun removeListener(listener: Listener) = this@toInt.removeListener(listener) + } + /** * Create a [PagingSource] that pages through results according to queries generated by * [queryProvider]. Queries returned by [queryProvider] should expected to do keyset paging.