main.app.cash.backfila.client.jooq.internal.OpenKeyRange.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of client-jooq Show documentation
Show all versions of client-jooq Show documentation
Backfila is a service that manages backfill state, calling into other services to do batched work.
package app.cash.backfila.client.jooq.internal
import app.cash.backfila.client.jooq.CompoundKeyComparer
import app.cash.backfila.client.jooq.CompoundKeyComparisonOperator
import app.cash.backfila.client.jooq.JooqBackfill
import app.cash.backfila.protos.clientservice.GetNextBatchRangeRequest
import org.jooq.Condition
import org.jooq.DSLContext
import org.jooq.Record
import org.jooq.impl.DSL.select
data class OpenKeyRange(
private val jooqBackfill: JooqBackfill,
/**
* Comparison function to use as a bound for the start value. Generally set to either
* `CompoundKeyComparer::gte` or `CompoundKeyComparer::gt` depending on whether the start of the
* range is inclusive or exclusive.
*/
private val startComparison: CompoundKeyComparisonOperator,
/**
* The start of the range.
*/
private val start: K,
/**
* The overall upper bound of the range.
*/
private val upperBound: K,
) {
fun determineStart(keyValues: List): K =
keyValues.firstOrNull() ?: start
fun determineEnd(keyValues: List): K =
keyValues.lastOrNull() ?: upperBound
/**
* Builds a jooq condition that limits a query to the start of the range and the overall upper
* bound.
*/
fun betweenStartAndUpperBoundCondition(): Condition {
return jooqBackfill.compareCompoundKey(start, startComparison)
.and(
jooqBackfill.compareCompoundKey(upperBound) { keyCompare, compoundKeyValue ->
keyCompare.lte(compoundKeyValue)
},
)
}
/**
* Builds a jooq condition that limits a query to the start of the range and the provided
* `end`.
*/
fun betweenStartAndEndCondition(end: K): Condition {
return jooqBackfill.compareCompoundKey(start, startComparison)
.and(
jooqBackfill.compareCompoundKey(end) { keyCompare, compoundKeyValue ->
keyCompare.lte(compoundKeyValue)
},
)
}
/**
* Builds a range that comes after the provided `end`.
*/
fun nextRangeFor(end: K): OpenKeyRange {
return OpenKeyRange(
jooqBackfill = jooqBackfill,
upperBound = upperBound,
start = end,
startComparison = { keyCompare: CompoundKeyComparer, compoundKeyValue: Record ->
keyCompare.gt(compoundKeyValue)
},
)
}
companion object {
/**
* Builds the initial range that we start with when iterating over a sequence of ranges.
*/
fun initialRangeFor(
jooqBackfill: JooqBackfill,
request: GetNextBatchRangeRequest,
session: DSLContext,
): OpenKeyRange {
// If this is the first batch, we want to start with the provided value on the backfila
// screen. If not, then, we need to start with one after the previous end value
val startComparison: CompoundKeyComparisonOperator =
if (request.previous_end_key == null) {
{ keyComparer: CompoundKeyComparer, compoundKeyValue: Record ->
keyComparer.gte(compoundKeyValue)
}
} else {
{ keyComparer: CompoundKeyComparer, compoundKeyValue: Record ->
keyComparer.gt(
compoundKeyValue,
)
}
}
val start = request.previous_end_key
?.let {
jooqBackfill.fromByteString(request.previous_end_key)
} ?: jooqBackfill.fromByteString(request.backfill_range.start)
return OpenKeyRange(
jooqBackfill = jooqBackfill,
start = start,
startComparison = startComparison,
upperBound = computeUpperBound(
jooqBackfill, request, session,
jooqBackfill.compareCompoundKey(start, startComparison),
),
)
}
/**
* Get the upper bound of the scan range without applying any backfill filter conditions.
* This is so that we can restrict the upper bound when the filter is applied subsequently.
* If we don't restrict the range, then the query could perform badly.
*
* This SQL is essentially going to look like
*
* select
* from (
* select
* from
* where (backfill fields > end of previous range or >= start of backfill)
* order by asc
* limit
* )
* order by desc
* limit 1
*
*/
private fun computeUpperBound(
jooqBackfill: JooqBackfill,
request: GetNextBatchRangeRequest,
session: DSLContext,
afterPreceedingRowsCondition: Condition,
): K {
return if (request.backfill_range != null && request.backfill_range.end != null) {
jooqBackfill.fromByteString(request.backfill_range.end)
} else {
session
.select(jooqBackfill.compoundKeyFields)
.from(
select(jooqBackfill.compoundKeyFields)
.from(jooqBackfill.table)
.where(afterPreceedingRowsCondition)
.orderBy(jooqBackfill.sortingByCompoundKeyFields { it.asc() })
.limit(request.scan_size),
)
.orderBy(jooqBackfill.sortingByCompoundKeyFields { it.desc() })
.limit(1)
.fetchOne { jooqBackfill.recordToKey(it) }
?: throw IllegalStateException(
"Expecting a row when calculating the upper bound",
)
}
}
}
}