I'm trying to create a typesafe dynamic DSL for a Slick table but not sure how to achieve this.
Users can post filters to the server by sending filters in form/json format, and I need to build a Slick query with all that.
So basically this means transforming a Scala case class representing my filters to a Slick query.
It seems the "predicates" can have 3 different shapes. I've seen the trait CanBeQueryCondition
. Can I fold over these different possible shapes?
I've seen the extension methods &&
and ||
and know there is something to do with this but I just don't know how to do.
Basically, I have a list of predicates which takes the following types:
(PatientTable) => Column[Option[Boolean]]
or
(PatientTable) => Column[Boolean]
The problem to me is that there is not a single supertype for all the 3 different types that have a CanBeQueryCondition
, so I don't really know how do fold the predicates with &&
as once added to the list these differently shaped predicate takes a very generic type List[(PatientTable) => Column[_ >: Boolean with Option[Boolean]]]
.
Also, I'm not sure about what can be considered a predicate in Slick. A composable predicate seems to be Column[Boolean]
, but actually the filter
method only accept parameters of type (PatientTable) => Column[Boolean]
"fold" is already the keyword here. Or "reduce" since you don't need a seeding value.
buildFilter.reduce(_ && _)
Seems like to want a more general version of this: Dynamic OR filtering - Slick. I think my last example on this page is exactly what you want - it is just what cvogt proposes. I hope this helps.
I was looking for the same thing, and came across this question - the accepted answer was a very heavy inspiration to what I eventually landed on. Details are here.
The only comments I'd make about the accepted answer -
TablePredicate[Item, T <: Table[Item]]
can just be simplified toTablePredicate[T <: Table[_]]
because Item is never used (at least in the sample).LiteralColumn(1) === LiteralColumn(1)
can also just beLiteralColumn(Some(true))
(makes the generated queries slightly less awkward) - I'm pretty sure with a little more work, these could be eliminated entirely.I'm answering my own question with what I've finally built.
Let's define a simple case class and row mapper
Notion of predicate in Slick
I assume that the notion of "predicate" is what can be put inside
TableQuery.filter
. But this type is rather complex as it is a function that takes aTable
and returns a type that has an implicitCanBeQueryCondition
Unfornunately for me there are 3 different types that have a
CanBeQueryCondition
and putting them in a list to be folded into a single predicate seems not easy (iefilter
is easy to apply, but the&&
and||
operators are hard to apply (as far as I've tried)). But fortunately it seems we can convert easily aBoolean
to aColunm[Boolean]
to aColumn[Option[Boolean]]
with the.?
extension method.So let's define our predicate type:
Folding a list of predicates (ie using conjunctions/disjunctions, ie composing AND and OR clauses)
Now we only have one type so we can easily fold a list of predicates into a single
The dynamic filtering case class
From these predicate primitives we can start creating our dynamic, composable and typesafe query DSL based on a case class.
Notice the usage of
.?
for thecompanyScopeId
field which permits to fit a non-optional column to our definition of a Slick predicateUsing the DSL
Conclusion
This is far from being perfect but is a first draft and at least can give you some inspiration :) I would like Slick to make it easier to build such things that are very common in other query DSL (like Hibernate/JPA Criteria API)
See also this Gist for up-to-date solutions