(* Applies the versioned schema migrations. The migrations themselves live in {!Migration_data}: the SQL and the ledger table. This module holds only the logic that runs them. That logic is the ledger check that makes applying idempotent, and the per-migration transaction. *) type migration = Migration_data.t = { version : int; name : string; statements : string list; } let migrations = Migration_data.migrations let ledger = Migration_data.ledger let statements = List.concat_map (fun migration -> migration.statements) migrations (* --- request helpers --- *) let exec_sql (module Db : Caqti_lwt.CONNECTION) sql = let request = let open Caqti_request.Infix in let open Caqti_type.Std in (unit ->. unit) ~oneshot:true sql in Db.exec request () let is_applied (module Db : Caqti_lwt.CONNECTION) version = let request = let open Caqti_request.Infix in let open Caqti_type.Std in (int ->! int) ~oneshot:true "SELECT COUNT(*) FROM schema_migrations WHERE version = ?" in Db.find request version let record_applied (module Db : Caqti_lwt.CONNECTION) migration = let request = let open Caqti_request.Infix in let open Caqti_type.Std in (t2 int string ->. unit) ~oneshot:true "INSERT INTO schema_migrations (version, name, applied_at) VALUES (?, ?, \ strftime('%s','now'))" in Db.exec request (migration.version, migration.name) (* Apply one migration inside a transaction, then record it. The ledger check makes this idempotent. The [IF NOT EXISTS] clauses keep an already-populated file from a pre-ledger build safe. *) let apply_migration ((module Db : Caqti_lwt.CONNECTION) as db) migration = let open Lwt_result.Syntax in let* applied = is_applied db migration.version in if applied > 0 then Lwt_result.return () else Db.with_transaction (fun () -> let rec go = function | [] -> record_applied db migration | sql :: tl -> let* () = exec_sql db sql in go tl in go migration.statements) let apply (module Db : Caqti_lwt.CONNECTION) = let db = (module Db : Caqti_lwt.CONNECTION) in let open Lwt_result.Syntax in let run = let* () = exec_sql db ledger in let rec go = function | [] -> Lwt_result.return () | migration :: tl -> let* () = apply_migration db migration in go tl in go migrations in Lwt.map (function Ok () -> Ok () | Error e -> Error (e :> Caqti_error.t)) run