Scala Quill filtering === If we only needed `from` and `MicroserviceName` it would be simple: ```scala def listMicroservices(svcName: Option[ServiceName], msvcName: Option[MicroserviceName], from: Option[MicroserviceName]) = { run(quote { for { s <- services.filter(s => lift(svcName).forall(_ == s.name)) m <- microservices .filter(m => lift(msvcName).forall(_ == m.name)) .filter(m => lift(from).forall(_ < m.name)) // <--- .filter(_.serviceId == s.id) } yield m }) } ``` But since users can sort on an arbitrary field, `from` needs to be `Option[String]`, in which case it needs to be mapped with `MicroserviceName.apply`: ```scala .filter(m => lift(from.map(MicroserviceName.apply)).forall(_ < m.name)) ``` Now, unless we want `if()` blocks or `case` switches based on the `sortBy` field, both `MicroserviceName.apply` and `.name` will have to be method references: ```scala val apply = MicroserviceName.apply val nameGetter = (m: Microservice) => m.name .filter(m => lift(from.map(apply)).forall(_ < getter(m))) ``` Unfortunately, quill doesn't let you make arbitrary function calls like that. You need to do something like this: ```scala def withFilter[T] = quote { (q: Query[T], f: Quoted[T => Boolean]) => q.filter(f(_)) } def listMicroservices(svcName: Option[ServiceName], msvcName: Option[MicroserviceName], from: Option[String]) = { val apply = MicroserviceName.apply val nameGetter = (m: Microservice) => m.name run(quote { for { s <- services.filter(s => lift(svcName).forall(_ == s.name)) m <- microservices .filter(m => lift(msvcName).forall(_ == m.name)) .filter(_.serviceId == s.id) } yield m withFilter(microservices,(m: Microservice) => lift(from.map(apply)).forall(_ < nameGetter(m))) // <-- }) } ``` Of course, it's still specific to `Microservice` and `<`. To make it fully generic, we get this sweet Scala code. It's probably overboard. ```scala /** * Creates a (quoted) filter for the given operation. For example, if you want to check that a Microservice has a * name that is less than the given value, you would invoke * * fieldOp(value, (m: Microservice) => m.name, MicroserviceName.apply, lt) * * (Also see the definition of `lt`.) */ def fieldOp[T, F <: AnyVal](value: Option[String], getter: T => F, apply: String => F, op: (F, F) => Quoted[Boolean]): Quoted[T => Boolean] = (t: T) => lift(value.map(apply)).forall(op(_, getter(t))) def withFilter[T] = quote { (q: Query[T], f: Quoted[T => Boolean]) => q.filter(f(_)) } def lt = (x: AnyVal, y: AnyVal) => AnyValQuotes(x) < y def gt = (x: AnyVal, y: AnyVal) => AnyValQuotes(x) > y def msvcNameComp(value: Option[String], op: (MicroserviceName, MicroserviceName) => Quoted[Boolean]): Quoted[Microservice => Boolean] = fieldOp(value, (m: Microservice) => m.name, MicroserviceName.apply, op) def listMicroservices(svcName: Option[ServiceName], msvcName: Option[MicroserviceName], from: Option[String]) = { run(quote { for { s <- services.filter(s => lift(svcName).forall(_ == s.name)) m <- microservices .filter(m => lift(msvcName).forall(_ == m.name)) .filter(_.serviceId == s.id) } yield m withFilter(microservices, msvcNameComp(from, lt)) }) } ``` **Update (2020-11-06 16:45 CT)** It can also be done like this. The operator (`<`) is not generic, but it's cleaner: ```scala def listMicroservices(svcName: Option[ServiceName], msvcName: Option[MicroserviceName], from: Option[String], to: Option[String]) = { val apply: Quoted[String => MicroserviceName] = MicroserviceName.apply(_) val fieldGetter: Microservice => MicroserviceName = (m: Microservice) => m.name val q = quote { for { s <- services.filter(s => lift(svcName).forall(_ == s.name)) m <- microservices .filter(m => lift(msvcName).forall(_ == m.name)) .filter(m => lift(from.map(apply)).forall(_ < lift(fieldGetter(m)))) .filter(m => lift(to.map(apply)).forall(_ > lift(fieldGetter(m)))) .filter(_.serviceId == s.id) } yield m } run(q) } ``` Unfortunately this doesn't work (`Can't find Encoder for type 'AnyVal'`): ```scala def listMicroservices(svcName: Option[ServiceName], msvcName: Option[MicroserviceName], from: Option[String], to: Option[String], sortBy: SortField = SortField.Name) = { var apply: Quoted[String => AnyVal] = null var fieldGetter: Microservice => AnyVal = null sortBy match { case SortField.Name => apply = MicroserviceName.apply(_) fieldGetter = (m: Microservice) => m.name case SortField.Id => apply = MicroserviceId.apply(_) fieldGetter = (m: Microservice) => m.id } val q = quote { for { s <- services.filter(s => lift(svcName).forall(_ == s.name)) m <- microservices .filter(m => lift(msvcName).forall(_ == m.name)) .filter(m => lift(from.map(apply)).forall(_ < lift(fieldGetter(m)))) .filter(m => lift(to.map(apply)).forall(_ > lift(fieldGetter(m)))) .filter(_.serviceId == s.id) } yield m } run(q) } ``` So the query has to be broken up: ```scala def listMicroservices(svcName: Option[ServiceName], msvcName: Option[MicroserviceName], from: Option[String], to: Option[String], sortBy: SortField = SortField.Name, limit: Int = 50) = { val q = quote { for { s <- services .filter(s => lift(svcName).forall(_ == s.name)) m <- microservices .filter(m => lift(msvcName).forall(_ == m.name)) .filter(_.serviceId == s.id) .take(limit) } yield m } val r = sortBy match { case SortField.Name => q.dynamic .filter(m => lift(from.map(MicroserviceName.apply)).forall(_ < m.name)) .filter(m => lift(to.map(MicroserviceName.apply)).forall(_ > m.name)) .sortBy(_.name) case SortField.Id => q.dynamic .filter(m => lift(from.map(MicroserviceId.apply)).forall(_ < m.id)) .filter(m => lift(to.map(MicroserviceId.apply)).forall(_ > m.id)) .sortBy(_.id) } run(r) } ```