Skip to content

Commit

Permalink
Merge pull request #349 from takapi327/feature/2024-12-Create-Codec
Browse files Browse the repository at this point in the history
Feature/2024 12 create codec
  • Loading branch information
takapi327 authored Dec 31, 2024
2 parents 485a6aa + 2c600d9 commit 96d79ef
Show file tree
Hide file tree
Showing 11 changed files with 228 additions and 204 deletions.
38 changes: 12 additions & 26 deletions benchmark/src/main/scala/benchmark/Model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ case class Model1(
) derives Table

object Model1:
given Encoder[Model1] = Encoder[Int].to[Model1]
given Decoder[Model1] = Decoder[Int].to[Model1]
given Codec[Model1] = Codec[Int].to[Model1]

case class Model5(
c1: Int,
Expand Down Expand Up @@ -65,17 +64,11 @@ case class Model20(
) derives Table

object Model20:
given Encoder[Model20] = (
Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *:
Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *:
Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *:
Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int]
).to[Model20]
given Decoder[Model20] = (
Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *:
Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *:
Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *:
Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int]
given Codec[Model20] = (
Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *:
Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *:
Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *:
Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int]
).to[Model20]

case class Model25(
Expand Down Expand Up @@ -107,19 +100,12 @@ case class Model25(
) derives Table

object Model25:
given Encoder[Model25] = (
Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *:
Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *:
Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *:
Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *:
Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int] *: Encoder[Int]
).to[Model25]
given Decoder[Model25] = (
Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *:
Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *:
Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *:
Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *:
Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int] *: Decoder[Int]
given Codec[Model25] = (
Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *:
Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *:
Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *:
Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *:
Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int] *: Codec[Int]
).to[Model25]

case class City(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ private[ldbc] object TableModelGenerator:
Some(s"""enum $enumName extends model.Enum:
| case ${ types.mkString(", ") }
| object $enumName extends model.EnumDataType[$enumName]:
| given ldbc.dsl.codec.Decoder[$enumName] = ldbc.dsl.codec.Decoder[Int].map($enumName.fromOrdinal)
| given ldbc.dsl.codec.Encoder[$enumName] = ldbc.dsl.codec.Encoder[Int].contramap(_.ordinal)
| given ldbc.dsl.codec.Codec[$enumName] = ldbc.dsl.codec.Codec[Int].imap($enumName.fromOrdinal)(_.ordinal)
|""".stripMargin)
case _ => None
12 changes: 10 additions & 2 deletions module/ldbc-dsl/src/main/scala/ldbc/dsl/Parameter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ package ldbc.dsl

import cats.syntax.all.*

import ldbc.dsl.codec.Encoder
import ldbc.dsl.codec.*

/**
* Trait for setting Scala and Java values to PreparedStatement.
Expand All @@ -35,10 +35,18 @@ object Parameter:
case Encoder.Encoded.Success(list) => list.map(value => Success(value))
case Encoder.Encoded.Failure(errors) => List(Failure(errors.toList))

given [A](using encoder: Encoder[A]): Conversion[A, Dynamic] with
given convFromEncoder[A](using encoder: Encoder[A]): Conversion[A, Dynamic] with
override def apply(value: A): Dynamic = encoder.encode(value) match
case Encoder.Encoded.Success(list) =>
list match
case head :: Nil => Dynamic.Success(head)
case _ => Dynamic.Failure(List("Multiple values are not allowed"))
case Encoder.Encoded.Failure(errors) => Dynamic.Failure(errors.toList)

given convFromCodec[A](using codec: Codec[A]): Conversion[A, Dynamic] with
override def apply(value: A): Dynamic = codec.encode(value) match
case Encoder.Encoded.Success(list) =>
list match
case head :: Nil => Dynamic.Success(head)
case _ => Dynamic.Failure(List("Multiple values are not allowed"))
case Encoder.Encoded.Failure(errors) => Dynamic.Failure(errors.toList)
153 changes: 153 additions & 0 deletions module/ldbc-dsl/src/main/scala/ldbc/dsl/codec/Codec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/**
* Copyright (c) 2023-2024 by Takahiko Tominaga
* This software is licensed under the MIT License (MIT).
* For more information see LICENSE or https://opensource.org/licenses/MIT
*/

package ldbc.dsl.codec

import java.time.*

import scala.deriving.Mirror

import cats.InvariantSemigroupal

import org.typelevel.twiddles.TwiddleSyntax

import ldbc.sql.ResultSet

/**
* Symmetric encoder and decoder of MySQL data to and from Scala types.
*
* @tparam A
* Types handled in Scala
*/
trait Codec[A] extends Encoder[A], Decoder[A]:
self =>

/** Forget this value is a `Codec` and treat it as an `Encoder`. */
def asEncoder: Encoder[A] = this

/** Forget this value is a `Codec` and treat it as a `Decoder`. */
def asDecoder: Decoder[A] = this

/** `Codec` is semigroupal: a pair of codecs make a codec for a pair. */
def product[B](fb: Codec[B]): Codec[(A, B)] = new Codec[(A, B)]:
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)

/** 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))

/** 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] =
val value = self.decode(resultSet, index)
if resultSet.wasNull() then None else Some(value)

object Codec extends TwiddleSyntax[Codec]:

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

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)

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)

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)

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)

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)

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)

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)

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)

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)

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)

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)

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)

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)

given [A](using codec: Codec[Int]): Codec[Year] =
codec.imap(Year.of)(_.getValue)

given [A](using codec: Codec[String]): Codec[YearMonth] =
codec.imap(YearMonth.parse)(_.toString)

given [A](using codec: Codec[String]): Codec[BigInt] =
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

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

given [A, B](using ca: Codec[A], cb: Codec[B]): Codec[(A, B)] = ca product cb

given [H, T <: Tuple](using dh: Codec[H], dt: Codec[T]): Codec[H *: T] =
dh.product(dt).imap { case (h, t) => h *: t }(tuple => (tuple.head, tuple.tail))

given [P <: Product](using mirror: Mirror.ProductOf[P], codec: Codec[mirror.MirroredElemTypes]): Codec[P] =
codec.to[P]
27 changes: 2 additions & 25 deletions module/ldbc-dsl/src/main/scala/ldbc/dsl/codec/Decoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

package ldbc.dsl.codec

import java.time.*

import scala.deriving.Mirror

import cats.Applicative
Expand All @@ -17,7 +15,7 @@ import org.typelevel.twiddles.TwiddleSyntax
import ldbc.sql.ResultSet

/**
* Class to get the DataType that matches the Scala type information from the ResultSet.
* Trait to get the DataType that matches the Scala type information from the ResultSet.
*
* @tparam A
* Scala types that match SQL DataType
Expand Down Expand Up @@ -71,28 +69,7 @@ object Decoder extends TwiddleSyntax[Decoder]:
override def offset: Int = 0
override def decode(resultSet: ResultSet, index: Int): A = x

given Decoder[String] = (resultSet: ResultSet, index: Int) => resultSet.getString(index)
given Decoder[Boolean] = (resultSet: ResultSet, index: Int) => resultSet.getBoolean(index)
given Decoder[Byte] = (resultSet: ResultSet, index: Int) => resultSet.getByte(index)
given Decoder[Array[Byte]] = (resultSet: ResultSet, index: Int) => resultSet.getBytes(index)
given Decoder[Short] = (resultSet: ResultSet, index: Int) => resultSet.getShort(index)
given Decoder[Int] = (resultSet: ResultSet, index: Int) => resultSet.getInt(index)
given Decoder[Long] = (resultSet: ResultSet, index: Int) => resultSet.getLong(index)
given Decoder[Float] = (resultSet: ResultSet, index: Int) => resultSet.getFloat(index)
given Decoder[Double] = (resultSet: ResultSet, index: Int) => resultSet.getDouble(index)
given Decoder[LocalDate] = (resultSet: ResultSet, index: Int) => resultSet.getDate(index)
given Decoder[LocalTime] = (resultSet: ResultSet, index: Int) => resultSet.getTime(index)
given Decoder[LocalDateTime] = (resultSet: ResultSet, index: Int) => resultSet.getTimestamp(index)
given Decoder[BigDecimal] = (resultSet: ResultSet, index: Int) => resultSet.getBigDecimal(index)

given (using decoder: Decoder[String]): Decoder[BigInt] =
decoder.map(str => if str == null then null else BigInt(str))

given (using decoder: Decoder[Int]): Decoder[Year] =
decoder.map(int => Year.of(int))

given (using decoder: Decoder[String]): Decoder[YearMonth] =
decoder.map(str => YearMonth.parse(str))
given [A](using codec: Codec[A]): Decoder[A] = codec.asDecoder

given [A](using decoder: Decoder[A]): Decoder[Option[A]] = decoder.opt

Expand Down
68 changes: 1 addition & 67 deletions module/ldbc-dsl/src/main/scala/ldbc/dsl/codec/Encoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -58,73 +58,7 @@ object Encoder extends TwiddleSyntax[Encoder]:
override def contramap[A, B](fa: Encoder[A])(f: B => A): Encoder[B] = fa.contramap(f)
override def product[A, B](fa: Encoder[A], fb: Encoder[B]): Encoder[(A, B)] = fa.product(fb)

given Encoder[Boolean] with
override def encode(value: Boolean): Encoded =
Encoded.success(List(value))

given Encoder[Byte] with
override def encode(value: Byte): Encoded =
Encoded.success(List(value))

given Encoder[Short] with
override def encode(value: Short): Encoded =
Encoded.success(List(value))

given Encoder[Int] with
override def encode(value: Int): Encoded =
Encoded.success(List(value))

given Encoder[Long] with
override def encode(value: Long): Encoded =
Encoded.success(List(value))

given Encoder[Float] with
override def encode(value: Float): Encoded =
Encoded.success(List(value))

given Encoder[Double] with
override def encode(value: Double): Encoded =
Encoded.success(List(value))

given Encoder[BigDecimal] with
override def encode(value: BigDecimal): Encoded =
Encoded.success(List(value))

given Encoder[String] with
override def encode(value: String): Encoded =
Encoded.success(List(value))

given Encoder[Array[Byte]] with
override def encode(value: Array[Byte]): Encoded =
Encoded.success(List(value))

given Encoder[LocalTime] with
override def encode(value: LocalTime): Encoded =
Encoded.success(List(value))

given Encoder[LocalDate] with
override def encode(value: LocalDate): Encoded =
Encoded.success(List(value))

given Encoder[LocalDateTime] with
override def encode(value: LocalDateTime): Encoded =
Encoded.success(List(value))

given (using encoder: Encoder[String]): Encoder[Year] = encoder.contramap(_.toString)

given (using encoder: Encoder[String]): Encoder[YearMonth] = encoder.contramap(_.toString)

given (using encoder: Encoder[String]): Encoder[BigInt] = encoder.contramap(_.toString)

given Encoder[None.type] with
override def encode(value: None.type): Encoded =
Encoded.success(List(None))

given [A](using encoder: Encoder[A]): Encoder[Option[A]] with
override def encode(value: Option[A]): Encoded =
value match
case Some(value) => encoder.encode(value)
case None => Encoded.success(List(None))
given [A](using codec: Codec[A]): Encoder[A] = codec.asEncoder

given [A, B](using ea: Encoder[A], eb: Encoder[B]): Encoder[(A, B)] =
ea.product(eb)
Expand Down
Loading

0 comments on commit 96d79ef

Please sign in to comment.