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

if (liftQuery(set).isEmpty) doesn't work #1557

Open mdedetrich opened 5 years ago

mdedetrich commented 5 years ago

Version: (e.g. 3.4.1) Module: (e.g. quill-jdbc-monix) Database: (e.g. postgres)

Expected behavior

Lets assume that we are lifting at runtime a List of values T which require a custom Encoder for T

Typical use case for this is when we have some filter, i.e.

def myQuery(statusFilter: List[Status]) = quote {
  query[Table].filter{t =>
    if (liftQueryScalar(statusFilter).isEmpty)
      true
    else
      liftQueryScalar(statusFilter).contains(rp.status)
  }
}

Since this compiles, I expect it to work fine when statusFilteris a valid value. If I happen to be using the wrong liftQuery/liftQueryScalar/liftScalar I expect a compile time error. It also errors when you use other real values apart from Nil

Actual behavior

This compiles fine, however at runtime if statusFilter is Nil I happen to get

[error]  Got the exception java.lang.ClassCastException: scala.collection.immutable.Nil$ cannot be cast to enumeratum.EnumEntry (QueriesSpec.scala:118)

In my case enumeratum.EnumEntry happens to be super of Status (we are using Enumeratum).

Steps to reproduce the behavior

https://scastie.scala-lang.org/A6DIDy0rQh62lTi4nfYAyQ

Workaround

EDIT: There doesn't seem to be a workaround that actually generates correct queries and works at runtime. You can get it to compile by also using lift or liftScalar however you then get broken queries.

@getquill/maintainers

mdedetrich commented 5 years ago

I checked the quill codebase and realized there are no test cases for liftQueryScalar so it seems like it always broken.

mdedetrich commented 5 years ago

So an update, I have managed to further isolate the issue. Turns out the problem isn't with liftQueryScalar but rather with this expression

  def `Ex 8 and 9 contains2`(set: Set[Int]) =
    quote {
      query[Person].filter { p =>
        if (liftQuery(set).isEmpty)
          true
        else
          liftQuery(set).contains(p.age)
      }
    }

Specifically if (liftQuery(set).isEmpty) is what is failing.

virusdave commented 3 years ago

Any word on this? This is a pretty common pattern for optional filters-to-sets-of-values.