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#[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 #[must_use]
79 pub fn new() -> Self {
80 Self::default()
81 }
82
83 pub fn from(settings: &dyn Settings) -> Self {
85 Self::new().program_dir(settings.get_binary_dir())
86 }
87
88 #[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 #[must_use]
97 pub fn mode(mut self, mode: Mode) -> Self {
98 self.mode = Some(mode);
99 self
100 }
101
102 #[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 #[must_use]
111 pub fn silent(mut self) -> Self {
112 self.silent = true;
113 self
114 }
115
116 #[must_use]
118 pub fn timeout(mut self, timeout: u16) -> Self {
119 self.timeout = Some(timeout);
120 self
121 }
122
123 #[must_use]
125 pub fn version(mut self) -> Self {
126 self.version = true;
127 self
128 }
129
130 #[must_use]
132 pub fn wait(mut self) -> Self {
133 self.wait = true;
134 self
135 }
136
137 #[must_use]
139 pub fn no_wait(mut self) -> Self {
140 self.no_wait = true;
141 self
142 }
143
144 #[must_use]
146 pub fn help(mut self) -> Self {
147 self.help = true;
148 self
149 }
150
151 #[must_use]
153 pub fn core_files(mut self) -> Self {
154 self.core_files = true;
155 self
156 }
157
158 #[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 #[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 #[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 #[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 #[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 #[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 fn get_program(&self) -> &'static OsStr {
204 "pg_ctl".as_ref()
205 }
206
207 fn get_program_dir(&self) -> &Option<PathBuf> {
209 &self.program_dir
210 }
211
212 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 fn get_envs(&self) -> Vec<(OsString, OsString)> {
287 self.envs.clone()
288 }
289
290 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}