Send no fts query if it is disabled

This commit is contained in:
eikek
2022-06-02 23:10:51 +02:00
parent 66aab0c952
commit b50f57f7fe
5 changed files with 58 additions and 43 deletions

View File

@ -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)
) )

View File

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

View File

@ -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" =>

View File

@ -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(

View File

@ -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,6 +122,8 @@ private[fts] object TempFtsOps {
"(?,?,?)" :: res "(?,?,?)" :: res
} }
.mkString(",") .mkString(",")
if (values.isEmpty) Monad[ConnectionIO].pure(0)
else {
val sql = val sql =
s"""INSERT INTO ${table.tableName} s"""INSERT INTO ${table.tableName}
| (${table.id.name}, ${table.score.name}, ${table.context.name}) | (${table.id.name}, ${table.score.name}, ${table.context.name})
@ -133,16 +135,19 @@ private[fts] object TempFtsOps {
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 row.context
.map(c => pst.setString(index + 3, encoder(c).noSpaces)) .fold(pst.setNull(index + 3, java.sql.Types.VARCHAR))(c =>
.getOrElse(pst.setNull(index + 3, java.sql.Types.VARCHAR)) pst.setString(index + 3, encoder(c).noSpaces)
)
index + 3 index + 3
} }
pst.executeUpdate() pst.executeUpdate()
} }
} }
}
def distinctCtePg(table: Table, name: String): CteBind = def distinctCtePg(table: Table, name: String): CteBind =
CteBind( CteBind(