postgresql_commands/
pg_rewind.rs

1use crate::Settings;
2use crate::traits::CommandBuilder;
3use std::convert::AsRef;
4use std::ffi::{OsStr, OsString};
5use std::path::PathBuf;
6
7/// `pg_rewind` synchronizes a `PostgreSQL` data directory with another data directory.
8#[derive(Clone, Debug, Default)]
9pub struct PgRewindBuilder {
10    program_dir: Option<PathBuf>,
11    envs: Vec<(OsString, OsString)>,
12    restore_target_wal: bool,
13    target_pgdata: Option<PathBuf>,
14    source_pgdata: Option<PathBuf>,
15    source_server: Option<OsString>,
16    dry_run: bool,
17    no_sync: bool,
18    progress: bool,
19    write_recovery_conf: bool,
20    config_file: Option<OsString>,
21    debug: bool,
22    no_ensure_shutdown: bool,
23    version: bool,
24    help: bool,
25}
26
27impl PgRewindBuilder {
28    /// Create a new [`PgRewindBuilder`]
29    #[must_use]
30    pub fn new() -> Self {
31        Self::default()
32    }
33
34    /// Create a new [`PgRewindBuilder`] from [Settings]
35    pub fn from(settings: &dyn Settings) -> Self {
36        Self::new().program_dir(settings.get_binary_dir())
37    }
38
39    /// Location of the program binary
40    #[must_use]
41    pub fn program_dir<P: Into<PathBuf>>(mut self, path: P) -> Self {
42        self.program_dir = Some(path.into());
43        self
44    }
45
46    /// use `restore_command` in target configuration to retrieve WAL files from archives
47    #[must_use]
48    pub fn restore_target_wal(mut self) -> Self {
49        self.restore_target_wal = true;
50        self
51    }
52
53    /// existing data directory to modify
54    #[must_use]
55    pub fn target_pgdata<P: Into<PathBuf>>(mut self, directory: P) -> Self {
56        self.target_pgdata = Some(directory.into());
57        self
58    }
59
60    /// source data directory to synchronize with
61    #[must_use]
62    pub fn source_pgdata<P: Into<PathBuf>>(mut self, directory: P) -> Self {
63        self.source_pgdata = Some(directory.into());
64        self
65    }
66
67    /// source server to synchronize with
68    #[must_use]
69    pub fn source_server<S: AsRef<OsStr>>(mut self, connstr: S) -> Self {
70        self.source_server = Some(connstr.as_ref().to_os_string());
71        self
72    }
73
74    /// stop before modifying anything
75    #[must_use]
76    pub fn dry_run(mut self) -> Self {
77        self.dry_run = true;
78        self
79    }
80
81    /// do not wait for changes to be written safely to disk
82    #[must_use]
83    pub fn no_sync(mut self) -> Self {
84        self.no_sync = true;
85        self
86    }
87
88    /// write progress messages
89    #[must_use]
90    pub fn progress(mut self) -> Self {
91        self.progress = true;
92        self
93    }
94
95    /// write configuration for replication (requires --source-server)
96    #[must_use]
97    pub fn write_recovery_conf(mut self) -> Self {
98        self.write_recovery_conf = true;
99        self
100    }
101
102    /// use specified main server configuration file when running target cluster
103    #[must_use]
104    pub fn config_file<S: AsRef<OsStr>>(mut self, filename: S) -> Self {
105        self.config_file = Some(filename.as_ref().to_os_string());
106        self
107    }
108
109    /// write a lot of debug messages
110    #[must_use]
111    pub fn debug(mut self) -> Self {
112        self.debug = true;
113        self
114    }
115
116    /// do not automatically fix unclean shutdown
117    #[must_use]
118    pub fn no_ensure_shutdown(mut self) -> Self {
119        self.no_ensure_shutdown = true;
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    /// show help, then exit
131    #[must_use]
132    pub fn help(mut self) -> Self {
133        self.help = true;
134        self
135    }
136}
137
138impl CommandBuilder for PgRewindBuilder {
139    /// Get the program name
140    fn get_program(&self) -> &'static OsStr {
141        "pg_rewind".as_ref()
142    }
143
144    /// Location of the program binary
145    fn get_program_dir(&self) -> &Option<PathBuf> {
146        &self.program_dir
147    }
148
149    /// Get the arguments for the command
150    fn get_args(&self) -> Vec<OsString> {
151        let mut args: Vec<OsString> = Vec::new();
152
153        if self.restore_target_wal {
154            args.push("--restore-target-wal".into());
155        }
156
157        if let Some(directory) = &self.target_pgdata {
158            args.push("--target-pgdata".into());
159            args.push(directory.into());
160        }
161
162        if let Some(directory) = &self.source_pgdata {
163            args.push("--source-pgdata".into());
164            args.push(directory.into());
165        }
166
167        if let Some(connstr) = &self.source_server {
168            args.push("--source-server".into());
169            args.push(connstr.into());
170        }
171
172        if self.dry_run {
173            args.push("--dry-run".into());
174        }
175
176        if self.no_sync {
177            args.push("--no-sync".into());
178        }
179
180        if self.progress {
181            args.push("--progress".into());
182        }
183
184        if self.write_recovery_conf {
185            args.push("--write-recovery-conf".into());
186        }
187
188        if let Some(filename) = &self.config_file {
189            args.push("--config-file".into());
190            args.push(filename.into());
191        }
192
193        if self.debug {
194            args.push("--debug".into());
195        }
196
197        if self.no_ensure_shutdown {
198            args.push("--no-ensure-shutdown".into());
199        }
200
201        if self.version {
202            args.push("--version".into());
203        }
204
205        if self.help {
206            args.push("--help".into());
207        }
208
209        args
210    }
211
212    /// Get the environment variables for the command
213    fn get_envs(&self) -> Vec<(OsString, OsString)> {
214        self.envs.clone()
215    }
216
217    /// Set an environment variable for the command
218    fn env<S: AsRef<OsStr>>(mut self, key: S, value: S) -> Self {
219        self.envs
220            .push((key.as_ref().to_os_string(), value.as_ref().to_os_string()));
221        self
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use crate::TestSettings;
229    use crate::traits::CommandToString;
230    use test_log::test;
231
232    #[test]
233    fn test_builder_new() {
234        let command = PgRewindBuilder::new().program_dir(".").build();
235        assert_eq!(
236            PathBuf::from(".").join("pg_rewind"),
237            PathBuf::from(command.to_command_string().replace('"', ""))
238        );
239    }
240
241    #[test]
242    fn test_builder_from() {
243        let command = PgRewindBuilder::from(&TestSettings).build();
244        #[cfg(not(target_os = "windows"))]
245        let command_prefix = r#""./pg_rewind""#;
246        #[cfg(target_os = "windows")]
247        let command_prefix = r#"".\\pg_rewind""#;
248
249        assert_eq!(format!("{command_prefix}"), command.to_command_string());
250    }
251
252    #[test]
253    fn test_builder() {
254        let command = PgRewindBuilder::new()
255            .env("PGDATABASE", "database")
256            .restore_target_wal()
257            .target_pgdata("target_pgdata")
258            .source_pgdata("source_pgdata")
259            .source_server("source_server")
260            .dry_run()
261            .no_sync()
262            .progress()
263            .write_recovery_conf()
264            .config_file("config_file")
265            .debug()
266            .no_ensure_shutdown()
267            .version()
268            .help()
269            .build();
270        #[cfg(not(target_os = "windows"))]
271        let command_prefix = r#"PGDATABASE="database" "#;
272        #[cfg(target_os = "windows")]
273        let command_prefix = String::new();
274
275        assert_eq!(
276            format!(
277                r#"{command_prefix}"pg_rewind" "--restore-target-wal" "--target-pgdata" "target_pgdata" "--source-pgdata" "source_pgdata" "--source-server" "source_server" "--dry-run" "--no-sync" "--progress" "--write-recovery-conf" "--config-file" "config_file" "--debug" "--no-ensure-shutdown" "--version" "--help""#
278            ),
279            command.to_command_string()
280        );
281    }
282}