mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
fix(quartz): clamp non-positive FTS reindex batch size
A batchSize <= 0 made the SQLite resumable reindex select no rows yet never report done, so a caller's loop would spin forever. Clamp the page size to at least one in both stores and add a regression test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZqPFds2TPPUKkMmBngwys
This commit is contained in:
+5
-2
@@ -199,6 +199,9 @@ class FullTextSearchModule : IModule {
|
||||
afterRowId: Long,
|
||||
batchSize: Int,
|
||||
): FtsReindexProgress {
|
||||
// A non-positive page would select no rows yet never report done,
|
||||
// spinning the caller's loop forever — clamp to at least one.
|
||||
val limit = batchSize.coerceAtLeast(1)
|
||||
val kinds = searchableKindsPresent(db)
|
||||
if (kinds.isEmpty()) return FtsReindexProgress(cursor = null, processedThisBatch = 0, done = true)
|
||||
|
||||
@@ -213,7 +216,7 @@ class FullTextSearchModule : IModule {
|
||||
db.prepare(insertFTS).use { write ->
|
||||
db.prepare(selectSql).use { read ->
|
||||
read.bindLong(1, afterRowId)
|
||||
read.bindLong(2, batchSize.toLong())
|
||||
read.bindLong(2, limit.toLong())
|
||||
while (read.step()) {
|
||||
val rowId = read.getLong(0)
|
||||
// Clear any existing row for this event first so a
|
||||
@@ -246,7 +249,7 @@ class FullTextSearchModule : IModule {
|
||||
}
|
||||
|
||||
// Fewer than a full page came back ⇒ we hit the end of the table.
|
||||
val done = processed < batchSize
|
||||
val done = processed < limit
|
||||
return FtsReindexProgress(
|
||||
cursor = if (done) null else last.toString(),
|
||||
processedThisBatch = processed,
|
||||
|
||||
+21
@@ -299,6 +299,27 @@ class SearchTest : BaseDBTest() {
|
||||
db.store.assertQuery(b, Filter(search = "uniqbeta"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testResumableReindexClampsNonPositiveBatchSize() =
|
||||
forEachDB { db ->
|
||||
val a = signer.sign(TextNoteEvent.build("uniqclamp note", createdAt = TimeUtils.now()))
|
||||
db.store.insertEvent(a)
|
||||
db.store.pool.useWriter { db.store.fullTextSearchModule.deleteAll(it) }
|
||||
|
||||
// batchSize 0 must not spin forever: it is clamped to one event
|
||||
// per call. Cap the loop so a regression fails fast instead of
|
||||
// hanging the suite.
|
||||
var cursor: String? = null
|
||||
var guard = 0
|
||||
do {
|
||||
val progress = db.store.reindexFullTextSearch(cursor, batchSize = 0)
|
||||
cursor = progress.cursor
|
||||
check(guard++ < 100) { "reindex did not terminate with batchSize=0" }
|
||||
} while (!progress.done)
|
||||
|
||||
db.store.assertQuery(a, Filter(search = "uniqclamp"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChannelJsonFieldsAreSearchable() =
|
||||
forEachDB { db ->
|
||||
|
||||
+2
-1
@@ -554,6 +554,7 @@ open class FsEventStore(
|
||||
Files.createDirectories(layout.idxFts)
|
||||
|
||||
val resumeKind = resumeFrom?.toIntOrNull()
|
||||
val target = batchSize.coerceAtLeast(1)
|
||||
val pending =
|
||||
Files
|
||||
.list(layout.idxKind)
|
||||
@@ -575,7 +576,7 @@ open class FsEventStore(
|
||||
}
|
||||
}
|
||||
index++
|
||||
if (processed >= batchSize) break
|
||||
if (processed >= target) break
|
||||
}
|
||||
|
||||
val done = index >= pending.size
|
||||
|
||||
Reference in New Issue
Block a user