---
# System prepended metadata

title: Scala Quill filtering

---

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)
  }
```