mirror of
				https://github.com/TheAnachronism/docspell.git
				synced 2025-10-31 09:30:12 +00:00 
			
		
		
		
	Refactor full-text migrations and add folder to solr schema
This commit is contained in:
		| @@ -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] | ||||||
|       }) |       }) | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user