outworkers/phantom

Can't use a numeric enum in a where clause

Closed this issue · 4 comments

Hi,
I have an enum that looks like this:

object SiteIds extends Enumeration {
  type SiteId = Value

  val SITE_0 = Value(0)
  val SITE_1 = Value(1)
}

It is stored as a tinyint in Cassandra and is currently modeled in the Phantom table like this:

abstract class Events2 extends Table[Events2, Event] {

  implicit val siteIdPrimitive: Primitive[SiteIds.Value] =
    Primitive.derive[SiteIds.Value, Byte](_.id.toByte)(SiteIds(_))
...
  object siteId extends EnumColumn[SiteIds.Value] with PartitionKey
...
}

When I try to do a query on it like this:

.and(_.siteId eqs siteId)

it compiles, but at runtime the generated query (from the debug output) looks like this:

SELECT * FROM mydb.events2 WHERE profileId = '123' AND siteId = 'SITE_0' which doesn't work. It should be using a number for siteId instead of a string. I am able to read and write rows with this column just fine, I just can't use it in a where clause.

Also, previously I had SiteId everywhere instead of SiteIds.Value, but that resulted in the following compile-time error (slightly edited):

[error] <macro>:1:21: value SiteId is not a member of object com.test.db.SiteIds
[error] com.test.db.SiteIds.SiteId
[error]                     ^
[error] com/test/db/DatabaseClient.scala:86:89: type mismatch;
[error]  found   : Any
[error]  required: com.test.db.SiteIds.SiteId
[error]     (which expands to)  com.test.db.SiteIds.Value
[error]       val newTableResult = newEventsTable.select.where(_.profileId eqs profileId).and(_.siteId eqs siteId).fetch()
[error]                                                                                         ^

Note: for now I've worked around this by setting the column type to TinyInt and using Byte in the model, but of course this isn't quite as nice.

@RyanSattler The reason is because we have no type inside Value to go by. This is what actually happens behind the scenes to generate a datatype for any Enum#Value type lookup.

  def enumPrimitive(tpe: Type): Tree = {
    val comp = tpe.typeSymbol.name.toTermName

    q"""
      $prefix.Primitive.derive[$tpe#Value, $strType](_.toString)(
        str =>
          $comp.values.find(_.toString == str) match {
            case Some(value) => value
            case _ => throw new Exception(
              "Value " + str + " not found in enumeration"
            ) with scala.util.control.NoStackTrace
          }
      )
    """
  }

What surprised me is that you already define a primitive for siteId.

implicit val siteIdPrimitive: Primitive[SiteIds.Value] =
    Primitive.derive[SiteIds.Value, Byte](_.id.toByte)(SiteIds(_))

I think if this was available in the scope of the where clause, phantom should never reach the point where it needs to derive its own implicit for this type, so it could be as simple as making that implicit visible in the right place.

Hi @RyanSattler, I'm going to close this for now, simply move your already defined implicit into the right place if you can, and re-open this if required.

Thanks @alexflav23 - importing the implicit does fix the issue. Perhaps this might be related to #774 ? To work around that issue we're putting our queries one level higher than they're supposed to be (ie in our database client class instead of in the class that extends Table), so perhaps that is why the implicit was not automatically found.