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

exception.UnknownOidException.scala Maven / Gradle / Ivy

// Copyright (c) 2018-2021 by Rob Norris
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package skunk.exception

import cats.syntax.all._
import skunk._
import skunk.data.TypedRowDescription
import skunk.net.message.RowDescription
import skunk.util.Text
import skunk.syntax.list._
import cats.data.Ior
import skunk.data.Type
import Strategy._

case class UnknownOidException(
  query: Query[_, _],
  types: List[(RowDescription.Field, Option[TypedRowDescription.Field])],
  strategy: Strategy
) extends SkunkException(
  sql       = Some(query.sql),
  message   = "Unknown oid(s) in row description.",
  detail    = Some("Skunk could not decode the row description for this query because it cannot determine the Scala types corresponding to one or more Postgres type oids."),
  hint      = Some {
    strategy match {
      case SearchPath   => "A referenced type was created after this session was initiated, or it is in a namespace that's not on the search path (see note below)."
      case BuiltinsOnly => "Try changing your typing strategy (see note below)."
     }
  },
  sqlOrigin = Some(query.origin),
) {

  import Text.{ green, red, cyan, empty }
  implicit def stringToText(s: String): Text = Text(s)

  def unk(f: RowDescription.Field): Text =
    s"${f.typeOid}" ///${f.typeMod}"

  private def describe(ior: Ior[(RowDescription.Field, Option[TypedRowDescription.Field]), Type]): List[Text] =
    ior match {
      case Ior.Left((f1,None))        => List(green(f1.name), unk(f1),     "->", red(""), cyan(s"── unmapped column, unknown type oid"))
      case Ior.Left((f1,Some(f2)))    => List(green(f1.name), f2.tpe.name, "->", red(""), cyan( "── unmapped column"))
      case Ior.Right(t)               => List(empty,          empty,       "->", t.name,  cyan( "── missing column"))
      case Ior.Both((f1,None), t)     => List(green(f1.name), unk(f1),     "->", t.name,  cyan(s"── unknown type oid"))
      case Ior.Both((f1,Some(f2)), t) => List(green(f1.name), f2.tpe.name, "->", t.name,  if (f2.tpe === t) empty else
                                                                                          cyan( "── type mismatch"))
    }

  private def codeHint: String =
    strategy match {
      case SearchPath   =>
        s"""|Your typing strategy is ${Console.GREEN}Strategy.SearchPath${Console.RESET} which can normally detect user-defined types; however Skunk
            |only reads the system catalog once (when the Session is initialized) so user-created types are not
            |immediately usable. If you are creating a new type you might consider doing it on a single-use session,
            |before initializing other sessions that use it.
            |""".stripMargin
      case BuiltinsOnly =>
        s"""|Your typing strategy is ${Console.GREEN}Strategy.BuiltinsOnly${Console.RESET} which does not know about user-defined
            |types such as enums. To include user-defined types add the following argument when you
            |construct your session resource.
            |
            |  ${Console.GREEN}strategy = Strategy.SearchPath${Console.RESET}
            |
            |""".stripMargin
    }

  private def columns: String =
    s"""|The actual and asserted output columns are
        |
        |  ${Text.grid(types.align(query.decoder.types).map(describe)).intercalate(Text("\n|  ")).render}
        |
        |""".stripMargin

  override def sections: List[String] =
    super.sections :+ columns :+ codeHint

}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy