Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Fix if (liftQuery(set).isEmpty) #1559

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

mdedetrich
Copy link
Collaborator

@mdedetrich mdedetrich commented Aug 11, 2019

Fixes #1557

Progress

  • Produce a minimal failing test
  • Come up with a solution

Problem

This is a PR aiming to fix liftQueryScalar which is currently broken.

So the core issue we have is that is on expansion of reified liftings. Reifying a traversable liftings is done as so

private def expandLiftings(statement: Statement, emptySetContainsToken: Token => Token) = {
    Statement {
      statement.tokens.foldLeft(List.empty[Token]) {
        case (tokens, SetContainsToken(a, op, ScalarLiftToken(lift: ScalarQueryLift))) =>
          lift.value.asInstanceOf[Traversable[Any]].toList match {
            case Nil => tokens :+ emptySetContainsToken(a)
            case values =>
              val liftings = values.map(v => ScalarLiftToken(ScalarValueLift(lift.name, v, lift.encoder)))
              val separators = List.fill(liftings.size - 1)(StringToken(", "))
              (tokens :+ stmt"$a $op (") ++ Interleave(liftings, separators) :+ StringToken(")")
          }
        case (tokens, token) =>
          tokens :+ token
      }
    }
  }

More specifically val liftings = values.map(v => ScalarLiftToken(ScalarValueLift(lift.name, v, lift.encoder))) is the part that creates a List of ScalarLiftToken. Here is the current code expanding this reification

  val prepare =
    (row: context.PrepareRow) => {
      val (_, values, prepare) = liftings.foldLeft((0, List.empty[Any], row)) {
        case ((idx, values, row), lift) =>
          val encoder = lift.encoder.asInstanceOf[context.Encoder[Any]]
          val newRow = encoder(idx, lift.value, row)
          (idx + 1, lift.value :: values, newRow)
      }
      (values, prepare)
    }

The problem here occurs when you have a custom encoder for something traversable. The ClassCastException happens at encoder(idx, lift.value, row). We happen to be applying the encoder to the collection rather than every element inside the collection.

Solution

Unfortunately the solution isn't going to be nice. Either its going to be hacky but will work without any major changes, but in order to create a clean solution some major changes need to be done.

To clarify, the issue is we have no way to distinguish whether an Encoder should be used on the collection or an every element inside the collection when doing expansion. When doing the expansion we just have some generic Encoder. If our Encoder happens to be a custom encoder than we typically do want to apply it on every element (linked issue describes this in more detail) however if its for already supported types (i.e. lets say a List of ints) then we apply the encoder to the entire list. To compound the problem further, expansion is shared amongst all modules so it needs to be completely generic (so currently distinguishing between jdbc/postgres-async/cassandra etc is impossible at this point)

From what I can see, the only way to solve without major changes is to catch a ClassCastException on the assumption we are hitting this case (custom encoder that needs to be applied on every element in the collection) and then if there is a problem here then we have to rethrow this same ClassCastException

Checklist

  • Unit test all changes
  • Update README.md if applicable
  • Add [WIP] to the pull request title if it's work in progress
  • Squash commits that aren't meaningful changes
  • Run sbt scalariformFormat test:scalariformFormat to make sure that the source files are formatted

@getquill/maintainers

@mdedetrich mdedetrich force-pushed the fix-lift-query-scalar branch 2 times, most recently from 8b456a1 to 60d1499 Compare August 13, 2019 13:51
@mdedetrich mdedetrich changed the title [WIP] Fix liftQueryScalar [WIP] Fix if (liftQuery(set).isEmpty) Aug 13, 2019
@mdedetrich
Copy link
Collaborator Author

mdedetrich commented Aug 13, 2019

So it turns out that the actual issue has nothing to do with liftQueryScalar but rather that the following query is broken

  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 liftQuery(set).isEmpty

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Matthew de Detrich seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

if (liftQuery(set).isEmpty) doesn't work
2 participants