zio / zio-quill

Compile-time Language Integrated Queries for Scala
https://zio.dev/zio-quill
Apache License 2.0
2.15k stars 346 forks source link

quill-cassandra MappedEncoding (decode case) is not working as expected using driver LocalDate in some cases #1207

Closed glammers1 closed 5 years ago

glammers1 commented 5 years ago

Version: 2.5.4 Module: quill-cassandra Database: cassandra

Expected behavior

Custom MappedEncoding for org.joda.time.LocalDate should work like the one already supported for java.time.LocalDate in quill-cassandra

import com.datastax.driver.core.{LocalDate => CassandraDriverLocalDate}
import org.joda.time.LocalDate

implicit val encodeLocalDate: MappedEncoding[CassandraDriverLocalDate, LocalDate] =
    MappedEncoding[CassandraDriverLocalDate, LocalDate](new LocalDate(_))
implicit val decodeLocalDate: MappedEncoding[LocalDate, CassandraDriverLocalDate] =
    MappedEncoding[LocalDate, CassandraDriverLocalDate](ld =>
      CassandraDriverLocalDate.fromYearMonthDay(ld.getYear, ld.getMonthOfYear, ld.getDayOfMonth))

Actual behavior

Insert (encode) works fine but read operation (decode) throws the following exception:

java.lang.IllegalArgumentException: No partial converter found for type: com.datastax.driver.core.LocalDate
  org.joda.time.convert.ConverterManager.getPartialConverter(ConverterManager.java:253)
  org.joda.time.LocalDate.<init>(LocalDate.java:415)
  org.joda.time.LocalDate.<init>(LocalDate.java:363)
  ammonite.$file.prueba_quill$X$.$anonfun$encodeLocalDate$1(prueba_quill.sc:17)
  io.getquill.dsl.EncodingDsl.$anonfun$mappedBaseDecoder$1(EncodingDsl.scala:70)
  io.getquill.dsl.EncodingDsl.$anonfun$mappedBaseDecoder$1$adapted(EncodingDsl.scala:70)
  io.getquill.context.cassandra.encoding.Decoders$CassandraDecoder.apply(Decoders.scala:16)
  ammonite.$file.prueba_quill$$anon$1.$anonfun$extract$1(prueba_quill.sc:46)
  scala.collection.immutable.List.map(List.scala:283)
  io.getquill.CassandraSyncContext.executeQuery(CassandraSyncContext.scala:40)
  ammonite.$file.prueba_quill$.<init>(prueba_quill.sc:46)
  ammonite.$file.prueba_quill$.<clinit>(prueba_quill.sc)

Steps to reproduce the behavior

I'm using Ammonite to reproduce the error. I've attached a zip with two files for both joda and java.time examples.

First, create a C* keyspace "test_quill" with a table "my_table".

CREATE KEYSPACE test_quill
WITH durable_writes = true
AND replication = {
    'class' : 'SimpleStrategy',
    'replication_factor' : 1
};

CREATE TABLE test_quill.my_table (
    id date,
    PRIMARY KEY (id)
);
import $ivy.`io.getquill::quill-cassandra:2.5.4`
import $ivy.`io.getquill::quill-core:2.5.4`
import $ivy.`joda-time:joda-time:2.10`
import io.getquill._
import org.joda.time.LocalDate
import com.datastax.driver.core.Cluster.Builder
import com.datastax.driver.core.{Cluster, ConsistencyLevel, QueryOptions}
import com.datastax.driver.core.{LocalDate => CassandraDriverLocalDate}

object EncodersDecoders {
  implicit val encodeLocalDate: MappedEncoding[CassandraDriverLocalDate, LocalDate] =
    MappedEncoding[CassandraDriverLocalDate, LocalDate](new LocalDate(_))
  implicit val decodeLocalDate: MappedEncoding[LocalDate, CassandraDriverLocalDate] =
    MappedEncoding[LocalDate, CassandraDriverLocalDate](ld =>
      CassandraDriverLocalDate.fromYearMonthDay(ld.getYear, ld.getMonthOfYear, ld.getDayOfMonth))
}

final case class MyTable(
  id: LocalDate
)

val cassandraHost = "localhost"
val keyspace = "test_quill"

val cluster = Cluster.builder().addContactPoint(cassandraHost).build()

val cassandraSyncContext = new CassandraSyncContext(SnakeCase, cluster, keyspace, 10)
import cassandraSyncContext._

import EncodersDecoders._

val data =
  MyTable(
    new LocalDate(2016,1,2)
  )

// insert is ok
run(quote {
  query[MyTable].insert(lift(data))
})

// the fail
println(run(quote {
  query[MyTable]
}))
import $ivy.`io.getquill::quill-cassandra:2.5.4`
import $ivy.`io.getquill::quill-core:2.5.4`
import io.getquill._
import com.datastax.driver.core.Cluster.Builder
import com.datastax.driver.core.{Cluster, ConsistencyLevel, QueryOptions}
import java.time._

final case class MyTable(
  id: LocalDate
)

val cassandraHost = "localhost"
val keyspace = "test_quill"

val cluster = Cluster.builder().addContactPoint(cassandraHost).build()

val cassandraSyncContext = new CassandraSyncContext(SnakeCase, cluster, keyspace, 10)
import cassandraSyncContext._

val data =
  MyTable(
    LocalDate.of(1989, 11, 11)
  )

// ok
run(quote {
  query[MyTable].insert(lift(data))
})

// ok
println(run(quote {
  query[MyTable]
}))

Workaround

Use raw enconding for joda:

import org.joda.time.LocalDate
import com.datastax.driver.core.{LocalDate => CassandraDriverLocalDate}

implicit val localDateDecoder: Decoder[LocalDate] =
    decoder((index, row) => new LocalDate(row.getObject(index).toString))
implicit val localDateEncoder: Encoder[LocalDate] =
    encoder(
      (index, value, row) =>
        row.setDate(
          index,
          CassandraDriverLocalDate.fromYearMonthDay(
            value.getYear,
            value.getMonthOfYear,
            value.getDayOfMonth
          )
      )
    )

Why?

test_cases.zip

@getquill/maintainers

fwbrasil commented 5 years ago

the issue seems to be this line:

MappedEncoding[CassandraDriverLocalDate, LocalDate](new LocalDate(_))

the LocalDate constructor seems to reject the CassandraDriverLocalDate value. It doesn't seem to be a quill bug, but feel free to reopen if necessary.

glammers1 commented 5 years ago

You are entirely right, I had been changing in my program the CQL data types related with dates from text to timestamp, date... I have had other issues (fixed prior to this one) and I was not aware that LocalDate constructor had the problem in runtime :man_facepalming:

@ CassandraDriverLocalDate.fromYearMonthDay(2018, 11, 14) 
res5: com.datastax.driver.core.LocalDate = 2018-11-14

@ new LocalDate(res5) 
java.lang.IllegalArgumentException: No partial converter found for type: com.datastax.driver.core.LocalDate
  org.joda.time.convert.ConverterManager.getPartialConverter(ConverterManager.java:253)
  org.joda.time.LocalDate.<init>(LocalDate.java:415)
  org.joda.time.LocalDate.<init>(LocalDate.java:363)
  ammonite.$sess.cmd6$.<init>(cmd6.sc:1)
  ammonite.$sess.cmd6$.<clinit>(cmd6.sc)

I've changed to:

implicit val decodeLocalDate: MappedEncoding[CassandraDriverLocalDate, LocalDate] =
    MappedEncoding[CassandraDriverLocalDate, LocalDate](ld =>
      new LocalDate(ld.getYear, ld.getMonth, ld.getDay))

Thanks! Sorry for the time spent on this, keep up the good work!