mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 10:28:27 +00:00
Send no fts query if it is disabled
This commit is contained in:
@ -147,7 +147,7 @@ object OSearch {
|
|||||||
case Some(ftq) =>
|
case Some(ftq) =>
|
||||||
for {
|
for {
|
||||||
timed <- Duration.stopTime[F]
|
timed <- Duration.stopTime[F]
|
||||||
ftq <- createFtsQuery(q.fix.account, batch, ftq)
|
ftq <- createFtsQuery(q.fix.account, ftq)
|
||||||
|
|
||||||
results <- WeakAsync.liftK[F, ConnectionIO].use { nat =>
|
results <- WeakAsync.liftK[F, ConnectionIO].use { nat =>
|
||||||
val tempTable = temporaryFtsTable(ftq, nat)
|
val tempTable = temporaryFtsTable(ftq, nat)
|
||||||
@ -206,7 +206,7 @@ object OSearch {
|
|||||||
fulltextQuery match {
|
fulltextQuery match {
|
||||||
case Some(ftq) =>
|
case Some(ftq) =>
|
||||||
for {
|
for {
|
||||||
ftq <- createFtsQuery(q.fix.account, Batch.limit(500), ftq)
|
ftq <- createFtsQuery(q.fix.account, ftq)
|
||||||
results <- WeakAsync.liftK[F, ConnectionIO].use { nat =>
|
results <- WeakAsync.liftK[F, ConnectionIO].use { nat =>
|
||||||
val tempTable = temporaryFtsTable(ftq, nat)
|
val tempTable = temporaryFtsTable(ftq, nat)
|
||||||
store.transact(
|
store.transact(
|
||||||
@ -221,13 +221,12 @@ object OSearch {
|
|||||||
|
|
||||||
private def createFtsQuery(
|
private def createFtsQuery(
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
batch: Batch,
|
|
||||||
ftq: String
|
ftq: String
|
||||||
): F[FtsQuery] =
|
): F[FtsQuery] =
|
||||||
store
|
store
|
||||||
.transact(QFolder.getMemberFolders(account))
|
.transact(QFolder.getMemberFolders(account))
|
||||||
.map(folders =>
|
.map(folders =>
|
||||||
FtsQuery(ftq, account.collective, batch.limit, 0)
|
FtsQuery(ftq, account.collective, 500, 0)
|
||||||
.withFolders(folders)
|
.withFolders(folders)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,6 +20,11 @@ sealed trait QueryParseResult {
|
|||||||
object QueryParseResult {
|
object QueryParseResult {
|
||||||
|
|
||||||
final case class Success(q: Query, ftq: Option[String]) extends QueryParseResult {
|
final case class Success(q: Query, ftq: Option[String]) extends QueryParseResult {
|
||||||
|
|
||||||
|
/** Drop the fulltext search query if disabled. */
|
||||||
|
def withFtsEnabled(enabled: Boolean) =
|
||||||
|
if (enabled || ftq.isEmpty) this else copy(ftq = None)
|
||||||
|
|
||||||
val get = Some(q -> ftq)
|
val get = Some(q -> ftq)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,7 @@ object ItemRoutes {
|
|||||||
|
|
||||||
case req @ POST -> Root / "search" =>
|
case req @ POST -> Root / "search" =>
|
||||||
for {
|
for {
|
||||||
|
timed <- Duration.stopTime[F]
|
||||||
userQuery <- req.as[ItemQuery]
|
userQuery <- req.as[ItemQuery]
|
||||||
batch = Batch(
|
batch = Batch(
|
||||||
userQuery.offset.getOrElse(0),
|
userQuery.offset.getOrElse(0),
|
||||||
@ -92,6 +93,8 @@ object ItemRoutes {
|
|||||||
)
|
)
|
||||||
fixQuery = Query.Fix(user.account, None, None)
|
fixQuery = Query.Fix(user.account, None, None)
|
||||||
resp <- searchItems(backend, dsl)(settings, fixQuery, itemQuery, limitCapped)
|
resp <- searchItems(backend, dsl)(settings, fixQuery, itemQuery, limitCapped)
|
||||||
|
dur <- timed
|
||||||
|
_ <- logger.debug(s"Search request: ${dur.formatExact}")
|
||||||
} yield resp
|
} yield resp
|
||||||
|
|
||||||
case req @ POST -> Root / "searchStats" =>
|
case req @ POST -> Root / "searchStats" =>
|
||||||
|
@ -14,8 +14,8 @@ import cats.syntax.all._
|
|||||||
import docspell.backend.BackendApp
|
import docspell.backend.BackendApp
|
||||||
import docspell.backend.auth.AuthToken
|
import docspell.backend.auth.AuthToken
|
||||||
import docspell.backend.ops.search.QueryParseResult
|
import docspell.backend.ops.search.QueryParseResult
|
||||||
import docspell.common.{SearchMode, Timestamp}
|
import docspell.common.{Duration, SearchMode, Timestamp}
|
||||||
import docspell.query.FulltextExtract
|
import docspell.query.FulltextExtract.Result
|
||||||
import docspell.restapi.model._
|
import docspell.restapi.model._
|
||||||
import docspell.restserver.Config
|
import docspell.restserver.Config
|
||||||
import docspell.restserver.conv.Conversions
|
import docspell.restserver.conv.Conversions
|
||||||
@ -44,31 +44,36 @@ final class ItemSearchPart[F[_]: Async](
|
|||||||
) :? QP.WithDetails(detailFlag) :? QP.SearchKind(searchMode) =>
|
) :? QP.WithDetails(detailFlag) :? QP.SearchKind(searchMode) =>
|
||||||
val userQuery =
|
val userQuery =
|
||||||
ItemQuery(offset, limit, detailFlag, searchMode, q.getOrElse(""))
|
ItemQuery(offset, limit, detailFlag, searchMode, q.getOrElse(""))
|
||||||
|
for {
|
||||||
Timestamp
|
today <- Timestamp.current[F].map(_.toUtcDate)
|
||||||
.current[F]
|
resp <- search(userQuery, today)
|
||||||
.map(_.toUtcDate)
|
} yield resp
|
||||||
.flatMap(search(userQuery, _))
|
|
||||||
|
|
||||||
case req @ POST -> Root / "search" =>
|
case req @ POST -> Root / "search" =>
|
||||||
for {
|
for {
|
||||||
|
timed <- Duration.stopTime[F]
|
||||||
userQuery <- req.as[ItemQuery]
|
userQuery <- req.as[ItemQuery]
|
||||||
today <- Timestamp.current[F]
|
today <- Timestamp.current[F].map(_.toUtcDate)
|
||||||
resp <- search(userQuery, today.toUtcDate)
|
resp <- search(userQuery, today)
|
||||||
|
dur <- timed
|
||||||
|
_ <- logger.debug(s"Search request: ${dur.formatExact}")
|
||||||
} yield resp
|
} yield resp
|
||||||
|
|
||||||
case GET -> Root / "searchStats" :? QP.Query(q) :? QP.SearchKind(searchMode) =>
|
case GET -> Root / "searchStats" :? QP.Query(q) :? QP.SearchKind(searchMode) =>
|
||||||
val userQuery = ItemQuery(None, None, None, searchMode, q.getOrElse(""))
|
val userQuery = ItemQuery(None, None, None, searchMode, q.getOrElse(""))
|
||||||
Timestamp
|
for {
|
||||||
.current[F]
|
today <- Timestamp.current[F].map(_.toUtcDate)
|
||||||
.map(_.toUtcDate)
|
resp <- searchStats(userQuery, today)
|
||||||
.flatMap(searchStats(userQuery, _))
|
} yield resp
|
||||||
|
|
||||||
case req @ POST -> Root / "searchStats" =>
|
case req @ POST -> Root / "searchStats" =>
|
||||||
for {
|
for {
|
||||||
|
timed <- Duration.stopTime[F]
|
||||||
userQuery <- req.as[ItemQuery]
|
userQuery <- req.as[ItemQuery]
|
||||||
today <- Timestamp.current[F].map(_.toUtcDate)
|
today <- Timestamp.current[F].map(_.toUtcDate)
|
||||||
resp <- searchStats(userQuery, today)
|
resp <- searchStats(userQuery, today)
|
||||||
|
dur <- timed
|
||||||
|
_ <- logger.debug(s"Search stats request: ${dur.formatExact}")
|
||||||
} yield resp
|
} yield resp
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +110,7 @@ final class ItemSearchPart[F[_]: Async](
|
|||||||
)
|
)
|
||||||
|
|
||||||
// order is always by date unless q is empty and ftq is not
|
// order is always by date unless q is empty and ftq is not
|
||||||
// TODO this is not obvious from the types and an impl detail.
|
// TODO this should be given explicitly by the result
|
||||||
ftsOrder = res.q.cond.isEmpty && res.ftq.isDefined
|
ftsOrder = res.q.cond.isEmpty && res.ftq.isDefined
|
||||||
|
|
||||||
resp <- Ok(convert(items, batch, limitCapped, ftsOrder))
|
resp <- Ok(convert(items, batch, limitCapped, ftsOrder))
|
||||||
@ -119,20 +124,18 @@ final class ItemSearchPart[F[_]: Async](
|
|||||||
): Either[F[Response[F]], QueryParseResult.Success] =
|
): Either[F[Response[F]], QueryParseResult.Success] =
|
||||||
backend.search.parseQueryString(authToken.account, mode, userQuery.query) match {
|
backend.search.parseQueryString(authToken.account, mode, userQuery.query) match {
|
||||||
case s: QueryParseResult.Success =>
|
case s: QueryParseResult.Success =>
|
||||||
Right(s)
|
Right(s.withFtsEnabled(cfg.fullTextSearch.enabled))
|
||||||
|
|
||||||
case QueryParseResult.ParseFailed(err) =>
|
case QueryParseResult.ParseFailed(err) =>
|
||||||
Left(BadRequest(BasicResult(false, s"Invalid query: $err")))
|
Left(BadRequest(BasicResult(false, s"Invalid query: $err")))
|
||||||
|
|
||||||
case QueryParseResult.FulltextMismatch(FulltextExtract.Result.TooMany) =>
|
case QueryParseResult.FulltextMismatch(Result.TooMany) =>
|
||||||
Left(
|
Left(
|
||||||
BadRequest(
|
BadRequest(
|
||||||
BasicResult(false, "Only one fulltext search expression is allowed.")
|
BasicResult(false, "Only one fulltext search expression is allowed.")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case QueryParseResult.FulltextMismatch(
|
case QueryParseResult.FulltextMismatch(Result.UnsupportedPosition) =>
|
||||||
FulltextExtract.Result.UnsupportedPosition
|
|
||||||
) =>
|
|
||||||
Left(
|
Left(
|
||||||
BadRequest(
|
BadRequest(
|
||||||
BasicResult(
|
BasicResult(
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
package docspell.store.fts
|
package docspell.store.fts
|
||||||
|
|
||||||
import cats.Foldable
|
|
||||||
import cats.syntax.all._
|
import cats.syntax.all._
|
||||||
|
import cats.{Foldable, Monad}
|
||||||
import fs2.{Pipe, Stream}
|
import fs2.{Pipe, Stream}
|
||||||
|
|
||||||
import docspell.common.Duration
|
import docspell.common.Duration
|
||||||
@ -38,10 +38,10 @@ private[fts] object TempFtsOps {
|
|||||||
timed <- Stream.eval(Duration.stopTime[ConnectionIO])
|
timed <- Stream.eval(Duration.stopTime[ConnectionIO])
|
||||||
tt <- Stream.eval(createTable(db, name))
|
tt <- Stream.eval(createTable(db, name))
|
||||||
n <- in.through(tt.insert).foldMonoid
|
n <- in.through(tt.insert).foldMonoid
|
||||||
_ <- Stream.eval(tt.createIndex)
|
_ <- if (n > 500) Stream.eval(tt.createIndex) else Stream(())
|
||||||
duration <- Stream.eval(timed)
|
duration <- Stream.eval(timed)
|
||||||
_ <- Stream.eval(
|
_ <- Stream.eval(
|
||||||
logger.info(
|
logger.debug(
|
||||||
s"Creating temporary fts table ($n elements) took: ${duration.formatExact}"
|
s"Creating temporary fts table ($n elements) took: ${duration.formatExact}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -122,25 +122,30 @@ private[fts] object TempFtsOps {
|
|||||||
"(?,?,?)" :: res
|
"(?,?,?)" :: res
|
||||||
}
|
}
|
||||||
.mkString(",")
|
.mkString(",")
|
||||||
val sql =
|
if (values.isEmpty) Monad[ConnectionIO].pure(0)
|
||||||
s"""INSERT INTO ${table.tableName}
|
else {
|
||||||
| (${table.id.name}, ${table.score.name}, ${table.context.name})
|
val sql =
|
||||||
| VALUES $values""".stripMargin
|
s"""INSERT INTO ${table.tableName}
|
||||||
|
| (${table.id.name}, ${table.score.name}, ${table.context.name})
|
||||||
|
| VALUES $values""".stripMargin
|
||||||
|
|
||||||
val encoder = io.circe.Encoder[ContextEntry]
|
val encoder = io.circe.Encoder[ContextEntry]
|
||||||
doobie.free.FC.raw { conn =>
|
doobie.free.FC.raw { conn =>
|
||||||
val pst = conn.prepareStatement(sql)
|
val pst = conn.prepareStatement(sql)
|
||||||
rows.foldl(0) { (index, row) =>
|
rows.foldl(0) { (index, row) =>
|
||||||
pst.setString(index + 1, row.id.id)
|
pst.setString(index + 1, row.id.id)
|
||||||
row.score
|
row.score
|
||||||
.map(d => pst.setDouble(index + 2, d))
|
.fold(pst.setNull(index + 2, java.sql.Types.DOUBLE))(d =>
|
||||||
.getOrElse(pst.setNull(index + 2, java.sql.Types.DOUBLE))
|
pst.setDouble(index + 2, d)
|
||||||
row.context
|
)
|
||||||
.map(c => pst.setString(index + 3, encoder(c).noSpaces))
|
row.context
|
||||||
.getOrElse(pst.setNull(index + 3, java.sql.Types.VARCHAR))
|
.fold(pst.setNull(index + 3, java.sql.Types.VARCHAR))(c =>
|
||||||
index + 3
|
pst.setString(index + 3, encoder(c).noSpaces)
|
||||||
|
)
|
||||||
|
index + 3
|
||||||
|
}
|
||||||
|
pst.executeUpdate()
|
||||||
}
|
}
|
||||||
pst.executeUpdate()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user