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

ldbc.dsl.codec.Decoder.scala Maven / Gradle / Ivy

/**
 * 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.compiletime.*
import scala.deriving.Mirror

import cats.Functor
import cats.syntax.all.*

import ldbc.sql.ResultSet

/**
 * Trait to get the DataType that matches the Scala type information from the ResultSet.
 *
 * @tparam A
 *   Scala types that match SQL DataType
 */
trait Decoder[A]:

  /**
   * Method to retrieve data from a ResultSet
   *
   * @param resultSet
   *   A table of data representing a database result set, which is usually generated by executing a statement that
   *   queries the database.
   * @param prefix
   *   Prefix to be added to the column name when retrieving data from the ResultSet.
   */
  def decode(resultSet: ResultSet, prefix: Option[String]): A

object Decoder:

  given Functor[[A] =>> Decoder[A]] with
    override def map[A, B](fa: Decoder[A])(f: A => B): Decoder[B] =
      (resultSet: ResultSet, prefix: Option[String]) => f(fa.decode(resultSet, prefix))

  /**
   * Trait to get the DataType that matches the Scala type information from the ResultSet.
   *
   * @tparam A
   *   Scala types that match SQL DataType
   */
  trait Elem[A]:

    /**
     * Method to retrieve data from a ResultSet using column names.
     *
     * @param resultSet
     * A table of data representing a database result set, which is usually generated by executing a statement that
     * queries the database.
     * @param columnLabel
     * Column name of the data to be retrieved from the ResultSet.
     */
    def decode(resultSet: ResultSet, columnLabel: String): A

    /**
     * Method to retrieve data from a ResultSet using an Index number.
     *
     * @param resultSet
     * A table of data representing a database result set, which is usually generated by executing a statement that
     * queries the database.
     * @param index
     * Index number of the data to be retrieved from the ResultSet.
     */
    def decode(resultSet: ResultSet, index: Int): A

  object Elem:
    def apply[T](
      decodeLabel: ResultSet => String => T,
      decodeIndex: ResultSet => Int => T
    ): Elem[T] =
      new Elem[T]:
        override def decode(resultSet: ResultSet, columnLabel: String): T =
          decodeLabel(resultSet)(columnLabel)

        override def decode(resultSet: ResultSet, index: Int): T =
          decodeIndex(resultSet)(index)

    given Functor[[T] =>> Elem[T]] with
      override def map[A, B](fa: Elem[A])(f: A => B): Elem[B] =
        Elem(
          resultSet => columnLabel => f(fa.decode(resultSet, columnLabel)),
          resultSet => index => f(fa.decode(resultSet, index))
        )

    /**
     * A method to convert the specified Scala type to an arbitrary type so that it can be handled by Decoder.
     *
     * @param f
     *   Function to convert from type A to B.
     * @param decoder
     *   Decoder to retrieve the DataType matching the type A information from the ResultSet.
     * @tparam A
     *   The Scala type to be converted from.
     * @tparam B
     *   The Scala type to be converted to.
     */
    def mapping[A, B](f: A => B)(using decoder: Decoder.Elem[A]): Decoder.Elem[B] =
      decoder.map(f(_))

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

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

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

    given (using decoder: Elem[String]): Elem[YearMonth] =
      decoder.map(str => YearMonth.parse(str))

    given [A](using decoder: Elem[A]): Elem[Option[A]] with
      override def decode(resultSet: ResultSet, columnLabel: String): Option[A] =
        val value = decoder.decode(resultSet, columnLabel)
        if resultSet.wasNull() then None else Some(value)

      override def decode(resultSet: ResultSet, index: Int): Option[A] =
        val value = decoder.decode(resultSet, index)
        if resultSet.wasNull() then None else Some(value)

  def one[A](using decoder: Decoder.Elem[A]): Decoder[A] =
    (resultSet: ResultSet, prefix: Option[String]) => decoder.decode(resultSet, 1)

  inline given derived[A](using mirror: Mirror.Of[A]): Decoder[A] =
    inline mirror match
      case s: Mirror.SumOf[A]     => error("Sum type is not supported")
      case p: Mirror.ProductOf[A] => derivedProduct(p)

  private[ldbc] inline def derivedProduct[A](mirror: Mirror.ProductOf[A]): Decoder[A] =
    val labels  = constValueTuple[mirror.MirroredElemLabels].toArray.map(_.toString)
    val decodes = getDecoders[mirror.MirroredElemTypes].toArray

    (resultSet: ResultSet, prefix: Option[String]) =>
      val results = labels.zip(decodes).map { (label, decoder) =>
        val column = prefix.map(_ + ".").getOrElse("") + label
        decoder match
          case dm: Decoder.Elem[t] => dm.decode(resultSet, column)
          case d: Decoder[t]       => d.decode(resultSet, Some(column))
      }

      mirror.fromTuple(Tuple.fromArray(results).asInstanceOf[mirror.MirroredElemTypes])

  private[ldbc] inline def derivedTuple[A](mirror: Mirror.ProductOf[A]): Decoder[A] =
    val decodes = getDecoders[mirror.MirroredElemTypes].toArray

    (resultSet: ResultSet, prefix: Option[String]) =>
      val results = decodes.zipWithIndex.map { (decoder, index) =>
        decoder match
          case dm: Decoder.Elem[t] => dm.decode(resultSet, index + 1)
          case d: Decoder[t]       => d.decode(resultSet, prefix)
      }

      mirror.fromTuple(Tuple.fromArray(results).asInstanceOf[mirror.MirroredElemTypes])

  private[ldbc] inline def getDecoders[T <: Tuple]: Tuple =
    inline erasedValue[T] match
      case _: EmptyTuple => EmptyTuple
      case _: (t *: ts) =>
        summonFrom {
          case dm: Decoder.Elem[`t`] => dm
          case d: Decoder[`t`]       => d
        } *: getDecoders[ts]




© 2015 - 2024 Weber Informatics LLC | Privacy Policy