Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ interface OutboxStore {
* [OutboxEntry.createdAt] of its oldest entry (the minimum `createdAt` among entries in
* that status). Statuses with no entries are omitted from the result -- callers rely on
* absence to mean "no backlog" (e.g. the lag gauge reports 0 for an omitted status).
* Useful for lag metrics.
* An empty [statuses] set returns an empty map.
*/
fun findOldestCreatedAt(statuses: Set<OutboxStatus>): Map<OutboxStatus, Instant>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,16 @@ fun FunSpec.outboxStoreContractTests(
oldest.keys shouldBe setOf(OutboxStatus.PENDING)
}

test("[$dbName] findOldestCreatedAt returns an empty map for an empty statuses set") {
// Regression guard (issue #60): an empty `statuses` set used to build `WHERE status IN ()`,
// a syntax error on both Postgres and MySQL. Must short-circuit to emptyMap() instead.
jdbc.withTransaction { store.persist(createTestEntry()) }

val oldest = jdbc.withTransaction { store.findOldestCreatedAt(emptySet()) }

oldest shouldBe emptyMap()
}

test("[$dbName] claimPending returns empty when no PENDING entries") {
val claimed = jdbc.withTransaction { store.claimPending(10) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ class MysqlOutboxStore(
Timestamp.from(entry.lastAttempt),
)
} else {
stmt.setNull(9, java.sql.Types.TIMESTAMP)
stmt.setNull(9, Types.TIMESTAMP)
}
if (entry.lastError != null) stmt.setString(10, entry.lastError) else stmt.setNull(10, java.sql.Types.VARCHAR)
if (entry.lastError != null) stmt.setString(10, entry.lastError) else stmt.setNull(10, Types.VARCHAR)
stmt.setString(11, entry.deliveryMetadata)
stmt.executeUpdate()
}
Expand Down Expand Up @@ -102,6 +102,8 @@ class MysqlOutboxStore(
}

override fun findOldestCreatedAt(statuses: Set<OutboxStatus>): Map<OutboxStatus, Instant> {
if (statuses.isEmpty()) return emptyMap()

val result = mutableMapOf<OutboxStatus, Instant>()
val placeholders = statuses.joinToString(",") { "?" }
val sql = "SELECT status, MIN(created_at) AS min_created_at FROM okapi_outbox WHERE status IN ($placeholders) GROUP BY status"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ class PostgresOutboxStore(
Timestamp.from(entry.lastAttempt),
)
} else {
stmt.setNull(9, java.sql.Types.TIMESTAMP)
stmt.setNull(9, Types.TIMESTAMP)
}
if (entry.lastError != null) stmt.setString(10, entry.lastError) else stmt.setNull(10, java.sql.Types.VARCHAR)
if (entry.lastError != null) stmt.setString(10, entry.lastError) else stmt.setNull(10, Types.VARCHAR)
stmt.setString(11, entry.deliveryMetadata)
stmt.executeUpdate()
}
Expand Down Expand Up @@ -96,6 +96,8 @@ class PostgresOutboxStore(
}

override fun findOldestCreatedAt(statuses: Set<OutboxStatus>): Map<OutboxStatus, Instant> {
if (statuses.isEmpty()) return emptyMap()

val result = mutableMapOf<OutboxStatus, Instant>()
val placeholders = statuses.joinToString(",") { "?" }
val sql = "SELECT status, MIN(created_at) AS min_created_at FROM okapi_outbox WHERE status IN ($placeholders) GROUP BY status"
Expand Down