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/dialect.ml.html
Source file dialect.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 127 128 129 130 131 132 133 134 135 136 137 138type t = PostgreSQL | MariaDB | SQLite module type DIALECT = sig val name : string val default_port : int option val admin_database : string option val database_exists_sql : string val create_database_sql : string -> string val drop_database_sql : string -> string val timestamp_to_string : string -> string val supports_database_lifecycle : bool end (** Quote [name] as a delimited SQL identifier using [q] as the delimiter, escaping any embedded delimiter by doubling it. This turns a name like [my-db] or one containing the delimiter into a single, injection-safe identifier token. Database names are interpolated into DDL (they cannot be bound as parameters), so this is what keeps them safe. *) let quote_identifier ~(q : char) (name : string) : string = let buf = Buffer.create (String.length name + 2) in Buffer.add_char buf q; String.iter (fun c -> if c = q then Buffer.add_char buf q; Buffer.add_char buf c) name; Buffer.add_char buf q; Buffer.contents buf module PostgreSQL_dialect : DIALECT = struct let name = "PostgreSQL" let default_port = Some 5432 let admin_database = Some "postgres" let supports_database_lifecycle = true let database_exists_sql = "SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname = $1)" (* Double-quote the identifier so names that are not bare identifiers (e.g. "my-db") are accepted and cannot break out of the statement. *) let create_database_sql db_name = Printf.sprintf "CREATE DATABASE %s" (quote_identifier ~q:'"' db_name) let drop_database_sql db_name = Printf.sprintf "DROP DATABASE IF EXISTS %s" (quote_identifier ~q:'"' db_name) let timestamp_to_string col = Printf.sprintf "%s::text" col end module MariaDB_dialect : DIALECT = struct let name = "MariaDB" let default_port = Some 3306 let admin_database = Some "mysql" let supports_database_lifecycle = true let database_exists_sql = "SELECT EXISTS(SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME \ = ?)" (* Backtick-quote the identifier (doubling any embedded backtick) so names with backticks or other special characters cannot break out of the statement; IF NOT EXISTS for idempotency. *) let create_database_sql db_name = Printf.sprintf "CREATE DATABASE IF NOT EXISTS %s" (quote_identifier ~q:'`' db_name) let drop_database_sql db_name = Printf.sprintf "DROP DATABASE IF EXISTS %s" (quote_identifier ~q:'`' db_name) let timestamp_to_string col = Printf.sprintf "CAST(%s AS CHAR)" col end module SQLite_dialect : DIALECT = struct let name = "SQLite" let default_port = None let admin_database = None let supports_database_lifecycle = false (* These are unused for SQLite but required by signature *) let database_exists_sql = "" let create_database_sql _ = "" let drop_database_sql _ = "" let timestamp_to_string col = col end (** Normalize database URL for Caqti compatibility SQLite: caqti-driver-sqlite3 expects sqlite3:path (single colon), not sqlite3://path We accept both formats for user convenience but normalize to what Caqti expects. *) let normalize_url (url : string) : string = let prefix = "sqlite3://" in if String.starts_with ~prefix url then let n = String.length prefix in "sqlite3:" ^ String.sub url n (String.length url - n) else url let detect_from_url (url : string) : (t, string) result = if String.starts_with ~prefix:"postgresql://" url then Ok PostgreSQL else if String.starts_with ~prefix:"postgres://" url then Ok PostgreSQL else if String.starts_with ~prefix:"mariadb://" url then Ok MariaDB else if String.starts_with ~prefix:"mysql://" url then Ok MariaDB else if String.starts_with ~prefix:"sqlite3://" url then Ok SQLite else if String.starts_with ~prefix:"sqlite3:" url then Ok SQLite else let scheme = match String.index_opt url ':' with (* Grab "scheme://" for the message, but clamp the length: a URL like "ht:" has no room for the "//" and would otherwise overflow String.sub. *) | Some idx -> String.sub url 0 (min (idx + 3) (String.length url)) | None -> url in Error (Printf.sprintf "Unsupported database URL scheme: '%s'\n\n\ Supported databases:\n\ - PostgreSQL: postgresql:// or postgres://\n\ - MariaDB/MySQL: mariadb:// or mysql://\n\ - SQLite: sqlite3:// or sqlite3:\n\n\ Examples:\n\ - postgresql://user@localhost:5432/mydb\n\ - mariadb://root@localhost:3306/mydb\n\ - sqlite3://./dev.db (normalized to sqlite3:./dev.db)\n\ - sqlite3::memory: (in-memory database)\n\n" scheme) let get_dialect (db_type : t) : (module DIALECT) = match db_type with | PostgreSQL -> (module PostgreSQL_dialect) | MariaDB -> (module MariaDB_dialect) | SQLite -> (module SQLite_dialect) let to_string = function | PostgreSQL -> "PostgreSQL" | MariaDB -> "MariaDB" | SQLite -> "SQLite"
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>