Skip to content

Commit

Permalink
Merge pull request #350 from takapi327/enhancement/2025-01-Make-Decod…
Browse files Browse the repository at this point in the history
…er-support-Either

Enhancement/2025 01 make decoder support either
  • Loading branch information
takapi327 authored Jan 2, 2025
2 parents 0e88467 + 13566f6 commit cb32ef0
Show file tree
Hide file tree
Showing 12 changed files with 251 additions and 112 deletions.
2 changes: 1 addition & 1 deletion module/ldbc-dsl/src/main/scala/ldbc/dsl/Mysql.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,6 @@ case class Mysql[F[_]: Temporal](statement: String, params: List[Parameter.Dynam
prepareStatement <- connection.prepareStatement(statement, Statement.RETURN_GENERATED_KEYS)
resultSet <- paramBind(prepareStatement, params) >> prepareStatement.executeUpdate() >> prepareStatement
.getGeneratedKeys()
result <- summon[ResultSetConsumer[F, T]].consume(resultSet) <* prepareStatement.close()
result <- summon[ResultSetConsumer[F, T]].consume(resultSet, statement) <* prepareStatement.close()
yield result
)
4 changes: 2 additions & 2 deletions module/ldbc-dsl/src/main/scala/ldbc/dsl/Query.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ object Query:
for
prepareStatement <- connection.prepareStatement(statement)
resultSet <- paramBind(prepareStatement, params) >> prepareStatement.executeQuery()
result <- summon[ResultSetConsumer[F, G[T]]].consume(resultSet) <* prepareStatement.close()
result <- summon[ResultSetConsumer[F, G[T]]].consume(resultSet, statement) <* prepareStatement.close()
yield result
)

Expand All @@ -66,6 +66,6 @@ object Query:
for
prepareStatement <- connection.prepareStatement(statement)
resultSet <- paramBind(prepareStatement, params) >> prepareStatement.executeQuery()
result <- summon[ResultSetConsumer[F, T]].consume(resultSet) <* prepareStatement.close()
result <- summon[ResultSetConsumer[F, T]].consume(resultSet, statement) <* prepareStatement.close()
yield result
)
35 changes: 23 additions & 12 deletions module/ldbc-dsl/src/main/scala/ldbc/dsl/ResultSetConsumer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import cats.syntax.all.*
import ldbc.sql.ResultSet
import ldbc.dsl.util.FactoryCompat
import ldbc.dsl.codec.Decoder
import ldbc.dsl.exception.DecodeFailureException

/**
* Trait for generating the specified data type from a ResultSet.
Expand All @@ -29,34 +30,44 @@ trait ResultSetConsumer[F[_], T]:
* @param resultSet
* A table of data representing a database result set, which is usually generated by executing a statement that
* queries the database.
* @param statement
* Statement that configured the ResultSet
* @return
* Type you want to build with data obtained from ResultSet
*/
def consume(resultSet: ResultSet): F[T]
def consume(resultSet: ResultSet, statement: String): F[T]

object ResultSetConsumer:

type Read[T] = ResultSet => T
private val FIRST_OFFSET = 1

given [F[_]: Monad, T](using
consumer: ResultSetConsumer[F, Option[T]],
error: MonadError[F, Throwable]
ev: MonadThrow[F]
): ResultSetConsumer[F, T] with
override def consume(resultSet: ResultSet): F[T] =
consumer.consume(resultSet).flatMap {
case Some(value) => error.pure(value)
case None => error.raiseError(new NoSuchElementException(""))
override def consume(resultSet: ResultSet, statement: String): F[T] =
consumer.consume(resultSet, statement).flatMap {
case Some(value) => ev.pure(value)
case None => ev.raiseError(new NoSuchElementException(""))
}

given [F[_]: Monad, T](using decoder: Decoder[T]): ResultSetConsumer[F, Option[T]] with
override def consume(resultSet: ResultSet): F[Option[T]] =
if resultSet.next() then Monad[F].pure(decoder.decode(resultSet, 1).some) else Monad[F].pure(None)
given [F[_]: Monad, T](using decoder: Decoder[T], ev: MonadThrow[F]): ResultSetConsumer[F, Option[T]] with
override def consume(resultSet: ResultSet, statement: String): F[Option[T]] =
if resultSet.next() then
decoder.decode(resultSet, FIRST_OFFSET) match
case Right(value) => Monad[F].pure(Some(value))
case Left(error) =>
ev.raiseError(new DecodeFailureException(error.message, decoder.offset, statement, error.cause))
else Monad[F].pure(None)

given [F[_]: Monad, T, G[_]](using
decoder: Decoder[T],
factoryCompat: FactoryCompat[T, G[T]]
): ResultSetConsumer[F, G[T]] with
override def consume(resultSet: ResultSet): F[G[T]] =
override def consume(resultSet: ResultSet, statement: String): F[G[T]] =
val builder = factoryCompat.newBuilder
while resultSet.next() do builder += decoder.decode(resultSet, 1)
while resultSet.next() do
decoder.decode(resultSet, FIRST_OFFSET) match
case Right(value) => builder += value
case Left(error) => throw new DecodeFailureException(error.message, decoder.offset, statement, error.cause)
Monad[F].pure(builder.result())
126 changes: 76 additions & 50 deletions module/ldbc-dsl/src/main/scala/ldbc/dsl/codec/Codec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import java.time.*
import scala.deriving.Mirror

import cats.InvariantSemigroupal
import cats.syntax.all.*

import org.typelevel.twiddles.TwiddleSyntax

Expand All @@ -36,97 +37,122 @@ trait Codec[A] extends Encoder[A], Decoder[A]:
private val pe = self.asEncoder product fb.asEncoder
private val pd = self.asDecoder product fb.asDecoder

override def offset: Int = self.offset + fb.offset
override def encode(value: (A, B)): Encoder.Encoded = pe.encode(value)
override def decode(resultSet: ResultSet, index: Int): (A, B) = pd.decode(resultSet, index)
override def offset: Int = self.offset + fb.offset
override def encode(value: (A, B)): Encoder.Encoded = pe.encode(value)
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, (A, B)] = pd.decode(resultSet, index)

/** Contramap inputs from, and map outputs to, a new type `B`, yielding a `Codec[B]`. */
def imap[B](f: A => B)(g: B => A): Codec[B] = new Codec[B]:
override def offset: Int = self.offset
override def encode(value: B): Encoder.Encoded = self.encode(g(value))
override def decode(resultSet: ResultSet, index: Int): B = f(self.decode(resultSet, index))
override def offset: Int = self.offset
override def encode(value: B): Encoder.Encoded = self.encode(g(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, B] =
self.decode(resultSet, index).map(f)

/** Contramap inputs from, and map decoded results to a new type `B` or an error, yielding a `Codec[B]`. */
def eimap[B](f: A => Either[String, B])(g: B => A): Codec[B] = new Codec[B]:
override def offset: Int = self.offset
override def encode(value: B): Encoder.Encoded = self.encode(g(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, B] =
self.decode(resultSet, index).flatMap(f(_).leftMap(Decoder.Error(offset, _)))

/** Lift this `Codec` into `Option`, where `None` is mapped to and from a vector of `NULL`. */
override def opt: Codec[Option[A]] = new Codec[Option[A]]:
override def offset: Int = self.offset
override def encode(value: Option[A]): Encoder.Encoded =
value.fold(Encoder.Encoded.success(List(None)))(self.encode)
override def decode(resultSet: ResultSet, index: Int): Option[A] =
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, Option[A]] =
val value = self.decode(resultSet, index)
if resultSet.wasNull() then None else Some(value)
if resultSet.wasNull() then Right(None) else value.map(Some(_))

object Codec extends TwiddleSyntax[Codec]:

def apply[A](using codec: Codec[A]): Codec[A] = codec

private def readCatchError[A](offset: Int, func: => A): Either[Decoder.Error, A] =
try Right(func)
catch case ex: Throwable => Left(Decoder.Error(offset, ex.getMessage, Some(ex)))

given InvariantSemigroupal[Codec] with
override def imap[A, B](fa: Codec[A])(f: A => B)(g: B => A): Codec[B] = fa.imap(f)(g)
override def product[A, B](fa: Codec[A], fb: Codec[B]): Codec[(A, B)] = fa product fb

given Codec[Boolean] with
override def offset: Int = 1
override def encode(value: Boolean): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Boolean = resultSet.getBoolean(index)
override def offset: Int = 1
override def encode(value: Boolean): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, Boolean] =
readCatchError(offset, resultSet.getBoolean(index))

given Codec[Byte] with
override def offset: Int = 1
override def encode(value: Byte): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Byte = resultSet.getByte(index)
override def offset: Int = 1
override def encode(value: Byte): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, Byte] =
readCatchError(offset, resultSet.getByte(index))

given Codec[Short] with
override def offset: Int = 1
override def encode(value: Short): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Short = resultSet.getShort(index)
override def offset: Int = 1
override def encode(value: Short): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, Short] =
readCatchError(offset, resultSet.getShort(index))

given Codec[Int] with
override def offset: Int = 1
override def encode(value: Int): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Int = resultSet.getInt(index)
override def offset: Int = 1
override def encode(value: Int): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, Int] =
readCatchError(offset, resultSet.getInt(index))

given Codec[Long] with
override def offset: Int = 1
override def encode(value: Long): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Long = resultSet.getLong(index)
override def offset: Int = 1
override def encode(value: Long): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, Long] =
readCatchError(offset, resultSet.getLong(index))

given Codec[Float] with
override def offset: Int = 1
override def encode(value: Float): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Float = resultSet.getFloat(index)
override def offset: Int = 1
override def encode(value: Float): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, Float] =
readCatchError(offset, resultSet.getFloat(index))

given Codec[Double] with
override def offset: Int = 1
override def encode(value: Double): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Double = resultSet.getDouble(index)
override def offset: Int = 1
override def encode(value: Double): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, Double] =
readCatchError(offset, resultSet.getDouble(index))

given Codec[BigDecimal] with
override def offset: Int = 1
override def encode(value: BigDecimal): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): BigDecimal = resultSet.getBigDecimal(index)
override def offset: Int = 1
override def encode(value: BigDecimal): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, BigDecimal] =
readCatchError(offset, resultSet.getBigDecimal(index))

given Codec[String] with
override def offset: Int = 1
override def encode(value: String): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): String = resultSet.getString(index)
override def offset: Int = 1
override def encode(value: String): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, String] =
readCatchError(offset, resultSet.getString(index))

given Codec[Array[Byte]] with
override def offset: Int = 1
override def encode(value: Array[Byte]): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Array[Byte] = resultSet.getBytes(index)
override def offset: Int = 1
override def encode(value: Array[Byte]): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, Array[Byte]] =
readCatchError(offset, resultSet.getBytes(index))

given Codec[LocalTime] with
override def offset: Int = 1
override def encode(value: LocalTime): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): LocalTime = resultSet.getTime(index)
override def offset: Int = 1
override def encode(value: LocalTime): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, LocalTime] =
readCatchError(offset, resultSet.getTime(index))

given Codec[LocalDate] with
override def offset: Int = 1
override def encode(value: LocalDate): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): LocalDate = resultSet.getDate(index)
override def offset: Int = 1
override def encode(value: LocalDate): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, LocalDate] =
readCatchError(offset, resultSet.getDate(index))

given Codec[LocalDateTime] with
override def offset: Int = 1
override def encode(value: LocalDateTime): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): LocalDateTime = resultSet.getTimestamp(index)
override def offset: Int = 1
override def encode(value: LocalDateTime): Encoder.Encoded = Encoder.Encoded.success(List(value))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, LocalDateTime] =
readCatchError(offset, resultSet.getTimestamp(index))

given [A](using codec: Codec[Int]): Codec[Year] =
codec.imap(Year.of)(_.getValue)
Expand All @@ -138,9 +164,9 @@ object Codec extends TwiddleSyntax[Codec]:
codec.imap(str => if str == null then null else BigInt(str))(_.toString)

given Codec[None.type] with
override def offset: Int = 1
override def encode(value: None.type): Encoder.Encoded = Encoder.Encoded.success(List(None))
override def decode(resultSet: ResultSet, index: Int): None.type = None
override def offset: Int = 1
override def encode(value: None.type): Encoder.Encoded = Encoder.Encoded.success(List(None))
override def decode(resultSet: ResultSet, index: Int): Either[Decoder.Error, None.type] = Right(None)

given [A](using codec: Codec[A]): Codec[Option[A]] = codec.opt

Expand Down
Loading

0 comments on commit cb32ef0

Please sign in to comment.