All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.app.cash.backfila.client.jooq.internal.OpenKeyRange.kt Maven / Gradle / Ivy

Go to download

Backfila is a service that manages backfill state, calling into other services to do batched work.

There is a newer version: 2024.10.28.205607-fab304f
Show newest version
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", ) } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy