mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-21 18:08:25 +00:00
Fixes searching items with fulltext
When using fulltext only search, then only the index must be searched. This wasn't working anymore, because the routes added a query to always select valid items (those not being processed). But this lead to the downstream code to always consult the database, too. Since the routes are using a "simple-search" interface, this is now adding the valid-state condition if applicable. There are still more low-level interfaces that can be used when searching should be done differently. Closes: #823
This commit is contained in:
@ -14,21 +14,38 @@ import docspell.query.ItemQuery._
|
||||
object FulltextExtract {
|
||||
|
||||
sealed trait Result
|
||||
sealed trait SuccessResult extends Result
|
||||
sealed trait SuccessResult extends Result {
|
||||
def getFulltextPart: Option[String]
|
||||
def getExprPart: Option[Expr]
|
||||
}
|
||||
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
|
||||
final case class SuccessNoFulltext(query: Expr) extends SuccessResult {
|
||||
val getExprPart = Some(query)
|
||||
val getFulltextPart = None
|
||||
}
|
||||
final case class SuccessNoExpr(fts: String) extends SuccessResult {
|
||||
val getExprPart = None
|
||||
val getFulltextPart = Some(fts)
|
||||
}
|
||||
final case class SuccessBoth(query: Expr, fts: String) extends SuccessResult {
|
||||
val getExprPart = Some(query)
|
||||
val getFulltextPart = Some(fts)
|
||||
}
|
||||
final case object TooMany extends FailureResult
|
||||
final case object UnsupportedPosition extends FailureResult
|
||||
}
|
||||
|
||||
def findFulltext(expr: Expr): Result =
|
||||
lookForFulltext(expr)
|
||||
|
||||
/** Extracts the fulltext node from the given expr and returns it
|
||||
* together with the expr without that node.
|
||||
*/
|
||||
private def lookForFulltext(expr: Expr): Result =
|
||||
expr match {
|
||||
case Expr.Fulltext(ftq) =>
|
||||
Result.Success(ItemQuery.all.expr, ftq.some)
|
||||
Result.SuccessNoExpr(ftq)
|
||||
case Expr.AndExpr(inner) =>
|
||||
inner.collect({ case Expr.Fulltext(fq) => fq }) match {
|
||||
case Nil =>
|
||||
@ -36,7 +53,7 @@ object FulltextExtract {
|
||||
case e :: Nil =>
|
||||
val c = foldMap(isFulltextExpr)(expr)
|
||||
if (c > 1) Result.TooMany
|
||||
else Result.Success(expr, e.some)
|
||||
else Result.SuccessBoth(expr, e)
|
||||
case _ =>
|
||||
Result.TooMany
|
||||
}
|
||||
@ -47,7 +64,7 @@ object FulltextExtract {
|
||||
private def checkPosition(expr: Expr, max: Int): Result = {
|
||||
val c = foldMap(isFulltextExpr)(expr)
|
||||
if (c > max) Result.UnsupportedPosition
|
||||
else Result.Success(expr, None)
|
||||
else Result.SuccessNoFulltext(expr)
|
||||
}
|
||||
|
||||
private def foldMap[B: Monoid](f: Expr => B)(expr: Expr): B =
|
||||
|
@ -17,7 +17,6 @@ final case class ItemQuery(expr: ItemQuery.Expr, raw: Option[String]) {
|
||||
}
|
||||
|
||||
object ItemQuery {
|
||||
val all = ItemQuery(Expr.Exists(Attr.ItemId), Some(""))
|
||||
|
||||
sealed trait Operator
|
||||
object Operator {
|
||||
|
@ -1,12 +1,17 @@
|
||||
package docspell.query
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
|
||||
import docspell.query.internal.ExprParser
|
||||
import docspell.query.internal.ExprUtil
|
||||
|
||||
object ItemQueryParser {
|
||||
|
||||
def parse(input: String): Either[ParseFailure, ItemQuery] =
|
||||
if (input.isEmpty) Right(ItemQuery.all)
|
||||
if (input.isEmpty)
|
||||
Left(
|
||||
ParseFailure("", 0, NonEmptyList.of(ParseFailure.SimpleMessage(0, "No input.")))
|
||||
)
|
||||
else {
|
||||
val in = if (input.charAt(0) == '(') input else s"(& $input )"
|
||||
ExprParser
|
||||
|
@ -1,7 +1,5 @@
|
||||
package docspell.query
|
||||
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.query.FulltextExtract.Result
|
||||
|
||||
import munit._
|
||||
@ -16,38 +14,43 @@ class FulltextExtractTest extends FunSuite {
|
||||
def assertFts(qstr: String, expect: Result) =
|
||||
assertEquals(findFts(qstr), expect)
|
||||
|
||||
def assertFtsSuccess(qstr: String, expect: Option[String]) = {
|
||||
def assertFtsSuccess(qstr: String, expect: String) = {
|
||||
val q = ItemQueryParser.parseUnsafe(qstr)
|
||||
assertEquals(findFts(qstr), Result.Success(q.expr, expect))
|
||||
assertEquals(findFts(qstr), Result.SuccessBoth(q.expr, expect))
|
||||
}
|
||||
|
||||
def assertNoFts(qstr: String) = {
|
||||
val q = ItemQueryParser.parseUnsafe(qstr)
|
||||
assertEquals(findFts(qstr), Result.SuccessNoFulltext(q.expr))
|
||||
}
|
||||
|
||||
test("find fulltext as root") {
|
||||
assertEquals(findFts("content:what"), Result.Success(ItemQuery.all.expr, "what".some))
|
||||
assertEquals(findFts("content:what"), Result.SuccessNoExpr("what"))
|
||||
assertEquals(
|
||||
findFts("content:\"what hello\""),
|
||||
Result.Success(ItemQuery.all.expr, "what hello".some)
|
||||
Result.SuccessNoExpr("what hello")
|
||||
)
|
||||
assertEquals(
|
||||
findFts("content:\"what OR hello\""),
|
||||
Result.Success(ItemQuery.all.expr, "what OR hello".some)
|
||||
Result.SuccessNoExpr("what OR hello")
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
findFts("(& content:\"what OR hello\" )"),
|
||||
Result.Success(ItemQuery.all.expr, "what OR hello".some)
|
||||
Result.SuccessNoExpr("what OR hello")
|
||||
)
|
||||
}
|
||||
|
||||
test("find no fulltext") {
|
||||
assertFtsSuccess("name:test", None)
|
||||
assertNoFts("name:test")
|
||||
}
|
||||
|
||||
test("find fulltext within and") {
|
||||
assertFtsSuccess("content:what name:test", "what".some)
|
||||
assertFtsSuccess("names:marc* content:what name:test", "what".some)
|
||||
assertFtsSuccess("content:what name:test", "what")
|
||||
assertFtsSuccess("names:marc* content:what name:test", "what")
|
||||
assertFtsSuccess(
|
||||
"names:marc* date:2021-02 content:\"what else\" name:test",
|
||||
"what else".some
|
||||
"what else"
|
||||
)
|
||||
}
|
||||
|
||||
@ -59,6 +62,6 @@ class FulltextExtractTest extends FunSuite {
|
||||
|
||||
test("wrong fulltext search position") {
|
||||
assertFts("name:test (| date:2021-02 content:yes)", Result.UnsupportedPosition)
|
||||
assertFtsSuccess("name:test (& date:2021-02 content:yes)", "yes".some)
|
||||
assertFtsSuccess("name:test (& date:2021-02 content:yes)", "yes")
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package docspell.query.internal
|
||||
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.query.ItemQuery
|
||||
import docspell.query.ItemQueryParser
|
||||
|
||||
import munit._
|
||||
@ -39,9 +38,9 @@ class ItemQueryParserTest extends FunSuite {
|
||||
assertEquals(expect, q)
|
||||
}
|
||||
|
||||
test("return all if query is empty") {
|
||||
val q = ItemQueryParser.parseUnsafe("")
|
||||
assertEquals(ItemQuery.all, q)
|
||||
test("throw if query is empty") {
|
||||
val result = ItemQueryParser.parse("")
|
||||
assert(result.isLeft)
|
||||
}
|
||||
|
||||
test("splice inner and nodes") {
|
||||
|
Reference in New Issue
Block a user