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

doobie.util.read.scala Maven / Gradle / Ivy

There is a newer version: 1.0.0-RC6
Show newest version
// Copyright (c) 2013-2020 Rob Norris and Contributors
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package doobie.util

import cats.Applicative
import doobie.ResultSetIO
import doobie.enumerated.Nullability
import doobie.enumerated.Nullability.{NoNulls, NullabilityKnown}
import doobie.free.resultset as IFRS

import java.sql.ResultSet
import scala.annotation.implicitNotFound

@implicitNotFound("""
Cannot find or construct a Read instance for type:

  ${A}

This can happen for a few reasons, but the most common case is that a data
member somewhere within this type doesn't have a Get instance in scope. Here are
some debugging hints:

- For auto derivation ensure `doobie.implicits._` or `doobie.generic.auto._` is
  being imported
- For Option types, ensure that a Read instance is in scope for the non-Option
  version.
- For types you expect to map to a single column ensure that a Get instance is
  in scope.
- For case classes, shapeless HLists/records ensure that each element
  has a Read instance in scope.
- Lather, rinse, repeat, recursively until you find the problematic bit.

You can check that an instance exists for Read in the REPL or in your code:

  scala> Read[Foo]

and similarly with Get:

  scala> Get[Foo]

And find the missing instance and construct it as needed. Refer to Chapter 12
of the book of doobie for more information.
""")
sealed trait Read[A] {
  def unsafeGet(rs: ResultSet, startIdx: Int): A
  def gets: List[(Get[?], NullabilityKnown)]
  def toOpt: Read[Option[A]]
  def length: Int

  final def get(n: Int): ResultSetIO[A] =
    IFRS.raw(unsafeGet(_, n))

  final def map[B](f: A => B): Read[B] = new Read.Transform[B, A](this, f)

  final def ap[B](ff: Read[A => B]): Read[B] = {
    new Read.Composite[B, A => B, A](ff, this, (f, a) => f(a))
  }
}

object Read extends LowerPriority1Read {

  def apply[A](implicit ev: Read[A]): Read[A] = ev

  def derived[A](implicit
      @implicitNotFound(
        "Cannot derive Read instance. Please check that each field in the case class has a Read instance or can derive one")
      ev: Derived[MkRead[A]]
  ): Read[A] = ev.instance.underlying

  trait Auto extends MkReadInstances

  implicit val ReadApply: Applicative[Read] =
    new Applicative[Read] {
      def ap[A, B](ff: Read[A => B])(fa: Read[A]): Read[B] = fa.ap(ff)
      def pure[A](x: A): Read[A] = unitRead.map(_ => x)
      override def map[A, B](fa: Read[A])(f: A => B): Read[B] = fa.map(f)
    }

  implicit val unitRead: Read[Unit] = new Read[Unit] {
    override def unsafeGet(rs: ResultSet, startIdx: Int): Unit = {
      () // Does not read anything from ResultSet
    }
    override def gets: List[(Get[?], NullabilityKnown)] = List.empty
    override def toOpt: Read[Option[Unit]] = this.map(_ => Some(()))
    override def length: Int = 0
  }

  /** Simple instance wrapping a Get. i.e. single column non-null value */
  class Single[A](get: Get[A]) extends Read[A] {
    def unsafeGet(rs: ResultSet, startIdx: Int): A =
      get.unsafeGetNonNullable(rs, startIdx)

    override def toOpt: Read[Option[A]] = new SingleOpt(get)

    override def gets: List[(Get[?], NullabilityKnown)] = List(get -> NoNulls)

    override val length: Int = 1

  }

  /** Simple instance wrapping a Get. i.e. single column nullable value */
  class SingleOpt[A](get: Get[A]) extends Read[Option[A]] {
    def unsafeGet(rs: ResultSet, startIdx: Int): Option[A] =
      get.unsafeGetNullable(rs, startIdx)

    override def toOpt: Read[Option[Option[A]]] = new Transform[Option[Option[A]], Option[A]](this, a => Some(a))
    override def gets: List[(Get[?], NullabilityKnown)] = List(get -> Nullability.Nullable)

    override val length: Int = 1
  }

  class Transform[A, From](underlyingRead: Read[From], f: From => A) extends Read[A] {
    override def unsafeGet(rs: ResultSet, startIdx: Int): A = f(underlyingRead.unsafeGet(rs, startIdx))
    override def gets: List[(Get[?], NullabilityKnown)] = underlyingRead.gets
    override def toOpt: Read[Option[A]] =
      new Transform[Option[A], Option[From]](underlyingRead.toOpt, opt => opt.map(f))
    override lazy val length: Int = underlyingRead.length
  }

  /** A Read instance consists of multiple underlying Read instances */
  class Composite[A, S0, S1](read0: Read[S0], read1: Read[S1], f: (S0, S1) => A) extends Read[A] {
    override def unsafeGet(rs: ResultSet, startIdx: Int): A = {
      val r0 = read0.unsafeGet(rs, startIdx)
      val r1 = read1.unsafeGet(rs, startIdx + read0.length)
      f(r0, r1)
    }

    override lazy val gets: List[(Get[?], NullabilityKnown)] =
      read0.gets ++ read1.gets

    override def toOpt: Read[Option[A]] = {
      val readOpt0 = read0.toOpt
      val readOpt1 = read1.toOpt
      new Composite[Option[A], Option[S0], Option[S1]](
        readOpt0,
        readOpt1,
        {
          case (Some(s0), Some(s1)) => Some(f(s0, s1))
          case _                    => None
        })

    }
    override lazy val length: Int = read0.length + read1.length
  }

}

trait LowerPriority1Read extends LowerPriority2Read {

  implicit def fromReadOption[A](implicit read: Read[A]): Read[Option[A]] = read.toOpt

}

trait LowerPriority2Read extends ReadPlatform {

  implicit def fromGet[A](implicit get: Get[A]): Read[A] = new Read.Single(get)

  implicit def fromGetOption[A](implicit get: Get[A]): Read[Option[A]] = new Read.SingleOpt(get)

}

trait LowestPriorityRead {
  implicit def fromDerived[A](implicit ev: Derived[Read[A]]): Read[A] = ev.instance
}

final class MkRead[A](val underlying: Read[A]) extends Read[A] {
  override def unsafeGet(rs: ResultSet, startIdx: Int): A = underlying.unsafeGet(rs, startIdx)
  override def gets: List[(Get[?], NullabilityKnown)] = underlying.gets
  override def toOpt: Read[Option[A]] = underlying.toOpt
  override def length: Int = underlying.length
}

object MkRead extends MkReadInstances

trait MkReadInstances extends MkReadPlatform




© 2015 - 2025 Weber Informatics LLC | Privacy Policy