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

a8.sync.DocumentLoader.scala Maven / Gradle / Ivy

The newest version!
package a8.sync


import a8.shared.jdbcf.SqlString._
import a8.shared.jdbcf.{Conn, Row, RowReader, SchemaName, SqlString, TableLocator, TableName, unsafe}
import a8.sync.Imports._

import java.time.LocalDateTime
import java.util.concurrent.atomic.AtomicReference
import a8.shared.json.DynamicJson
import a8.shared.json.ast._

import scala.concurrent.duration.Duration
import zio._

object DocumentLoader {

  object RowToJsObj {
    implicit val rowReader: RowReader[RowToJsObj] =
      new RowReader[RowToJsObj] {
        override def rawRead(row: Row, index: Int): (RowToJsObj, Int) =
          RowToJsObj(
            row
              .subRow(index)
              .unsafeAsJsObj
          ) -> (row.size - index)
      }
  }
  case class RowToJsObj(value: JsObj)

  case class CacheableLoader(cacheDuration: Duration, loader: DocumentLoader) extends AbstractDocumentLoader {

    val lastLoad = new AtomicReference[Option[(LocalDateTime, JsVal)]](None)

    def cachedValue: Option[JsVal] =
      lastLoad.get match {
        case None =>
          None
        case Some((dt, value)) =>
          val now = LocalDateTime.now()
          val timeout = (dt + cacheDuration)
          if ( timeout.isBefore(now) ) {
            None
          } else {
            Some(value)
          }
      }

    def load: RIO[Any,JsVal] = {
      ZIO.suspend {
        cachedValue match {
          case Some(value) =>
            ZIO.succeed(value)
          case None =>
            loader
              .load
              .map { doc =>
                lastLoad.set(Some(LocalDateTime.now() -> doc))
                doc
              }
        }
      }
    }

  }

  def cache(loader: DocumentLoader, cacheDuration: Option[Duration]): DocumentLoader = {
    cacheDuration
      .map(d => CacheableLoader(d, loader))
      .getOrElse(loader)
  }

  def queryIntoMap(connFactory: Resource[Conn], keyExpr: SqlString, valueExpr: SqlString, schemaName: Option[SchemaName], tableName: TableName, whereExpr: Option[SqlString] = None, cacheDuration: Option[Duration] = None): DocumentLoader = {
    val queryLoader =
      new AbstractDocumentLoader {
        override def load: Task[JsVal] = {
          connFactory.use { conn =>
            val whereClause = whereExpr.map(e => q" where ${e}")
            val schemaPrefix = schemaName.map(sn => q"${sn}${conn.dialect.schemaSeparator}").getOrElse(q"")
            val sql = q"select ${keyExpr}, ${valueExpr} from ${schemaPrefix}${tableName}${whereClause}"
            conn
              .query[(String,JsVal)](sql)
              .select
              .map { rows =>
                JsObj(
                  rows.toMap
                )
              }
          }
        }
      }
    cache(queryLoader, cacheDuration)
  }

  def query(connFactory: Resource[Conn], schemaName: Option[SchemaName], tableName: TableName, whereExpr: Option[SqlString] = None, cacheDuration: Option[Duration] = None): DocumentLoader = {
    val queryLoader =
      new AbstractDocumentLoader {
        override def load: Task[JsVal] = {
          connFactory.use { conn =>
            for {
              resolvedJdbcTable <- conn.tableMetadata(TableLocator(schemaName, tableName))
              sql = resolvedJdbcTable.querySql(whereExpr)
              rowsToJsObj <-
                conn
                  .query[RowToJsObj](sql)
                  .select
            } yield JsArr(rowsToJsObj.map(_.value).toList)
          }
        }
      }

    cache(queryLoader, cacheDuration)
  }


  def jobject(fields: (String,DocumentLoader)*):  DocumentLoader  = {
    val vfields = fields.toVector
    new AbstractDocumentLoader {
      override def load: Task[JsVal] =
        vfields
          .map { case (fieldName, loader) =>
            loader
              .load
              .map(fieldName -> _)
          }
          .sequence
          .map(v => JsObj(v.toMap))
    }
  }

  abstract class AbstractDocumentLoader extends DocumentLoader {
    def loadAsDynamicJson: ZIO[Any,Throwable,DynamicJson] = load.map(DynamicJson.apply)

  }

}


sealed trait DocumentLoader {
  def load: Task[JsVal]
  def loadAsDynamicJson: Task[DynamicJson]
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy