package migra
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
A database migration tool and library for OCaml supporting PostgreSQL, MariaDB/MySQL, and SQLite
Install
dune-project
Dependency
Authors
Maintainers
Sources
migra-1.0.1.tbz
sha256=daaaab416e580e5b8dc9f0a74482e142cef6d77b248d995d320f6f882d3a9e35
sha512=8f60494b9ee6ac2e7e69a16eefce69d1e8c22e831fe28f600a42200e3086e2f4d15331323ad1848f7165e1c8ff30fe320445185b7e988325588293a66937966a
doc/src/migra.engine/discovery.ml.html
Source file discovery.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126(** Migration file discovery and filtering. *) module Int64Set = Set.Make (Int64) let default_migrations_dir = "migrations" let applied_set_of_list (versions : int64 list) : Int64Set.t = List.fold_left (fun set version -> Int64Set.add version set) Int64Set.empty versions let has_sql_extension (filename : string) : bool = String.ends_with ~suffix:".sql" (String.lowercase_ascii filename) (** A well-formed migration filename: exactly what {!Migration.from_file} accepts. Defined in terms of the parser so the two can never disagree. *) let is_migration_file (filename : string) : bool = Result.is_ok (Migration.from_file filename) (** A [.sql] file whose name starts with a digit clearly intends to be a timestamped migration; if it then fails to parse it is an error, not something to silently skip. Files that don't look like migration attempts (README.md, helpers.sql, ...) are ignored. *) let looks_like_migration (filename : string) : bool = has_sql_extension filename && String.length filename > 0 && let c = filename.[0] in c >= '0' && c <= '9' let read_directory (dir_path : string) : (string list, Types.error) result = try if not (Sys.file_exists dir_path) then Error (Types.DiscoveryError (Printf.sprintf "Migrations directory does not exist: %s" dir_path)) else if not (Sys.is_directory dir_path) then Error (Types.DiscoveryError (Printf.sprintf "Path is not a directory: %s" dir_path)) else let files = Sys.readdir dir_path |> Array.to_list in Ok files with e -> Error (Types.DiscoveryError (Printf.sprintf "Error reading directory %s: %s" dir_path (Printexc.to_string e))) (** Find the first pair of migrations sharing a version. Assumes the list is sorted by version, so duplicates are adjacent. *) let rec first_duplicate_version = function | (a : Migration.t) :: (b :: _ as rest) -> if Int64.equal a.Migration.version b.Migration.version then Some (a, b) else first_duplicate_version rest | _ -> None let find_migrations ?(dir = default_migrations_dir) () : (Migration.t list, Types.error) result = match read_directory dir with | Error e -> Error e | Ok files -> ( (* Parse every .sql file: a malformed file whose name looks like a migration (see looks_like_migration) is an error; others are ignored. *) let rec parse_all acc = function | [] -> Ok (List.rev acc) | filename :: rest -> ( if not (has_sql_extension filename) then parse_all acc rest else match Migration.from_file (Filename.concat dir filename) with | Ok migration -> parse_all (migration :: acc) rest | Error err -> if looks_like_migration filename then Error err else parse_all acc rest) in match parse_all [] files with | Error e -> Error e | Ok migrations -> ( let sorted = List.sort Migration.compare migrations in (* Two files with the same version would corrupt apply/pending tracking (applying one marks the version, silently hiding the other), so reject the ambiguity up front. *) match first_duplicate_version sorted with | Some (a, b) -> Error (Types.MigrationError (Types.VersionConflict (a.Migration.version, a.file_path, b.file_path))) | None -> Ok sorted)) (** Find pending migrations (not yet applied) Takes a list of applied versions and all discovered migrations, returns migrations that haven't been applied yet. *) let find_pending (applied_versions : int64 list) (all_migrations : Migration.t list) : Migration.t list = let applied_set = applied_set_of_list applied_versions in List.filter (fun (migration : Migration.t) -> not (Int64Set.mem migration.Migration.version applied_set)) all_migrations let find_by_version (migrations : Migration.t list) (version : int64) : Migration.t option = List.find_opt (fun (m : Migration.t) -> Int64.equal m.Migration.version version) migrations let ensure_migrations_dir ?(dir = default_migrations_dir) () : (unit, Types.error) result = try if not (Sys.file_exists dir) then begin Unix.mkdir dir 0o755; Ok () end else if Sys.is_directory dir then Ok () else Error (Types.DiscoveryError (Printf.sprintf "Path exists but is not a directory: %s" dir)) with e -> Error (Types.DiscoveryError (Printf.sprintf "Error creating migrations directory %s: %s" dir (Printexc.to_string e)))
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>