postgresql_commands/
pg_ctl.rs

1use crate::Settings;
2use crate::traits::CommandBuilder;
3use std::convert::AsRef;
4use std::ffi::{OsStr, OsString};
5use std::fmt::Display;
6use std::path::PathBuf;
7
8/// `pg_ctl` is a utility to initialize, start, stop, or control a `PostgreSQL` server.
9#[derive(Clone, Debug, Default)]
10pub struct PgCtlBuilder {
11    program_dir: Option<PathBuf>,
12    envs: Vec<(OsString, OsString)>,
13    mode: Option<Mode>,
14    pgdata: Option<PathBuf>,
15    silent: bool,
16    timeout: Option<u16>,
17    version: bool,
18    wait: bool,
19    no_wait: bool,
20    help: bool,
21    core_files: bool,
22    log: Option<PathBuf>,
23    options: Vec<OsString>,
24    path_to_postgres: Option<OsString>,
25    shutdown_mode: Option<ShutdownMode>,
26    signal: Option<OsString>,
27    pid: Option<OsString>,
28}
29
30#[derive(Clone, Debug)]
31pub enum Mode {
32    InitDb,
33    Kill,
34    LogRotate,
35    Promote,
36    Restart,
37    Reload,
38    Start,
39    Stop,
40    Status,
41}
42
43impl Display for Mode {
44    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        match self {
46            Mode::InitDb => write!(formatter, "initdb"),
47            Mode::Kill => write!(formatter, "kill"),
48            Mode::LogRotate => write!(formatter, "logrotate"),
49            Mode::Promote => write!(formatter, "promote"),
50            Mode::Restart => write!(formatter, "restart"),
51            Mode::Reload => write!(formatter, "reload"),
52            Mode::Start => write!(formatter, "start"),
53            Mode::Stop => write!(formatter, "stop"),
54            Mode::Status => write!(formatter, "status"),
55        }
56    }
57}
58
59#[derive(Clone, Debug)]
60pub enum ShutdownMode {
61    Smart,
62    Fast,
63    Immediate,
64}
65
66impl Display for ShutdownMode {
67    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        match self {
69            ShutdownMode::Smart => write!(formatter, "smart"),
70            ShutdownMode::Fast => write!(formatter, "fast"),
71            ShutdownMode::Immediate => write!(formatter, "immediate"),
72        }
73    }
74}
75
76impl PgCtlBuilder {
77    /// Create a new [`PgCtlBuilder`]
78    #[must_use]
79    pub fn new() -> Self {
80        Self::default()
81    }
82
83    /// Create a new [`PgCtlBuilder`] from [Settings]
84    pub fn from(settings: &dyn Settings) -> Self {
85        Self::new().program_dir(settings.get_binary_dir())
86    }
87
88    /// Location of the program binary
89    #[must_use]
90    pub fn program_dir<P: Into<PathBuf>>(mut self, path: P) -> Self {
91        self.program_dir = Some(path.into());
92        self
93    }
94
95    /// mode
96    #[must_use]
97    pub fn mode(mut self, mode: Mode) -> Self {
98        self.mode = Some(mode);
99        self
100    }
101
102    /// location of the database storage area
103    #[must_use]
104    pub fn pgdata<P: Into<PathBuf>>(mut self, pgdata: P) -> Self {
105        self.pgdata = Some(pgdata.into());
106        self
107    }
108
109    /// only print errors, no informational messages
110    #[must_use]
111    pub fn silent(mut self) -> Self {
112        self.silent = true;
113        self
114    }
115
116    /// seconds to wait when using -w option
117    #[must_use]
118    pub fn timeout(mut self, timeout: u16) -> Self {
119        self.timeout = Some(timeout);
120        self
121    }
122
123    /// output version information, then exit
124    #[must_use]
125    pub fn version(mut self) -> Self {
126        self.version = true;
127        self
128    }
129
130    /// wait until operation completes (default)
131    #[must_use]
132    pub fn wait(mut self) -> Self {
133        self.wait = true;
134        self
135    }
136
137    /// do not wait until operation completes
138    #[must_use]
139    pub fn no_wait(mut self) -> Self {
140        self.no_wait = true;
141        self
142    }
143
144    /// show help, then exit
145    #[must_use]
146    pub fn help(mut self) -> Self {
147        self.help = true;
148        self
149    }
150
151    /// allow postgres to produce core files
152    #[must_use]
153    pub fn core_files(mut self) -> Self {
154        self.core_files = true;
155        self
156    }
157
158    /// write (or append) server log to FILENAME
159    #[must_use]
160    pub fn log<P: Into<PathBuf>>(mut self, log: P) -> Self {
161        self.log = Some(log.into());
162        self
163    }
164
165    /// command line options to pass to postgres (`PostgreSQL` server executable) or initdb
166    #[must_use]
167    pub fn options<S: AsRef<OsStr>>(mut self, options: &[S]) -> Self {
168        self.options = options.iter().map(|s| s.as_ref().to_os_string()).collect();
169        self
170    }
171
172    /// normally not necessary
173    #[must_use]
174    pub fn path_to_postgres<S: AsRef<OsStr>>(mut self, path_to_postgres: S) -> Self {
175        self.path_to_postgres = Some(path_to_postgres.as_ref().to_os_string());
176        self
177    }
178
179    /// MODE can be "smart", "fast", or "immediate"
180    #[must_use]
181    pub fn shutdown_mode(mut self, shutdown_mode: ShutdownMode) -> Self {
182        self.shutdown_mode = Some(shutdown_mode);
183        self
184    }
185
186    /// SIGNALNAME
187    #[must_use]
188    pub fn signal<S: AsRef<OsStr>>(mut self, signal: S) -> Self {
189        self.signal = Some(signal.as_ref().to_os_string());
190        self
191    }
192
193    /// PID
194    #[must_use]
195    pub fn pid<S: AsRef<OsStr>>(mut self, pid: S) -> Self {
196        self.pid = Some(pid.as_ref().to_os_string());
197        self
198    }
199}
200
201impl CommandBuilder for PgCtlBuilder {
202    /// Get the program name
203    fn get_program(&self) -> &'static OsStr {
204        "pg_ctl".as_ref()
205    }
206
207    /// Location of the program binary
208    fn get_program_dir(&self) -> &Option<PathBuf> {
209        &self.program_dir
210    }
211
212    /// Get the arguments for the command
213    fn get_args(&self) -> Vec<OsString> {
214        let mut args: Vec<OsString> = Vec::new();
215
216        if let Some(mode) = &self.mode {
217            args.push(mode.to_string().into());
218        }
219
220        if let Some(pgdata) = &self.pgdata {
221            args.push("--pgdata".into());
222            args.push(pgdata.into());
223        }
224
225        if self.silent {
226            args.push("--silent".into());
227        }
228
229        if let Some(timeout) = &self.timeout {
230            args.push("--timeout".into());
231            args.push(timeout.to_string().into());
232        }
233
234        if self.version {
235            args.push("--version".into());
236        }
237
238        if self.wait {
239            args.push("--wait".into());
240        }
241
242        if self.no_wait {
243            args.push("--no-wait".into());
244        }
245
246        if self.help {
247            args.push("--help".into());
248        }
249
250        if self.core_files {
251            args.push("--core-files".into());
252        }
253
254        if let Some(log) = &self.log {
255            args.push("--log".into());
256            args.push(log.into());
257        }
258
259        for option in &self.options {
260            args.push("-o".into());
261            args.push(option.into());
262        }
263
264        if let Some(path_to_postgres) = &self.path_to_postgres {
265            args.push("-p".into());
266            args.push(path_to_postgres.into());
267        }
268
269        if let Some(shutdown_mode) = &self.shutdown_mode {
270            args.push("--mode".into());
271            args.push(shutdown_mode.to_string().into());
272        }
273
274        if let Some(signal) = &self.signal {
275            args.push(signal.into());
276        }
277
278        if let Some(pid) = &self.pid {
279            args.push(pid.into());
280        }
281
282        args
283    }
284
285    /// Get the environment variables for the command
286    fn get_envs(&self) -> Vec<(OsString, OsString)> {
287        self.envs.clone()
288    }
289
290    /// Set an environment variable for the command
291    fn env<S: AsRef<OsStr>>(mut self, key: S, value: S) -> Self {
292        self.envs
293            .push((key.as_ref().to_os_string(), value.as_ref().to_os_string()));
294        self
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301    use crate::TestSettings;
302    use crate::traits::CommandToString;
303    use test_log::test;
304
305    #[test]
306    fn test_display_mode() {
307        assert_eq!("initdb", Mode::InitDb.to_string());
308        assert_eq!("kill", Mode::Kill.to_string());
309        assert_eq!("logrotate", Mode::LogRotate.to_string());
310        assert_eq!("promote", Mode::Promote.to_string());
311        assert_eq!("restart", Mode::Restart.to_string());
312        assert_eq!("reload", Mode::Reload.to_string());
313        assert_eq!("start", Mode::Start.to_string());
314        assert_eq!("stop", Mode::Stop.to_string());
315        assert_eq!("status", Mode::Status.to_string());
316    }
317
318    #[test]
319    fn test_display_shutdown_mode() {
320        assert_eq!("smart", ShutdownMode::Smart.to_string());
321        assert_eq!("fast", ShutdownMode::Fast.to_string());
322        assert_eq!("immediate", ShutdownMode::Immediate.to_string());
323    }
324
325    #[test]
326    fn test_builder_new() {
327        let command = PgCtlBuilder::new().program_dir(".").build();
328        assert_eq!(
329            PathBuf::from(".").join("pg_ctl"),
330            PathBuf::from(command.to_command_string().replace('"', ""))
331        );
332    }
333
334    #[test]
335    fn test_builder_from() {
336        let command = PgCtlBuilder::from(&TestSettings).build();
337        #[cfg(not(target_os = "windows"))]
338        let command_prefix = r#""./pg_ctl""#;
339        #[cfg(target_os = "windows")]
340        let command_prefix = r#"".\\pg_ctl""#;
341
342        assert_eq!(format!("{command_prefix}"), command.to_command_string());
343    }
344
345    #[test]
346    fn test_builder() {
347        let command = PgCtlBuilder::new()
348            .env("PGDATABASE", "database")
349            .mode(Mode::Start)
350            .pgdata("pgdata")
351            .silent()
352            .timeout(60)
353            .version()
354            .wait()
355            .no_wait()
356            .help()
357            .core_files()
358            .log("log")
359            .options(&["-c log_connections=on"])
360            .path_to_postgres("path_to_postgres")
361            .shutdown_mode(ShutdownMode::Smart)
362            .signal("HUP")
363            .pid("12345")
364            .build();
365        #[cfg(not(target_os = "windows"))]
366        let command_prefix = r#"PGDATABASE="database" "#;
367        #[cfg(target_os = "windows")]
368        let command_prefix = String::new();
369
370        assert_eq!(
371            format!(
372                r#"{command_prefix}"pg_ctl" "start" "--pgdata" "pgdata" "--silent" "--timeout" "60" "--version" "--wait" "--no-wait" "--help" "--core-files" "--log" "log" "-o" "-c log_connections=on" "-p" "path_to_postgres" "--mode" "smart" "HUP" "12345""#
373            ),
374            command.to_command_string()
375        );
376    }
377}