pg_embed/pg_commands.rs
1//! Factories for the three pg_ctl / initdb command executors.
2//!
3//! Each function in [`PgCommand`] constructs an [`AsyncCommandExecutor`] that
4//! is ready to run but has not yet been awaited. Callers obtain the executor,
5//! then call [`crate::command_executor::AsyncCommand::execute`] to actually
6//! run the command.
7
8use std::path::Path;
9
10use crate::command_executor::{AsyncCommand, AsyncCommandExecutor};
11use crate::pg_enums::{PgAuthMethod, PgProcessType, PgServerStatus};
12use crate::pg_errors::Error;
13use crate::pg_errors::Result;
14
15/// Factories for the three PostgreSQL lifecycle commands.
16pub struct PgCommand {}
17
18impl PgCommand {
19 /// Creates an [`AsyncCommandExecutor`] that runs `initdb` to initialise a
20 /// new database cluster.
21 ///
22 /// # Arguments
23 ///
24 /// * `init_db_exe` — Path to the `initdb` binary.
25 /// * `database_dir` — Target directory for the new cluster.
26 /// * `pw_file_path` — Path to the password file created by
27 /// [`crate::pg_access::PgAccess::create_password_file`].
28 /// * `user` — Name of the initial superuser.
29 /// * `auth_method` — Authentication method written to `pg_hba.conf`.
30 ///
31 /// # Errors
32 ///
33 /// Returns [`Error::InvalidPgUrl`] if any of the path arguments cannot be
34 /// converted to a UTF-8 string (required for the `--pwfile=` argument
35 /// format).
36 /// Returns [`Error::PgInitFailure`] if the process cannot be spawned.
37 pub fn init_db_executor(
38 init_db_exe: &Path,
39 database_dir: &Path,
40 pw_file_path: &Path,
41 user: &str,
42 auth_method: &PgAuthMethod,
43 ) -> Result<AsyncCommandExecutor<PgServerStatus, Error, PgProcessType>> {
44 let init_db_executable = init_db_exe.as_os_str();
45 let pw_file_str = pw_file_path
46 .to_str()
47 .ok_or(Error::InvalidPgUrl)?;
48 let password_file_arg = format!("--pwfile={}", pw_file_str);
49 let auth_host = match auth_method {
50 PgAuthMethod::Plain => "password",
51 PgAuthMethod::MD5 => "md5",
52 PgAuthMethod::ScramSha256 => "scram-sha-256",
53 };
54 let db_dir_str = database_dir.to_str().ok_or(Error::InvalidPgUrl)?;
55 let args = [
56 "-A",
57 auth_host,
58 "-U",
59 user,
60 // The postgres-tokio driver uses utf8 encoding, however on windows
61 // if -E is not specified WIN1252 encoding is chosen by default
62 // which can lead to encoding errors like this:
63 //
64 // ERROR: character with byte sequence 0xe0 0xab 0x87 in encoding
65 // "UTF8" has no equivalent in encoding "WIN1252"
66 "-E=UTF8",
67 "-D",
68 db_dir_str,
69 &password_file_arg,
70 ];
71
72 AsyncCommandExecutor::<PgServerStatus, Error, PgProcessType>::new(
73 init_db_executable,
74 args,
75 PgProcessType::InitDb,
76 )
77 }
78
79 /// Creates an [`AsyncCommandExecutor`] that runs `pg_ctl start`.
80 ///
81 /// # Arguments
82 ///
83 /// * `pg_ctl_exe` — Path to the `pg_ctl` binary.
84 /// * `database_dir` — The cluster directory passed to `pg_ctl -D`.
85 /// * `port` — TCP port PostgreSQL should listen on.
86 ///
87 /// # Errors
88 ///
89 /// Returns [`Error::InvalidPgUrl`] if `database_dir` is not valid UTF-8.
90 /// Returns [`Error::PgStartFailure`] if the process cannot be spawned.
91 pub fn start_db_executor(
92 pg_ctl_exe: &Path,
93 database_dir: &Path,
94 port: &u16,
95 ) -> Result<AsyncCommandExecutor<PgServerStatus, Error, PgProcessType>> {
96 let pg_ctl_executable = pg_ctl_exe.as_os_str();
97 let port_arg = format!("-F -p {}", port);
98 let db_dir_str = database_dir.to_str().ok_or(Error::InvalidPgUrl)?;
99 let args = ["-o", &port_arg, "start", "-w", "-D", db_dir_str];
100 AsyncCommandExecutor::<PgServerStatus, Error, PgProcessType>::new(
101 pg_ctl_executable,
102 args,
103 PgProcessType::StartDb,
104 )
105 }
106
107 /// Creates an [`AsyncCommandExecutor`] that runs `pg_ctl stop`.
108 ///
109 /// # Arguments
110 ///
111 /// * `pg_ctl_exe` — Path to the `pg_ctl` binary.
112 /// * `database_dir` — The cluster directory passed to `pg_ctl -D`.
113 ///
114 /// # Errors
115 ///
116 /// Returns [`Error::InvalidPgUrl`] if `database_dir` is not valid UTF-8.
117 /// Returns [`Error::PgStopFailure`] if the process cannot be spawned.
118 pub fn stop_db_executor(
119 pg_ctl_exe: &Path,
120 database_dir: &Path,
121 ) -> Result<AsyncCommandExecutor<PgServerStatus, Error, PgProcessType>> {
122 let pg_ctl_executable = pg_ctl_exe.as_os_str();
123 let db_dir_str = database_dir.to_str().ok_or(Error::InvalidPgUrl)?;
124 let args = ["stop", "-w", "-D", db_dir_str];
125 AsyncCommandExecutor::<PgServerStatus, Error, PgProcessType>::new(
126 pg_ctl_executable,
127 args,
128 PgProcessType::StopDb,
129 )
130 }
131}