tminglei / slick-pg

Slick extensions for PostgreSQL
BSD 2-Clause "Simplified" License
837 stars 180 forks source link

`ClassCastException` when reading from `column[List[Double]]` of column type `numeric[]` #589

Open TobiasPfeifer opened 1 year ago

TobiasPfeifer commented 1 year ago

slick-pg version: 0.20.3 slick version: 3.3.3

A table with a column of type numeric[] that is defined as column[List[Double]] will insert List[Double] for that column just fine. However, it will not read a List[Double] but a List[BigDecimal] at runtime.

This will result in a ClassCastException when trying to access the values of List[Double]: ERROR: java.lang.ClassCastException: class java.math.BigDecimal cannot be cast to class java.lang.Double (java.math.BigDecimal and java.lang.Double are in module java.base of loader 'bootstrap')

create table test
(
    id                   serial primary key,
    test             numeric[]
);

case class Test(test: List[Double]) //the field test will be read as List[BigDecimal]

def id                  = column[Int]("id", O.PrimaryKey, O.AutoInc)
def test                  = column[List[Double]]("test")
    def * =
      (
        id.? ::
          test::
          HNil
      ).<>(applyTest, unapplyTest)

def applyTest(data: Option[Int] :: List[Double] :: HNil): Test = data match {
      data match {
        case _ :: list => Test(list)
      }
}

VPostgresProfile.api._ and VPostgresProfile is in scope:

trait VPostgresProfile
    extends ExPostgresProfile
    with PgArraySupport
    with PgDate2Support
    with PgRangeSupport
    with PgHStoreSupport {
  def pgjson = "jsonb"

  // Add back `capabilities.insertOrUpdate` to enable native `upsert` support; for postgres 9.5+
  override protected def computeCapabilities: Set[Capability] =
    super.computeCapabilities + slick.jdbc.JdbcCapabilities.insertOrUpdate

  override val api = VAPI

  object VAPI extends API with ArrayImplicits with DateTimeImplicits with RangeImplicits with HStoreImplicits {
    val strSimpleJdbcArrayListType: SimpleArrayJdbcType[String] = new SimpleArrayJdbcType[String]("text")
    val longSimpleJdbcArrayListType: SimpleArrayJdbcType[Long]  = new SimpleArrayJdbcType[Long]("bigint")
    val sqlTSSimpleJdbcArrayListType: SimpleArrayJdbcType[SQLTimestamp] =
      new SimpleArrayJdbcType[SQLTimestamp]("timestamp")

    implicit val strListTypeMapper: JdbcType[List[String]] =
      strSimpleJdbcArrayListType.to(_.toList)
  }
}

object VPostgresProfile extends VPostgresProfile

workaround:

def applyTest(data: Option[Int] :: List[Double] :: HNil): Test = data match {
      data match {
        case _ :: list => Test(list.asInstanceOf[List[java.math.BigDecimal]].map(_.doubleValue))
      }
tminglei commented 1 year ago

@TobiasPfeifer numeric[] was bind to List[BigDecimal], and float8[] was bind to List[Double]. But pg can accept double[] to numeric[], so the insert with List[Double] is ok.

You can change the declaration of test numeric[] to test float8[], to keep them consistent.

perotom commented 1 month ago

I had the same issue. When I want to use BigDecimal instead, I get the following compile error:

could not find implicit value for parameter tt: slick.ast.TypedType[Option[List[BigDecimal]]]
    def valueNumericArray = column[Option[List[BigDecimal]]]("value_numeric_array")

Declaration looks like: def valueNumericArray = column[Option[List[BigDecimal]]]("value_numeric_array")