diff --git a/modules/fts-client/src/main/scala/docspell/ftsclient/FtsResult.scala b/modules/fts-client/src/main/scala/docspell/ftsclient/FtsResult.scala index 8af09caf..516711a9 100644 --- a/modules/fts-client/src/main/scala/docspell/ftsclient/FtsResult.scala +++ b/modules/fts-client/src/main/scala/docspell/ftsclient/FtsResult.scala @@ -29,7 +29,7 @@ object FtsResult { case class ItemMatch( id: Ident, itemId: Ident, - collectiveId: Ident, + collectiveId: CollectiveId, score: Double, data: MatchData ) diff --git a/modules/fts-psql/src/main/scala/docspell/ftspsql/FtsRepository.scala b/modules/fts-psql/src/main/scala/docspell/ftspsql/FtsRepository.scala index 55d51c19..b8729760 100644 --- a/modules/fts-psql/src/main/scala/docspell/ftspsql/FtsRepository.scala +++ b/modules/fts-psql/src/main/scala/docspell/ftspsql/FtsRepository.scala @@ -16,6 +16,7 @@ import doobie._ import doobie.implicits._ object FtsRepository extends DoobieMeta { + private[this] val logger = docspell.logging.getLogger[ConnectionIO] val table = fr"ftspsql_search" def containsData: ConnectionIO[Boolean] = @@ -62,15 +63,17 @@ object FtsRepository extends DoobieMeta { val query = mkQueryPart(pq, q) - sql"""select $select - |from $table, $query - |where ${mkCondition(q)} AND query @@ text_index - |order by rank desc - |limit ${q.limit} - |offset ${q.offset} - |""".stripMargin - .query[SearchResult] - .to[Vector] + val sqlFrag = + sql"""select $select + |from $table, $query + |where ${mkCondition(q)} AND query @@ text_index + |order by rank desc + |limit ${q.limit} + |offset ${q.offset} + |""".stripMargin + + logger.asUnsafe.trace(s"PSQL Fulltext query: $sqlFrag") + sqlFrag.query[SearchResult].to[Vector] } private def mkCondition(q: FtsQuery): Fragment = { @@ -84,7 +87,7 @@ object FtsRepository extends DoobieMeta { val folders = NonEmptyList.fromList(q.folders.toList).map { nel => val ids = nel.map(id => fr"$id").reduceLeft(_ ++ fr"," ++ _) - fr"folder_id in ($ids)" + fr"(folder_id in ($ids) or folder_id is null)" } List(items, folders).flatten.foldLeft(coll)(_ ++ fr"AND" ++ _) diff --git a/modules/fts-psql/src/main/scala/docspell/ftspsql/PsqlFtsClient.scala b/modules/fts-psql/src/main/scala/docspell/ftspsql/PsqlFtsClient.scala index f0dc64ea..fafc92ad 100644 --- a/modules/fts-psql/src/main/scala/docspell/ftspsql/PsqlFtsClient.scala +++ b/modules/fts-psql/src/main/scala/docspell/ftspsql/PsqlFtsClient.scala @@ -26,6 +26,8 @@ final class PsqlFtsClient[F[_]: Sync](cfg: PsqlConfig, xa: Transactor[F]) val engine = Ident.unsafe("postgres") val config = cfg + private[this] val logger = docspell.logging.getLogger[F] + private[ftspsql] val transactor = xa private[this] val searchSummary = @@ -83,6 +85,7 @@ final class PsqlFtsClient[F[_]: Sync](cfg: PsqlConfig, xa: Transactor[F]) summary <- searchSummary(q).transact(xa) results <- search(q, true).transact(xa) endNanos <- Sync[F].delay(System.nanoTime()) + _ <- logger.debug(s"PSQL fulltext search hits: ${results.size}") duration = Duration.nanos(endNanos - startNanos) res = SearchResult .toFtsResult(summary, results) diff --git a/modules/fts-psql/src/main/scala/docspell/ftspsql/SearchResult.scala b/modules/fts-psql/src/main/scala/docspell/ftspsql/SearchResult.scala index faf37fe7..04073102 100644 --- a/modules/fts-psql/src/main/scala/docspell/ftspsql/SearchResult.scala +++ b/modules/fts-psql/src/main/scala/docspell/ftspsql/SearchResult.scala @@ -13,7 +13,7 @@ import docspell.ftsclient.FtsResult.{ItemMatch, MatchData} final case class SearchResult( id: Ident, itemId: Ident, - collective: Ident, + collective: CollectiveId, language: Language, attachId: Option[Ident], folderId: Option[Ident], diff --git a/modules/fts-solr/src/main/scala/docspell/ftssolr/JsonCodec.scala b/modules/fts-solr/src/main/scala/docspell/ftssolr/JsonCodec.scala index e4684fe8..fae84a88 100644 --- a/modules/fts-solr/src/main/scala/docspell/ftssolr/JsonCodec.scala +++ b/modules/fts-solr/src/main/scala/docspell/ftssolr/JsonCodec.scala @@ -125,7 +125,7 @@ trait JsonCodec { for { itemId <- c.get[Ident](Field.itemId.name) id <- c.get[Ident](Field.id.name) - coll <- c.get[Ident](Field.collectiveId.name) + coll <- c.get[CollectiveId](Field.collectiveId.name) score <- c.get[Double]("score") md <- decodeMatchData(c) } yield FtsResult.ItemMatch(id, itemId, coll, score, md) diff --git a/modules/store/src/test/scala/docspell/store/fts/TempFtsOpsTest.scala b/modules/store/src/test/scala/docspell/store/fts/TempFtsOpsTest.scala index e4876c74..03b37fe8 100644 --- a/modules/store/src/test/scala/docspell/store/fts/TempFtsOpsTest.scala +++ b/modules/store/src/test/scala/docspell/store/fts/TempFtsOpsTest.scala @@ -130,14 +130,14 @@ class TempFtsOpsTest extends DatabaseTest { ItemMatch( id(s"m$n"), id(s"item-$n"), - DocspellSystem.user, + CollectiveId(1), math.random(), FtsResult.ItemData ), ItemMatch( id(s"m$n-1"), id(s"item-$n"), - DocspellSystem.user, + CollectiveId(1), math.random(), AttachmentData(id(s"item-$n-attach-1"), "attachment.pdf") )