mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-21 18:08:25 +00:00
Resolve fulltext search queries the same way as before
For now, fulltext search is only possible when being the only term or inside the root AND expression.
This commit is contained in:
@ -0,0 +1,71 @@
|
||||
package docspell.query
|
||||
|
||||
import cats._
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.query.ItemQuery.Expr.AndExpr
|
||||
import docspell.query.ItemQuery.Expr.NotExpr
|
||||
import docspell.query.ItemQuery.Expr.OrExpr
|
||||
import docspell.query.ItemQuery._
|
||||
|
||||
/** Currently, fulltext in a query is only supported when in "root
|
||||
* AND" position
|
||||
*/
|
||||
object FulltextExtract {
|
||||
|
||||
sealed trait Result
|
||||
sealed trait SuccessResult extends Result
|
||||
sealed trait FailureResult extends Result
|
||||
object Result {
|
||||
case class Success(query: Expr, fts: Option[String]) extends SuccessResult
|
||||
case object TooMany extends FailureResult
|
||||
case object UnsupportedPosition extends FailureResult
|
||||
}
|
||||
|
||||
def findFulltext(expr: Expr): Result =
|
||||
lookForFulltext(expr)
|
||||
|
||||
private def lookForFulltext(expr: Expr): Result =
|
||||
expr match {
|
||||
case Expr.Fulltext(ftq) =>
|
||||
Result.Success(ItemQuery.all.expr, ftq.some)
|
||||
case Expr.AndExpr(inner) =>
|
||||
inner.collect({ case Expr.Fulltext(fq) => fq }) match {
|
||||
case Nil =>
|
||||
checkPosition(expr, 0)
|
||||
case e :: Nil =>
|
||||
val c = foldMap(isFulltextExpr)(expr)
|
||||
if (c > 1) Result.TooMany
|
||||
else Result.Success(expr, e.some)
|
||||
case _ =>
|
||||
Result.TooMany
|
||||
}
|
||||
case _ =>
|
||||
checkPosition(expr, 0)
|
||||
}
|
||||
|
||||
private def checkPosition(expr: Expr, max: Int): Result = {
|
||||
val c = foldMap(isFulltextExpr)(expr)
|
||||
if (c > max) Result.UnsupportedPosition
|
||||
else Result.Success(expr, None)
|
||||
}
|
||||
|
||||
private def foldMap[B: Monoid](f: Expr => B)(expr: Expr): B =
|
||||
expr match {
|
||||
case OrExpr(inner) =>
|
||||
inner.map(foldMap(f)).fold
|
||||
case AndExpr(inner) =>
|
||||
inner.map(foldMap(f)).fold
|
||||
case NotExpr(e) =>
|
||||
f(e)
|
||||
case _ =>
|
||||
f(expr)
|
||||
}
|
||||
|
||||
private def isFulltextExpr(expr: Expr): Int =
|
||||
expr match {
|
||||
case Expr.Fulltext(_) => 1
|
||||
case _ => 0
|
||||
}
|
||||
|
||||
}
|
@ -11,7 +11,10 @@ import docspell.query.ItemQuery.Attr.{DateAttr, StringAttr}
|
||||
* against a specific field of an item using some operator or a
|
||||
* combination thereof.
|
||||
*/
|
||||
final case class ItemQuery(expr: ItemQuery.Expr, raw: Option[String])
|
||||
final case class ItemQuery(expr: ItemQuery.Expr, raw: Option[String]) {
|
||||
def findFulltext: FulltextExtract.Result =
|
||||
FulltextExtract.findFulltext(expr)
|
||||
}
|
||||
|
||||
object ItemQuery {
|
||||
val all = ItemQuery(Expr.Exists(Attr.ItemId), Some(""))
|
||||
|
@ -0,0 +1,57 @@
|
||||
package docspell.query
|
||||
|
||||
import cats.implicits._
|
||||
import munit._
|
||||
import docspell.query.FulltextExtract.Result
|
||||
|
||||
class FulltextExtractTest extends FunSuite {
|
||||
|
||||
def findFts(q: String): Result = {
|
||||
val p = ItemQueryParser.parseUnsafe(q)
|
||||
FulltextExtract.findFulltext(p.expr)
|
||||
}
|
||||
|
||||
def assertFts(qstr: String, expect: Result) =
|
||||
assertEquals(findFts(qstr), expect)
|
||||
|
||||
def assertFtsSuccess(qstr: String, expect: Option[String]) = {
|
||||
val q = ItemQueryParser.parseUnsafe(qstr)
|
||||
assertEquals(findFts(qstr), Result.Success(q.expr, expect))
|
||||
}
|
||||
|
||||
test("find fulltext as root") {
|
||||
assertEquals(findFts("content:what"), Result.Success(ItemQuery.all.expr, "what".some))
|
||||
assertEquals(
|
||||
findFts("content:\"what hello\""),
|
||||
Result.Success(ItemQuery.all.expr, "what hello".some)
|
||||
)
|
||||
assertEquals(
|
||||
findFts("content:\"what OR hello\""),
|
||||
Result.Success(ItemQuery.all.expr, "what OR hello".some)
|
||||
)
|
||||
}
|
||||
|
||||
test("find no fulltext") {
|
||||
assertFtsSuccess("name:test", None)
|
||||
}
|
||||
|
||||
test("find fulltext within and") {
|
||||
assertFtsSuccess("content:what name:test", "what".some)
|
||||
assertFtsSuccess("$names:marc* content:what name:test", "what".some)
|
||||
assertFtsSuccess(
|
||||
"$names:marc* date:2021-02 content:\"what else\" name:test",
|
||||
"what else".some
|
||||
)
|
||||
}
|
||||
|
||||
test("too many fulltext searches") {
|
||||
assertFts("content:yes content:no", Result.TooMany)
|
||||
assertFts("content:yes (| name:test content:no)", Result.TooMany)
|
||||
assertFts("content:yes (| name:test (& date:2021-02 content:no))", Result.TooMany)
|
||||
}
|
||||
|
||||
test("wrong fulltext search position") {
|
||||
assertFts("name:test (| date:2021-02 content:yes)", Result.UnsupportedPosition)
|
||||
assertFts("name:test (& date:2021-02 content:yes)", Result.UnsupportedPosition) //TODO
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user