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

com.mchange.feedletter.db.core.scala Maven / Gradle / Ivy

package com.mchange.feedletter.db

import com.mchange.feedletter.*

import zio.*
import java.sql.*
import java.time.Instant
import javax.sql.DataSource
import scala.util.control.NonFatal
import java.lang.System

import com.mchange.cryptoutil.*

enum MetadataKey:
  case SchemaVersion
  case CreatorAppVersion

object MailSpec:
  final case class WithHash(
    seqnum       : Long,
    templateHash : Hash.SHA3_256,
    from         : AddressHeader[From],
    replyTo      : Option[AddressHeader[ReplyTo]],
    to           : AddressHeader[To],
    subject : String,
    templateParams : TemplateParams,
    retried : Int
  )
  final case class WithTemplate(
    seqnum : Long,
    templateHash : Hash.SHA3_256,
    template : String,
    from : AddressHeader[From],
    replyTo : Option[AddressHeader[ReplyTo]],
    to : AddressHeader[To],
    subject : String,
    templateParams : TemplateParams,
    retried : Int
  )

def acquireConnection( ds : DataSource ) : Task[Connection] = ZIO.attemptBlocking( ds.getConnection )

private def _inTransactionZIO[T]( conn : Connection )( transactioningHappyPath : Connection => Task[T]) : Task[T] =
  val rollback : PartialFunction[Throwable,Task[T]] =
    case NonFatal(t) =>
      ZIO.attemptBlocking( conn.rollback() ) *> ZIO.fail(t)
  val resetAutocommit = ZIO.attemptBlocking( conn.setAutoCommit(true) ).logError.catchAll( _ => ZIO.unit )
  transactioningHappyPath(conn).catchSome( rollback ).ensuring( resetAutocommit )

def inTransaction[T]( conn : Connection )( op : Connection => T) : Task[T] =
  val transactioningHappyPath = (cxn : Connection) => ZIO.attemptBlocking:
    cxn.setAutoCommit(false)
    val out = op(cxn)
    cxn.commit()
    out
  _inTransactionZIO(conn)(transactioningHappyPath)

def inTransactionZIO[T]( conn : Connection )( op : Connection => Task[T]) : Task[T] =
  val transactioningHappyPath = (cxn : Connection ) =>
    for
      _ <- ZIO.attemptBlocking( cxn.setAutoCommit(false) )
      out <- op(cxn)
      _ <- ZIO.attemptBlocking( cxn.commit() )
    yield out
  _inTransactionZIO(conn)(transactioningHappyPath)

def releaseConnection( conn : Connection ) : UIO[Unit] = ZIO.succeed:
  try
    conn.close()
  catch
    case NonFatal(t) =>
      System.err.println("Best-attempt close() of Connection yielded a throwable!")
      t.printStackTrace()

def setStringOptional( ps : PreparedStatement, position : Int, sqlType : Int, value : Option[String] ) =
  value match
    case Some( s ) => ps.setString(position, s)
    case None      => ps.setNull( position, sqlType )

def setTimestampOptional( ps : PreparedStatement, position : Int, value : Option[Timestamp] ) =
  value match
    case Some( ts ) => ps.setTimestamp(position, ts)
    case None       => ps.setNull( position, Types.TIMESTAMP )

def setLongOptional( ps : PreparedStatement, position : Int, sqlType : Int, value : Option[Long] ) =
  value match
    case Some( l ) => ps.setLong(position, l)
    case None      => ps.setNull( position, sqlType )

def toSet[T]( rs : ResultSet )( extract : ResultSet => T ) : Set[T] =
  val builder = Set.newBuilder[T]
  while rs.next() do
    builder += extract(rs)
  builder.result()

def toSeq[T]( rs : ResultSet )( extract : ResultSet => T ) : Seq[T] =
  val builder = Seq.newBuilder[T]
  while rs.next() do
    builder += extract(rs)
  builder.result()

def uniqueResult[T]( queryDesc : String, rs : ResultSet )( materialize : ResultSet => T ) : T =
  if !rs.next() then
    throw new UnexpectedlyEmptyResultSet(s"Expected a value for ${queryDesc}, none found.")
  else
    val out = materialize(rs)
    if rs.next() then
      throw new NonUniqueRow(s"Expected a unique value for ${queryDesc}. Multiple rows found.")
    else
      out

def withConnection[T]( ds : DataSource )( operation : Connection => T ) : Task[T] =
  withConnectionZIO( ds )( conn => ZIO.attemptBlocking( operation(conn) ) )

def withConnectionZIO[T]( ds : DataSource )( operation : Connection => Task[T]) : Task[T] =
  ZIO.acquireReleaseWith(acquireConnection(ds))( releaseConnection )( operation )

def withConnectionTransactional[T]( ds : DataSource )( op : Connection => T) : Task[T] =
  withConnectionZIO(ds)( conn => inTransaction(conn)( op ) )

def withConnectionTransactionalZIO[T]( ds : DataSource )( op : Connection => Task[T] ) : Task[T] =
  withConnectionZIO(ds)( conn => inTransactionZIO(conn)( op ) )

def zeroOrOneResult[T]( queryDesc : String, rs : ResultSet )( materialize : ResultSet => T ) : Option[T] =
  if !rs.next() then
    None
  else
    val out = materialize(rs)
    if rs.next() then
      throw new NonUniqueRow(s"Expected a unique value for ${queryDesc}. Multiple rows found.")
    else
      Some(out)





© 2015 - 2025 Weber Informatics LLC | Privacy Policy