lucille

Lucille is a small library for parsing and representing queries using the Lucene query syntax.

Usage

This library is currently available for Scala binary versions 2.12, 2.13, and 3.

Additionally, it's available for the JVM, Scala.js, and Scala Native.

To use the latest version, include the following in your build.sbt:

// use this snippet for the JVM
libraryDependencies += "pink.cozydev" %% "lucille" % "0.0.2"

// use this snippet for JS, Native, or cross-building
libraryDependencies += "pink.cozydev" %%% "lucille" % "0.0.2"

Parsing

Lucille offers a parse function to parse a whole string into a Lucille MultiQuery structure:

import pink.cozydev.lucille.QueryParser

QueryParser.parse("cats OR dogs")
// res0: Either[String, pink.cozydev.lucille.MultiQuery] = Right(
//   value = MultiQuery(
//     qs = NonEmptyList(
//       head = Or(
//         qs = NonEmptyList(
//           head = Term(str = "cats"),
//           tail = List(Term(str = "dogs"))
//         )
//       ),
//       tail = List()
//     )
//   )
// )

Last Query Rewriting

To enable a better interactive search experience, it can be helpful to rewrite the last term as a prefix term to enable partial matching on terms.

We'll write a helper function expandQ to rewrite Term queries into a query that matches either that term OR a Prefix query:

import pink.cozydev.lucille.Query

def expandQ(q: Query): Query =
  q match {
    case Query.Term(t) => Query.Or(Query.Term(t), Query.Prefix(t))
    case _ => q
  }

We can now use expandQ along with mapLastTerm to rewrite the last term of a MultiQuery into our expanded term + prefix:

QueryParser.parse("cats meo").map(mq => mq.mapLastTerm(expandQ))
// res1: Either[String, pink.cozydev.lucille.MultiQuery] = Right(
//   value = MultiQuery(
//     qs = NonEmptyList(
//       head = Term(str = "cats"),
//       tail = List(
//         Or(
//           qs = NonEmptyList(
//             head = Term(str = "meo"),
//             tail = List(Prefix(str = "meo"))
//           )
//         )
//       )
//     )
//   )
// )

This also works when the last term is part of a boolean or field query.

QueryParser.parse("cats AND do").map(mq => mq.mapLastTerm(expandQ))
// res2: Either[String, pink.cozydev.lucille.MultiQuery] = Right(
//   value = MultiQuery(
//     qs = NonEmptyList(
//       head = And(
//         qs = NonEmptyList(
//           head = Term(str = "cats"),
//           tail = List(
//             Or(
//               qs = NonEmptyList(
//                 head = Term(str = "do"),
//                 tail = List(Prefix(str = "do"))
//               )
//             )
//           )
//         )
//       ),
//       tail = List()
//     )
//   )
// )