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

harness.sql.query.QueryResult.scala Maven / Gradle / Ivy

package harness.sql.query

import cats.syntax.option.*
import harness.serviceTracer.*
import harness.sql.*
import harness.sql.error.QueryError
import harness.sql.typeclass.*
import harness.zio.*
import zio.*
import zio.stream.*

final class QueryResult[O] private (queryName: String, fragment: Fragment, _stream: => ZStream[Database & Scope, QueryError, O]) {

  def single: ZIO[Database, QueryError, O] =
    chunk.flatMap {
      case Chunk(value) => ZIO.succeed(value)
      case chunk        => ZIO.fail(QueryError(queryName, fragment.sql, QueryError.Cause.InvalidResultSetSize("1", chunk.length)))
    }

  def single[E](onMissing: => E)(implicit errorMapper: ErrorMapper[QueryError, E]): ZIO[Database, E, O] =
    option.mapError(errorMapper.mapError).someOrFail(onMissing)

  def option: ZIO[Database, QueryError, Option[O]] =
    chunk.flatMap {
      case Chunk(value) => ZIO.some(value)
      case Chunk()      => ZIO.none
      case chunk        => ZIO.fail(QueryError(queryName, fragment.sql, QueryError.Cause.InvalidResultSetSize("0..1", chunk.length)))
    }

  inline def list: ZIO[Database, QueryError, List[O]] = chunk.map(_.toList)

  inline def chunk: ZIO[Database, QueryError, Chunk[O]] =
    ZIO.scoped { _stream.runCollect } @@
      Telemetry.telemetrize("Executed SQL query", Logger.LogLevel.Trace, "query-name" -> queryName) @@
      ServiceTracer.trace(TraceClosure("Database", "Live", "read"), "query-name" -> queryName)

  def stream: ZStream[Database & Scope, QueryError, O] =
    ZStream.fromZIO { ServiceTracer.traceScoped(TraceClosure("Database", "Live", "read-stream"), "query-name" -> queryName) } *> _stream

  // =====|  |=====

  // NOTE : Make sure results are ordered by `K`
  def groupBy[K, V](kf: O => K)(vf: O => V): ZStream[Database & Scope, QueryError, (K, NonEmptyChunk[V])] =
    stream.groupAdjacentBy(kf).map { (k, os) => (k, os.map(vf)) }

  // NOTE : Make sure results are ordered by `K`
  def groupByLeft[K, V](kf: O => K)(vf: O => Option[V]): QueryResult[(K, Chunk[V])] =
    QueryResult(
      queryName,
      fragment,
      _stream.groupAdjacentBy(kf).map { case (k, os) => (k, os.map(vf)) }.mapZIO { case (k, chunk) =>
        if (chunk.length == 1 && chunk(0).isEmpty) ZIO.succeed(k, Chunk.empty)
        else if (chunk.forall(_.nonEmpty)) ZIO.succeed((k, chunk.map(_.get)))
        else ZIO.dieMessage("GroupBy has unexpected results")
      },
    )

  // NOTE : Make sure results are ordered by `K`
  def groupByLeftOpt[K, V](kf: O => K)(vf: O => Option[V]): QueryResult[(K, Option[NonEmptyChunk[V]])] =
    QueryResult(
      queryName,
      fragment,
      _stream.groupAdjacentBy(kf).map { case (k, os) => (k, os.map(vf)) }.mapZIO { case (k, chunk) =>
        if (chunk.length == 1 && chunk(0).isEmpty) ZIO.succeed(k, None)
        else if (chunk.forall(_.nonEmpty)) ZIO.succeed((k, chunk.map(_.get).some))
        else ZIO.dieMessage("GroupBy has unexpected results")
      },
    )

}
object QueryResult {

  private[query] def stream[I, O](
      queryName: String,
      fragment: Fragment,
      input: Option[(I, Input[I, ?])],
      decoder: QueryDecoderMany[O],
  ): QueryResult[O] =
    QueryResult(
      queryName,
      fragment,
      ZStream
        .fromZIO {
          for {
            ps <- PreparedStatement.make(queryName, fragment)
            _ <- ZIO.foreachDiscard(input)(ps.writeSingle(_, _))
            rs <- ps.executeQuery(decoder)
          } yield rs
        }
        .flatMap { resultSet =>
          ZStream.repeatZIOOption { resultSet.decodeRowOpt }
        },
    )

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy