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

tamer.db.DbSetup.scala Maven / Gradle / Ivy

The newest version!
package tamer
package db

import java.time.Instant

import doobie.Query0
import doobie.implicits._
import doobie.util.transactor.Transactor
import fs2.Stream
import log.effect.zio.ZioLogWriter.log4sFromName
import zio._
import zio.interop.catz._

sealed abstract case class DbSetup[K: Tag, V: Tag, SV: Tag: Hashable](
    initialState: SV,
    recordFrom: (SV, V) => Record[K, V],
    query: SV => Query0[V],
    stateFold: (SV, QueryResult[V]) => UIO[SV]
)(
    implicit ev: SerdesProvider[K, V, SV]
) extends Setup[Transactor[Task] with DbConfig, K, V, SV] {

  private[this] final val sql              = query(initialState).sql
  private[this] final val queryHash        = sql.hash
  private[this] final val initialStateHash = initialState.hash

  override final val stateKey = queryHash + initialStateHash
  override final val repr: String =
    s"""query:              $sql
       |query hash:         $queryHash
       |initial state:      $initialState
       |initial state hash: $initialStateHash
       |state key:          $stateKey
       |""".stripMargin

  import compat._

  private[this] final val logTask = log4sFromName.provideEnvironment(ZEnvironment("tamer.db"))

  private[this] final def process(query: Query0[V], chunkSize: Int, tx: Transactor[Task], queue: Enqueue[NonEmptyChunk[Record[K, V]]], state: SV) =
    query
      .streamWithChunkSize(chunkSize)
      .chunks
      .transact(tx)
      .map(ChunkWithMetadata(_))
      .evalTap { c =>
        val chunk = Chunk.fromIterable(c.chunk.iterator.to(LazyList).map(recordFrom(state, _)))
        NonEmptyChunk.fromChunk(chunk).map(queue.offer).getOrElse(ZIO.unit)
      }
      .flatMap(c => Stream.chunk(c.chunk).map(ValueWithMetadata(_, c.pulledAt)))
      .compile
      .toList
      .map(values => values -> values.headOption.map(_.pulledAt).getOrElse(java.lang.System.nanoTime()))

  override def iteration(currentState: SV, queue: Enqueue[NonEmptyChunk[Record[K, V]]]): RIO[Transactor[Task] with DbConfig, SV] = for {
    log        <- logTask
    transactor <- ZIO.service[Transactor[Task]]
    chunkSize  <- ZIO.service[DbConfig].map(_.fetchChunkSize)
    query      <- ZIO.succeed(query(currentState))
    _          <- log.debug(s"running ${query.sql} with params derived from $currentState")
    start      <- Clock.nanoTime
    tuple      <- process(query, chunkSize, transactor, queue, currentState)
    (values, time) = tuple
    newState <- stateFold(currentState, QueryResult(ResultMetadata(time - start), values.map(_.value)))
  } yield newState
}

object DbSetup {
  final def apply[K: Tag, V: Tag, SV: Tag: Hashable](
      initialState: SV
  )(
      query: SV => Query0[V]
  )(
      recordFrom: (SV, V) => Record[K, V],
      stateFold: (SV, QueryResult[V]) => UIO[SV]
  )(
      implicit ev: SerdesProvider[K, V, SV]
  ): DbSetup[K, V, SV] = new DbSetup(initialState, recordFrom, query, stateFold) {}

  final def tumbling[K: Tag, V <: Timestamped: Tag: Ordering](
      query: Window => Query0[V]
  )(
      recordFrom: (Window, V) => Record[K, V],
      from: Instant = Instant.now,
      tumblingStep: Duration = 5.minutes,
      lag: Duration = 0.seconds
  )(
      implicit ev: SerdesProvider[K, V, Window]
  ): DbSetup[K, V, Window] = {
    def stateFold(currentWindow: Window, queryResult: QueryResult[V]): UIO[Window] =
      if (queryResult.results.isEmpty) {
        Clock.instant.map(now => Window(currentWindow.from, (currentWindow.to + tumblingStep).or(now, lag)))
      } else {
        val mostRecent = queryResult.results.max.timestamp
        Clock.instant.map(now => Window(mostRecent, (mostRecent + tumblingStep).or(now, lag)))
      }

    DbSetup(Window(from, from + tumblingStep))(query)(recordFrom, stateFold)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy