Refactor full-text migrations and add folder to solr schema

This commit is contained in:
Eike Kettner 2020-07-12 00:30:37 +02:00
parent e387b5513f
commit aeba4ba913
9 changed files with 102 additions and 30 deletions
modules
fts-client/src/main/scala/docspell/ftsclient
fts-solr/src/main/scala/docspell/ftssolr
joex/src/main/scala/docspell/joex/fts

@ -17,11 +17,12 @@ import org.log4s.getLogger
*/ */
trait FtsClient[F[_]] { trait FtsClient[F[_]] {
/** Initialization tasks. This is called exactly once and then never /** Initialization tasks. This is called exactly once at the very
* beginning when initializing the full-text index and then never
* again (except when re-indexing everything). It may be used to * again (except when re-indexing everything). It may be used to
* setup the database. * setup the database.
*/ */
def initialize: F[Unit] def initialize: List[FtsMigration[F]]
/** Run a full-text search. */ /** Run a full-text search. */
def search(q: FtsQuery): F[FtsResult] def search(q: FtsQuery): F[FtsResult]
@ -107,8 +108,8 @@ object FtsClient {
new FtsClient[F] { new FtsClient[F] {
private[this] val logger = Logger.log4s[F](getLogger) private[this] val logger = Logger.log4s[F](getLogger)
def initialize: F[Unit] = def initialize: List[FtsMigration[F]] =
logger.info("Full-text search is disabled!") Nil
def search(q: FtsQuery): F[FtsResult] = def search(q: FtsQuery): F[FtsResult] =
logger.warn("Full-text search is disabled!") *> FtsResult.empty.pure[F] logger.warn("Full-text search is disabled!") *> FtsResult.empty.pure[F]

@ -0,0 +1,24 @@
package docspell.ftsclient
import docspell.common._
final case class FtsMigration[F[_]](
version: Int,
engine: Ident,
description: String,
task: F[FtsMigration.Result]
)
object FtsMigration {
sealed trait Result
object Result {
case object WorkDone extends Result
case object ReIndexAll extends Result
case object IndexAll extends Result
def workDone: Result = WorkDone
def reIndexAll: Result = ReIndexAll
def indexAll: Result = IndexAll
}
}

@ -25,6 +25,7 @@ object Field {
val content_en = Field("content_en") val content_en = Field("content_en")
val itemName = Field("itemName") val itemName = Field("itemName")
val itemNotes = Field("itemNotes") val itemNotes = Field("itemNotes")
val folderId = Field("folder")
def contentField(lang: Language): Field = def contentField(lang: Language): Field =
lang match { lang match {

@ -17,7 +17,7 @@ final class SolrFtsClient[F[_]: Effect](
solrQuery: SolrQuery[F] solrQuery: SolrQuery[F]
) extends FtsClient[F] { ) extends FtsClient[F] {
def initialize: F[Unit] = def initialize: List[FtsMigration[F]] =
solrSetup.setupSchema solrSetup.setupSchema
def search(q: FtsQuery): F[FtsResult] = def search(q: FtsQuery): F[FtsResult] =

@ -4,6 +4,7 @@ import cats.effect._
import cats.implicits._ import cats.implicits._
import docspell.common._ import docspell.common._
import docspell.ftsclient.FtsMigration
import _root_.io.circe._ import _root_.io.circe._
import _root_.io.circe.generic.semiauto._ import _root_.io.circe.generic.semiauto._
@ -15,21 +16,48 @@ import org.http4s.client.dsl.Http4sClientDsl
trait SolrSetup[F[_]] { trait SolrSetup[F[_]] {
def setupSchema: F[Unit] def setupSchema: List[FtsMigration[F]]
} }
object SolrSetup { object SolrSetup {
private val solrEngine = Ident.unsafe("solr")
def apply[F[_]: ConcurrentEffect](cfg: SolrConfig, client: Client[F]): SolrSetup[F] = { def apply[F[_]: ConcurrentEffect](cfg: SolrConfig, client: Client[F]): SolrSetup[F] = {
val dsl = new Http4sClientDsl[F] {} val dsl = new Http4sClientDsl[F] {}
import dsl._ import dsl._
new SolrSetup[F] { new SolrSetup[F] {
val url = (Uri.unsafeFromString(cfg.url.asString) / "schema") val url = (Uri.unsafeFromString(cfg.url.asString) / "schema")
.withQueryParam("commitWithin", cfg.commitWithin.toString) .withQueryParam("commitWithin", cfg.commitWithin.toString)
def setupSchema: F[Unit] = { def setupSchema: List[FtsMigration[F]] =
List(
FtsMigration[F](
1,
solrEngine,
"Initialize",
setupCoreSchema.map(_ => FtsMigration.Result.workDone)
),
FtsMigration[F](
3,
solrEngine,
"Add folder field",
addFolderField.map(_ => FtsMigration.Result.workDone)
),
FtsMigration[F](
4,
solrEngine,
"Index all from database",
FtsMigration.Result.indexAll.pure[F]
)
)
def addFolderField: F[Unit] =
addStringField(Field.folderId)
def setupCoreSchema: F[Unit] = {
val cmds0 = val cmds0 =
List( List(
Field.id, Field.id,

@ -1,9 +1,8 @@
package docspell.joex.fts package docspell.joex.fts
import cats.data.{Kleisli, NonEmptyList} import cats.data.{Kleisli, NonEmptyList}
import cats.effect._
import cats.implicits._ import cats.implicits._
import cats.{ApplicativeError, FlatMap, Semigroup} import cats.{Applicative, ApplicativeError, FlatMap, Monad, Semigroup}
import docspell.common._ import docspell.common._
import docspell.ftsclient._ import docspell.ftsclient._
@ -15,6 +14,19 @@ object FtsWork {
def apply[F[_]](f: FtsContext[F] => F[Unit]): FtsWork[F] = def apply[F[_]](f: FtsContext[F] => F[Unit]): FtsWork[F] =
Kleisli(f) Kleisli(f)
def allInitializeTasks[F[_]: Monad]: FtsWork[F] =
FtsWork[F](_ => ().pure[F]).tap[FtsContext[F]].flatMap { ctx =>
NonEmptyList.fromList(ctx.fts.initialize.map(fm => from[F](fm.task))) match {
case Some(nel) =>
nel.reduce(semigroup[F])
case None =>
FtsWork[F](_ => ().pure[F])
}
}
def from[F[_]: FlatMap: Applicative](t: F[FtsMigration.Result]): FtsWork[F] =
Kleisli.liftF(t).flatMap(transformResult[F])
def all[F[_]: FlatMap]( def all[F[_]: FlatMap](
m0: FtsWork[F], m0: FtsWork[F],
mn: FtsWork[F]* mn: FtsWork[F]*
@ -24,14 +36,25 @@ object FtsWork {
implicit def semigroup[F[_]: FlatMap]: Semigroup[FtsWork[F]] = implicit def semigroup[F[_]: FlatMap]: Semigroup[FtsWork[F]] =
Semigroup.instance((mt1, mt2) => mt1.flatMap(_ => mt2)) Semigroup.instance((mt1, mt2) => mt1.flatMap(_ => mt2))
private def transformResult[F[_]: Applicative: FlatMap](
r: FtsMigration.Result
): FtsWork[F] =
r match {
case FtsMigration.Result.WorkDone =>
Kleisli.pure(())
case FtsMigration.Result.IndexAll =>
insertAll[F](None)
case FtsMigration.Result.ReIndexAll =>
clearIndex[F](None) >> insertAll[F](None)
}
// some tasks // some tasks
def log[F[_]](f: Logger[F] => F[Unit]): FtsWork[F] = def log[F[_]](f: Logger[F] => F[Unit]): FtsWork[F] =
FtsWork(ctx => f(ctx.logger)) FtsWork(ctx => f(ctx.logger))
def initialize[F[_]]: FtsWork[F] =
FtsWork(_.fts.initialize)
def clearIndex[F[_]](coll: Option[Ident]): FtsWork[F] = def clearIndex[F[_]](coll: Option[Ident]): FtsWork[F] =
coll match { coll match {
case Some(cid) => case Some(cid) =>
@ -40,7 +63,7 @@ object FtsWork {
FtsWork(ctx => ctx.fts.clearAll(ctx.logger)) FtsWork(ctx => ctx.fts.clearAll(ctx.logger))
} }
def insertAll[F[_]: Effect](coll: Option[Ident]): FtsWork[F] = def insertAll[F[_]: FlatMap](coll: Option[Ident]): FtsWork[F] =
FtsWork FtsWork
.all( .all(
FtsWork(ctx => FtsWork(ctx =>

@ -1,6 +1,6 @@
package docspell.joex.fts package docspell.joex.fts
import cats.Traverse import cats.{Applicative, FlatMap, Traverse}
import cats.data.{Kleisli, OptionT} import cats.data.{Kleisli, OptionT}
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
@ -20,6 +20,9 @@ case class Migration[F[_]](
object Migration { object Migration {
def from[F[_]: Applicative: FlatMap](fm: FtsMigration[F]): Migration[F] =
Migration(fm.version, fm.engine, fm.description, FtsWork.from(fm.task))
def apply[F[_]: Effect]( def apply[F[_]: Effect](
cfg: Config.FullTextSearch, cfg: Config.FullTextSearch,
fts: FtsClient[F], fts: FtsClient[F],

@ -21,7 +21,7 @@ object MigrationTask {
.flatMap(_ => .flatMap(_ =>
Task(ctx => Task(ctx =>
Migration[F](cfg, fts, ctx.store, ctx.logger) Migration[F](cfg, fts, ctx.store, ctx.logger)
.run(migrationTasks[F]) .run(migrationTasks[F](fts))
) )
) )
@ -44,11 +44,7 @@ object MigrationTask {
Some(DocspellSystem.migrationTaskTracker) Some(DocspellSystem.migrationTaskTracker)
) )
private val solrEngine = Ident.unsafe("solr") def migrationTasks[F[_]: Effect](fts: FtsClient[F]): List[Migration[F]] =
def migrationTasks[F[_]: Effect]: List[Migration[F]] = fts.initialize.map(fm => Migration.from(fm))
List(
Migration[F](1, solrEngine, "initialize", FtsWork.initialize[F]),
Migration[F](2, solrEngine, "Index all from database", FtsWork.insertAll[F](None))
)
} }

@ -21,13 +21,7 @@ object ReIndexTask {
Task Task
.log[F, Args](_.info(s"Running full-text re-index now")) .log[F, Args](_.info(s"Running full-text re-index now"))
.flatMap(_ => .flatMap(_ =>
Task(ctx => Task(ctx => clearData[F](ctx.args.collective).forContext(cfg, fts).run(ctx))
(clearData[F](ctx.args.collective) ++
FtsWork.log[F](_.info("Inserting data from database")) ++
FtsWork.insertAll[F](
ctx.args.collective
)).forContext(cfg, fts).run(ctx)
)
) )
def onCancel[F[_]: Sync]: Task[F, Args, Unit] = def onCancel[F[_]: Sync]: Task[F, Args, Unit] =
@ -41,7 +35,9 @@ object ReIndexTask {
.clearIndex(collective) .clearIndex(collective)
.recoverWith( .recoverWith(
FtsWork.log[F](_.info("Clearing data failed. Continue re-indexing.")) FtsWork.log[F](_.info("Clearing data failed. Continue re-indexing."))
) ) ++
FtsWork.log[F](_.info("Inserting data from database")) ++
FtsWork.insertAll[F](collective)
case None => case None =>
FtsWork FtsWork
@ -50,6 +46,6 @@ object ReIndexTask {
FtsWork.log[F](_.info("Clearing data failed. Continue re-indexing.")) FtsWork.log[F](_.info("Clearing data failed. Continue re-indexing."))
) ++ ) ++
FtsWork.log[F](_.info("Running index initialize")) ++ FtsWork.log[F](_.info("Running index initialize")) ++
FtsWork.initialize[F] FtsWork.allInitializeTasks[F]
}) })
} }