Skip to main content

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}