Skip to content

Commit

Permalink
Fix IndexOutOfBounds after getRefreshKey
Browse files Browse the repository at this point in the history
It's possible that on a paging source that's not in the initial
generation, we fail with an `IndexOutOfBoundsException`. This
is caused when the underlying data source undergoes a delete, which
then invalidates the paging source, which then calls
`getRefreshKey`. In the event that the the refresh key is the
very last index of the previous paging source, we would fail
with the above exception because that index does not exist
in the next generation paging source.

Closes cashapp#2434.
  • Loading branch information
kevincianfarini committed Jul 15, 2021
1 parent b50f0b8 commit db8bca5
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ internal class OffsetQueryPagingSource<RowType : Any>(
params: LoadParams<Long>
): LoadResult<Long, RowType> = withContext(dispatcher) {
try {
val key = params.key ?: 0L
transacter.transactionWithResult {
val count = countQuery.executeAsOne()
val key = when (params) {
is LoadParams.Refresh -> params.key?.let { notNullKey -> minOf(count - 1, notNullKey) }
else -> params.key
} ?: 0L

if (count != 0L && key >= count) throw IndexOutOfBoundsException()

val loadSize = if (key < 0) params.loadSize + key else params.loadSize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.squareup.sqldelight.android.paging3

import androidx.paging.*
import androidx.paging.PagingSource.LoadParams.Refresh
import androidx.paging.PagingSource.LoadResult
import com.squareup.sqldelight.Query
Expand All @@ -23,6 +24,7 @@ import com.squareup.sqldelight.TransacterImpl
import com.squareup.sqldelight.db.SqlDriver
import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineDispatcher
import org.junit.After
Expand All @@ -47,8 +49,7 @@ class OffsetQueryPagingSourceTest {
transacter = object : TransacterImpl(driver) {}
}

@After
fun after() {
@After fun after() {
File("test.db").delete()
}

Expand Down Expand Up @@ -241,11 +242,25 @@ class OffsetQueryPagingSourceTest {

runBlocking {
assertFailsWith<IndexOutOfBoundsException> {
source.load(Refresh(10, 2, false))
source.load(PagingSource.LoadParams.Append(10, 2, false))
}
}
}

@Test fun `refresh key too big gets truncated`() {
val source = OffsetQueryPagingSource(
this::query,
countQuery(),
transacter,
TestCoroutineDispatcher()
)

runBlocking {
val results = source.load(Refresh(10, 2, false))
assertEquals(listOf(9L), (results as LoadResult.Page).data)
}
}

@Test fun `query invaliation invalidates paging source`() {
val query = query(2, 0)
val source = OffsetQueryPagingSource(
Expand Down

0 comments on commit db8bca5

Please sign in to comment.