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

zio.jdbc.JdbcDecoder.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2022 John A. De Goes and the ZIO Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package zio.jdbc

import zio._

import java.io._
import java.sql.{ Array => _, _ }
import scala.collection.immutable.ListMap

/**
 * A type class that describes the ability to decode  a value of type `A` from
 * a `ResultSet`.
 */
trait JdbcDecoder[+A] { self =>
  def unsafeDecode(columIndex: Int, rs: ResultSet): (Int, A)

  final def decode(columnIndex: Int, rs: ResultSet): Either[Throwable, (Int, A)] =
    try Right(unsafeDecode(columnIndex, rs))
    catch { case e: JdbcDecoderError => Left(e) }

  final def map[B](f: A => B): JdbcDecoder[B] =
    new JdbcDecoder[B] {
      override def unsafeDecode(
        inputColumnIndex: Int,
        inputResultSet: ResultSet
      ): (Int, B) = {
        val (columnIndex, a) = self.unsafeDecode(inputColumnIndex, inputResultSet)
        (columnIndex, f(a))
      }
    }

  final def flatMap[B](f: A => JdbcDecoder[B]): JdbcDecoder[B] =
    new JdbcDecoder[B] {
      override def unsafeDecode(
        inputColumnIndex: Int,
        inputResultSet: ResultSet
      ): (Int, B) = {
        val (columnIndex, a) = self.unsafeDecode(inputColumnIndex, inputResultSet)
        f(a).unsafeDecode(columnIndex + 1, inputResultSet)
      }
    }

  final def zip[A1 >: A, B, C](
    that: => JdbcDecoder[B]
  )(implicit Z: Zippable.Out[A1, B, C]): JdbcDecoder[C] =
    self.flatMap(a => that.map(b => Z.zip(a, b)))

}

object JdbcDecoder extends JdbcDecoderLowPriorityImplicits {

  final case class RowState(rs: ResultSet, columnIndex: Int)

  def readPrimitive[A](n: Int)(implicit A: JdbcDecoder[A]): ResultSet => A = { (rs: ResultSet) =>
    A.unsafeDecode(n, rs)._2
  }

  def apply[A](implicit decoder: JdbcDecoder[A]): JdbcDecoder[A] = decoder

  def apply[A](f: ResultSet => (Int => A), expected: String = "value"): JdbcDecoder[A] =
    new JdbcDecoder[A] {
      override def unsafeDecode(
        inputColumnIndex: Int,
        inputResultSet: ResultSet
      ): (Int, A) =
        try {
          val newValue = f(inputResultSet)(inputColumnIndex)
          (inputColumnIndex, newValue)
        } catch {
          case t: Throwable if !t.isInstanceOf[VirtualMachineError] =>
            throw JdbcDecoderError(
              s"Error decoding $expected from ResultSet",
              t,
              inputResultSet.getMetaData,
              inputResultSet.getRow
            )
        }
    }

  implicit val intDecoder: JdbcDecoder[Int]                         = JdbcDecoder(_.getInt)
  implicit val longDecoder: JdbcDecoder[Long]                       = JdbcDecoder(_.getLong)
  implicit val doubleDecoder: JdbcDecoder[Double]                   = JdbcDecoder(_.getDouble)
  implicit val stringDecoder: JdbcDecoder[String]                   = JdbcDecoder(_.getString)
  implicit val booleanDecoder: JdbcDecoder[Boolean]                 = JdbcDecoder(_.getBoolean)
  implicit val bigDecimalDecoder: JdbcDecoder[java.math.BigDecimal] = JdbcDecoder(_.getBigDecimal)
  implicit val bigDecimalDecoderScala: JdbcDecoder[scala.math.BigDecimal] =
    // This `if null` check is only needed because of Scala 2.12. Can be removed once Scala 2.12 support is dropped.
    bigDecimalDecoder.map(v => if (v eq null) null else scala.math.BigDecimal.javaBigDecimal2bigDecimal(v))
  implicit val shortDecoder: JdbcDecoder[Short]                     = JdbcDecoder(_.getShort)
  implicit val floatDecoder: JdbcDecoder[Float]                     = JdbcDecoder(_.getFloat)
  implicit val byteDecoder: JdbcDecoder[Byte]                       = JdbcDecoder(_.getByte)
  implicit val byteArrayDecoder: JdbcDecoder[Array[Byte]]           = JdbcDecoder(_.getBytes)
  implicit val blobDecoder: JdbcDecoder[Blob]                       = JdbcDecoder(_.getBlob)
  implicit val uuidDecoder: JdbcDecoder[java.util.UUID] =
    // See: https://stackoverflow.com/a/56267754/2431728
    JdbcDecoder(rs => i => rs.getObject(i, classOf[java.util.UUID]), "UUID")

  implicit val dateDecoder: JdbcDecoder[java.sql.Date]           = JdbcDecoder(_.getDate)
  implicit val timeDecoder: JdbcDecoder[java.sql.Time]           = JdbcDecoder(_.getTime)
  implicit val timestampDecoder: JdbcDecoder[java.sql.Timestamp] = JdbcDecoder(_.getTimestamp)

  // These `java.time.*` decoders are copied from Quill's 'ObjectGenericTimeDecoders' trait.
  // Note:
  //   1. These decoders probably don't work for SQLite. Quill as a separate trait, named `BasicTimeDecoders` which seems dedicated to SQLite.
  //   2. We deliberately decided not to support `java.time.OffsetTime`.
  //      The reasons for this choice are detailed next to the `java.time.*` Setters implementation
  implicit val localDateDecoder: JdbcDecoder[java.time.LocalDate]           =
    JdbcDecoder(rs => i => rs.getObject(i, classOf[java.time.LocalDate]), "java.time.LocalDate")
  implicit val localTimeDecoder: JdbcDecoder[java.time.LocalTime]           =
    JdbcDecoder(rs => i => rs.getObject(i, classOf[java.time.LocalTime]), "java.time.LocalTime")
  implicit val localDateTimeDecoder: JdbcDecoder[java.time.LocalDateTime]   =
    JdbcDecoder(rs => i => rs.getObject(i, classOf[java.time.LocalDateTime]), "java.time.LocalDateTime")
  implicit val zonedDateTimeDecoder: JdbcDecoder[java.time.ZonedDateTime]   =
    JdbcDecoder(
      rs => i => rs.getObject(i, classOf[java.time.OffsetDateTime]).toZonedDateTime,
      "java.time.ZonedDateTime"
    )
  implicit val instantDecoder: JdbcDecoder[java.time.Instant]               =
    JdbcDecoder(rs => i => rs.getObject(i, classOf[java.time.OffsetDateTime]).toInstant, "java.time.Instant")
  implicit val offsetDateTimeDecoder: JdbcDecoder[java.time.OffsetDateTime] =
    JdbcDecoder(rs => i => rs.getObject(i, classOf[java.time.OffsetDateTime]), "java.time.OffsetDateTime")

  implicit def optionDecoder[A](implicit decoder: JdbcDecoder[A]): JdbcDecoder[Option[A]] =
    JdbcDecoder(rs =>
      int =>
        decoder.decode(int, rs) match {
          case Left(_)      => None
          case Right(value) => Option(value._2)
        }
    )

  implicit def tuple2Decoder[A, B](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B]
  ): JdbcDecoder[(A, B)] =
    a.zip(b)

  implicit def tuple3Decoder[A, B, C](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C]
  ): JdbcDecoder[(A, B, C)] =
    a.zip(b).zip(c)

  // Use Zipper typeclass to form the rest
  implicit def tuple4Decoder[A, B, C, D](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D]
  ): JdbcDecoder[(A, B, C, D)] =
    a.zip(b).zip(c).zip(d)

  implicit def tuple5Decoder[A, B, C, D, E](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E]
  ): JdbcDecoder[(A, B, C, D, E)] =
    a.zip(b).zip(c).zip(d).zip(e)

  implicit def tuple6Decoder[A, B, C, D, E, F](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F]
  ): JdbcDecoder[(A, B, C, D, E, F)] =
    a.zip(b).zip(c).zip(d).zip(e).zip(f)

  implicit def tuple7Decoder[A, B, C, D, E, F, G](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G]
  ): JdbcDecoder[(A, B, C, D, E, F, G)] =
    a.zip(b).zip(c).zip(d).zip(e).zip(f).zip(g)

  implicit def tuple8Decoder[A, B, C, D, E, F, G, H](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H)] =
    a.zip(b).zip(c).zip(d).zip(e).zip(f).zip(g).zip(h)

  implicit def tuple9Decoder[A, B, C, D, E, F, G, H, I](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H],
    i: JdbcDecoder[I]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H, I)] =
    a.zip(b).zip(c).zip(d).zip(e).zip(f).zip(g).zip(h).zip(i)

  implicit def tuple10Decoder[A, B, C, D, E, F, G, H, I, J](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H],
    i: JdbcDecoder[I],
    j: JdbcDecoder[J]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H, I, J)] =
    a.zip(b).zip(c).zip(d).zip(e).zip(f).zip(g).zip(h).zip(i).zip(j)

  implicit def tuple11Decoder[A, B, C, D, E, F, G, H, I, J, K](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H],
    i: JdbcDecoder[I],
    j: JdbcDecoder[J],
    k: JdbcDecoder[K]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H, I, J, K)] =
    a.zip(b).zip(c).zip(d).zip(e).zip(f).zip(g).zip(h).zip(i).zip(j).zip(k)

  implicit def tuple12Decoder[A, B, C, D, E, F, G, H, I, J, K, L](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H],
    i: JdbcDecoder[I],
    j: JdbcDecoder[J],
    k: JdbcDecoder[K],
    l: JdbcDecoder[L]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H, I, J, K, L)] =
    a.zip(b).zip(c).zip(d).zip(e).zip(f).zip(g).zip(h).zip(i).zip(j).zip(k).zip(l)

  implicit def tuple13Decoder[A, B, C, D, E, F, G, H, I, J, K, L, M](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H],
    i: JdbcDecoder[I],
    j: JdbcDecoder[J],
    k: JdbcDecoder[K],
    l: JdbcDecoder[L],
    m: JdbcDecoder[M]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H, I, J, K, L, M)] =
    a.zip(b).zip(c).zip(d).zip(e).zip(f).zip(g).zip(h).zip(i).zip(j).zip(k).zip(l).zip(m)

  implicit def tuple14Decoder[A, B, C, D, E, F, G, H, I, J, K, L, M, N](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H],
    i: JdbcDecoder[I],
    j: JdbcDecoder[J],
    k: JdbcDecoder[K],
    l: JdbcDecoder[L],
    m: JdbcDecoder[M],
    n: JdbcDecoder[N]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H, I, J, K, L, M, N)] =
    a.zip(b).zip(c).zip(d).zip(e).zip(f).zip(g).zip(h).zip(i).zip(j).zip(k).zip(l).zip(m).zip(n)

  implicit def tuple15Decoder[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H],
    i: JdbcDecoder[I],
    j: JdbcDecoder[J],
    k: JdbcDecoder[K],
    l: JdbcDecoder[L],
    m: JdbcDecoder[M],
    n: JdbcDecoder[N],
    o: JdbcDecoder[O]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O)] =
    a.zip(b).zip(c).zip(d).zip(e).zip(f).zip(g).zip(h).zip(i).zip(j).zip(k).zip(l).zip(m).zip(n).zip(o)

  implicit def tuple16Decoder[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H],
    i: JdbcDecoder[I],
    j: JdbcDecoder[J],
    k: JdbcDecoder[K],
    l: JdbcDecoder[L],
    m: JdbcDecoder[M],
    n: JdbcDecoder[N],
    o: JdbcDecoder[O],
    p: JdbcDecoder[P]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P)] =
    a.zip(b).zip(c).zip(d).zip(e).zip(f).zip(g).zip(h).zip(i).zip(j).zip(k).zip(l).zip(m).zip(n).zip(o).zip(p)

  implicit def tuple17Decoder[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H],
    i: JdbcDecoder[I],
    j: JdbcDecoder[J],
    k: JdbcDecoder[K],
    l: JdbcDecoder[L],
    m: JdbcDecoder[M],
    n: JdbcDecoder[N],
    o: JdbcDecoder[O],
    p: JdbcDecoder[P],
    q: JdbcDecoder[Q]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q)] =
    a.zip(b).zip(c).zip(d).zip(e).zip(f).zip(g).zip(h).zip(i).zip(j).zip(k).zip(l).zip(m).zip(n).zip(o).zip(p).zip(q)

  implicit def tuple18Decoder[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H],
    i: JdbcDecoder[I],
    j: JdbcDecoder[J],
    k: JdbcDecoder[K],
    l: JdbcDecoder[L],
    m: JdbcDecoder[M],
    n: JdbcDecoder[N],
    o: JdbcDecoder[O],
    p: JdbcDecoder[P],
    q: JdbcDecoder[Q],
    r: JdbcDecoder[R]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R)] =
    a.zip(b)
      .zip(c)
      .zip(d)
      .zip(e)
      .zip(f)
      .zip(g)
      .zip(h)
      .zip(i)
      .zip(j)
      .zip(k)
      .zip(l)
      .zip(m)
      .zip(n)
      .zip(o)
      .zip(p)
      .zip(q)
      .zip(r)

  implicit def tuple19Decoder[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H],
    i: JdbcDecoder[I],
    j: JdbcDecoder[J],
    k: JdbcDecoder[K],
    l: JdbcDecoder[L],
    m: JdbcDecoder[M],
    n: JdbcDecoder[N],
    o: JdbcDecoder[O],
    p: JdbcDecoder[P],
    q: JdbcDecoder[Q],
    r: JdbcDecoder[R],
    s: JdbcDecoder[S]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S)] =
    a.zip(b)
      .zip(c)
      .zip(d)
      .zip(e)
      .zip(f)
      .zip(g)
      .zip(h)
      .zip(i)
      .zip(j)
      .zip(k)
      .zip(l)
      .zip(m)
      .zip(n)
      .zip(o)
      .zip(p)
      .zip(q)
      .zip(r)
      .zip(s)

  implicit def tuple20Decoder[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H],
    i: JdbcDecoder[I],
    j: JdbcDecoder[J],
    k: JdbcDecoder[K],
    l: JdbcDecoder[L],
    m: JdbcDecoder[M],
    n: JdbcDecoder[N],
    o: JdbcDecoder[O],
    p: JdbcDecoder[P],
    q: JdbcDecoder[Q],
    r: JdbcDecoder[R],
    s: JdbcDecoder[S],
    t: JdbcDecoder[T]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T)] =
    a.zip(b)
      .zip(c)
      .zip(d)
      .zip(e)
      .zip(f)
      .zip(g)
      .zip(h)
      .zip(i)
      .zip(j)
      .zip(k)
      .zip(l)
      .zip(m)
      .zip(n)
      .zip(o)
      .zip(p)
      .zip(q)
      .zip(r)
      .zip(s)
      .zip(t)

  implicit def tuple21Decoder[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H],
    i: JdbcDecoder[I],
    j: JdbcDecoder[J],
    k: JdbcDecoder[K],
    l: JdbcDecoder[L],
    m: JdbcDecoder[M],
    n: JdbcDecoder[N],
    o: JdbcDecoder[O],
    p: JdbcDecoder[P],
    q: JdbcDecoder[Q],
    r: JdbcDecoder[R],
    s: JdbcDecoder[S],
    t: JdbcDecoder[T],
    u: JdbcDecoder[U]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U)] =
    a.zip(b)
      .zip(c)
      .zip(d)
      .zip(e)
      .zip(f)
      .zip(g)
      .zip(h)
      .zip(i)
      .zip(j)
      .zip(k)
      .zip(l)
      .zip(m)
      .zip(n)
      .zip(o)
      .zip(p)
      .zip(q)
      .zip(r)
      .zip(s)
      .zip(t)
      .zip(u)

  implicit def tuple22Decoder[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V](implicit
    a: JdbcDecoder[A],
    b: JdbcDecoder[B],
    c: JdbcDecoder[C],
    d: JdbcDecoder[D],
    e: JdbcDecoder[E],
    f: JdbcDecoder[F],
    g: JdbcDecoder[G],
    h: JdbcDecoder[H],
    i: JdbcDecoder[I],
    j: JdbcDecoder[J],
    k: JdbcDecoder[K],
    l: JdbcDecoder[L],
    m: JdbcDecoder[M],
    n: JdbcDecoder[N],
    o: JdbcDecoder[O],
    p: JdbcDecoder[P],
    q: JdbcDecoder[Q],
    r: JdbcDecoder[R],
    s: JdbcDecoder[S],
    t: JdbcDecoder[T],
    u: JdbcDecoder[U],
    v: JdbcDecoder[V]
  ): JdbcDecoder[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V)] =
    a.zip(b)
      .zip(c)
      .zip(d)
      .zip(e)
      .zip(f)
      .zip(g)
      .zip(h)
      .zip(i)
      .zip(j)
      .zip(k)
      .zip(l)
      .zip(m)
      .zip(n)
      .zip(o)
      .zip(p)
      .zip(q)
      .zip(r)
      .zip(s)
      .zip(t)
      .zip(u)
      .zip(v)

}

trait JdbcDecoderLowPriorityImplicits {
  import zio.schema._

  import java.sql.{ Types => SqlTypes }

  private def getBinary(binary: InputStream): Chunk[Byte] = {
    val baos = new ByteArrayOutputStream()

    val buffer = Array.ofDim[Byte](1024)

    var read = binary.read(buffer)

    while (read >= 0) {
      baos.write(buffer, 0, read)

      read = binary.read(buffer)
    }

    Chunk.fromArray(baos.toByteArray())
  }

  private[jdbc] def createRemapTable(schema: Schema[_]): String => String =
    schema match {
      case record: Schema.Record[_] =>
        val recordNames = record.fields.map(field => (field.name.toLowerCase, field.name)).toMap

        columnName => recordNames.getOrElse(columnName.toLowerCase, columnName)

      case _ => identity[String](_)
    }

  private[jdbc] def createDynamicDecoder(schema: Schema[_], meta: ResultSetMetaData): ResultSet => DynamicValue =
    resultSet => {
      val remapName   = createRemapTable(schema)
      var columnIndex = 1
      var listMap     = ListMap.empty[String, DynamicValue]

      while (columnIndex <= meta.getColumnCount()) {
        val name = remapName(meta.getColumnName(columnIndex))

        val value: DynamicValue =
          meta.getColumnType(columnIndex) match {
            case SqlTypes.ARRAY =>
              val array = resultSet.getArray(columnIndex)

              createDynamicDecoder(schema, array.getResultSet().getMetaData())(array.getResultSet())

            case SqlTypes.BIGINT =>
              val bigInt = resultSet.getBigDecimal(columnIndex).toBigInteger()

              DynamicValue.Primitive(bigInt, StandardType.BigIntegerType)

            case SqlTypes.BINARY =>
              val chunk = getBinary(resultSet.getBinaryStream(columnIndex))

              DynamicValue.Primitive(chunk, StandardType.BinaryType)

            case SqlTypes.BIT =>
              val bit = resultSet.getInt(columnIndex) == 1

              DynamicValue.Primitive(bit, StandardType.BoolType)

            case SqlTypes.BLOB =>
              val blob = resultSet.getBlob(columnIndex)

              DynamicValue.Primitive(Chunk.fromArray(blob.getBytes(0, blob.length().toInt)), StandardType.BinaryType)

            case SqlTypes.BOOLEAN =>
              val bool = resultSet.getBoolean(columnIndex)

              DynamicValue.Primitive(bool, StandardType.BoolType)

            case SqlTypes.CHAR =>
              val char: Char = resultSet.getString(columnIndex)(0)

              DynamicValue.Primitive(char, StandardType.CharType)

            case SqlTypes.CLOB =>
              val clob = resultSet.getClob(columnIndex)

              DynamicValue.Primitive(clob.getSubString(0L, clob.length().toInt), StandardType.StringType)

            case SqlTypes.DATE =>
              val date = resultSet.getDate(columnIndex)

              DynamicValue.Primitive(date.toLocalDate(), StandardType.LocalDateType)

            case SqlTypes.DECIMAL =>
              val bigDecimal = resultSet.getBigDecimal(columnIndex)

              DynamicValue.Primitive(bigDecimal, StandardType.BigDecimalType)

            case SqlTypes.DOUBLE =>
              val double = resultSet.getDouble(columnIndex)

              DynamicValue.Primitive(double, StandardType.DoubleType)

            case SqlTypes.FLOAT =>
              val float = resultSet.getFloat(columnIndex)

              DynamicValue.Primitive(float, StandardType.FloatType)

            case SqlTypes.INTEGER =>
              val int = resultSet.getInt(columnIndex)

              DynamicValue.Primitive(int, StandardType.IntType)

            case SqlTypes.LONGNVARCHAR =>
              val string = resultSet.getString(columnIndex)

              DynamicValue.Primitive(string, StandardType.StringType)

            case SqlTypes.LONGVARBINARY =>
              val chunk = getBinary(resultSet.getBinaryStream(columnIndex))

              DynamicValue.Primitive(chunk, StandardType.BinaryType)

            case SqlTypes.LONGVARCHAR =>
              val string = resultSet.getString(columnIndex)

              DynamicValue.Primitive(string, StandardType.StringType)

            case SqlTypes.NCHAR =>
              val string = resultSet.getNString(columnIndex)

              DynamicValue.Primitive(string, StandardType.StringType)

            case SqlTypes.NCLOB =>
              val clob = resultSet.getNClob(columnIndex)

              DynamicValue.Primitive(clob.getSubString(0L, clob.length().toInt), StandardType.StringType)

            case SqlTypes.NULL =>
              DynamicValue.Primitive((), StandardType.UnitType)

            case SqlTypes.NUMERIC =>
              val bigDecimal = resultSet.getBigDecimal(columnIndex)

              DynamicValue.Primitive(bigDecimal, StandardType.BigDecimalType)

            case SqlTypes.NVARCHAR =>
              val string = resultSet.getString(columnIndex)

              DynamicValue.Primitive(string, StandardType.StringType)

            case SqlTypes.REAL =>
              val bigDecimal = resultSet.getBigDecimal(columnIndex)

              DynamicValue.Primitive(bigDecimal, StandardType.BigDecimalType)

            case SqlTypes.ROWID =>
              val long = resultSet.getLong(columnIndex)

              DynamicValue.Primitive(long, StandardType.LongType)

            case SqlTypes.SMALLINT =>
              val short = resultSet.getShort(columnIndex)

              DynamicValue.Primitive(short, StandardType.ShortType)

            case SqlTypes.SQLXML =>
              val xml = resultSet.getSQLXML(columnIndex)

              DynamicValue.Primitive(xml.getString(), StandardType.StringType)

            case SqlTypes.TIME =>
              val time = resultSet.getTime(columnIndex)

              DynamicValue.Primitive(time.toLocalTime(), StandardType.LocalTimeType)

            case SqlTypes.TIMESTAMP =>
              val timestamp = resultSet.getTimestamp(columnIndex)

              DynamicValue.Primitive(timestamp.toInstant(), StandardType.InstantType)

            case SqlTypes.TIMESTAMP_WITH_TIMEZONE =>
              // TODO: Timezone
              val timestamp = resultSet.getTimestamp(columnIndex)

              DynamicValue.Primitive(timestamp.toInstant(), StandardType.InstantType)

            case SqlTypes.TIME_WITH_TIMEZONE =>
              // TODO: Timezone
              val time = resultSet.getTime(columnIndex)

              DynamicValue.Primitive(time.toLocalTime(), StandardType.LocalTimeType)

            case SqlTypes.TINYINT =>
              val short = resultSet.getShort(columnIndex)

              DynamicValue.Primitive(short, StandardType.ShortType)

            case SqlTypes.VARBINARY =>
              val chunk = getBinary(resultSet.getBinaryStream(columnIndex))

              DynamicValue.Primitive(chunk, StandardType.BinaryType)

            case SqlTypes.VARCHAR =>
              val string = resultSet.getString(columnIndex)

              DynamicValue.Primitive(string, StandardType.StringType)

            case other =>
              throw new SQLException(
                s"Unsupported SQL type ${other} when attempting to decode result set from a ZIO Schema ${schema}"
              )
          }

        listMap = listMap.updated(name, value)

        columnIndex += 1
      }

      DynamicValue.Record(TypeId.Structural, listMap)
    }

  def fromSchema[A](implicit schema: Schema[A]): JdbcDecoder[A] =
    (columnIndex: Int, resultSet: ResultSet) => {
      val dynamicDecoder = createDynamicDecoder(schema, resultSet.getMetaData())
      val dynamicValue   = dynamicDecoder(resultSet)

      dynamicValue.toTypedValue(schema) match {
        case Left(error) => throw JdbcDecoderError(error, null, resultSet.getMetaData(), resultSet.getRow())

        case Right(value) => (columnIndex, value)
      }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy