Skip to main content

postgresql_commands/
clusterdb.rs

1use crate::Settings;
2use crate::traits::CommandBuilder;
3use std::convert::AsRef;
4use std::ffi::{OsStr, OsString};
5use std::path::PathBuf;
6
7/// `clusterdb` clusters all previously clustered tables in a database.
8#[derive(Clone, Debug, Default)]
9pub struct ClusterDbBuilder {
10    program_dir: Option<PathBuf>,
11    envs: Vec<(OsString, OsString)>,
12    all: bool,
13    dbname: Option<OsString>,
14    echo: bool,
15    quiet: bool,
16    table: Option<OsString>,
17    verbose: bool,
18    version: bool,
19    help: bool,
20    host: Option<OsString>,
21    port: Option<u16>,
22    username: Option<OsString>,
23    no_password: bool,
24    password: bool,
25    pg_password: Option<OsString>,
26    maintenance_db: Option<OsString>,
27}
28
29impl ClusterDbBuilder {
30    /// Create a new [`ClusterDbBuilder`]
31    #[must_use]
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    /// Create a new [`ClusterDbBuilder`] from [Settings]
37    pub fn from(settings: &dyn Settings) -> Self {
38        let mut builder = Self::new()
39            .program_dir(settings.get_binary_dir())
40            .host(settings.get_host())
41            .port(settings.get_port())
42            .username(settings.get_username())
43            .pg_password(settings.get_password());
44        if let Some(socket_dir) = settings.get_socket_dir() {
45            builder = builder.host(socket_dir.to_string_lossy().to_string());
46        }
47        builder
48    }
49
50    /// Location of the program binary
51    #[must_use]
52    fn program_dir<P: Into<PathBuf>>(mut self, path: P) -> Self {
53        self.program_dir = Some(path.into());
54        self
55    }
56
57    /// Cluster all databases
58    #[must_use]
59    pub fn all(mut self) -> Self {
60        self.all = true;
61        self
62    }
63
64    /// Database to cluster
65    #[must_use]
66    pub fn dbname<S: AsRef<OsStr>>(mut self, dbname: S) -> Self {
67        self.dbname = Some(dbname.as_ref().to_os_string());
68        self
69    }
70
71    /// Show the commands being sent to the server
72    #[must_use]
73    pub fn echo(mut self) -> Self {
74        self.echo = true;
75        self
76    }
77
78    /// Don't write any messages
79    #[must_use]
80    pub fn quiet(mut self) -> Self {
81        self.quiet = true;
82        self
83    }
84
85    /// Cluster specific table(s) only
86    #[must_use]
87    pub fn table<S: AsRef<OsStr>>(mut self, table: S) -> Self {
88        self.table = Some(table.as_ref().to_os_string());
89        self
90    }
91
92    /// Write a lot of output
93    #[must_use]
94    pub fn verbose(mut self) -> Self {
95        self.verbose = true;
96        self
97    }
98
99    /// Output version information, then exit
100    #[must_use]
101    pub fn version(mut self) -> Self {
102        self.version = true;
103        self
104    }
105
106    /// Show help, then exit
107    #[must_use]
108    pub fn help(mut self) -> Self {
109        self.help = true;
110        self
111    }
112
113    /// Database server host or socket directory
114    #[must_use]
115    pub fn host<S: AsRef<OsStr>>(mut self, host: S) -> Self {
116        self.host = Some(host.as_ref().to_os_string());
117        self
118    }
119
120    /// Database server port
121    #[must_use]
122    pub fn port(mut self, port: u16) -> Self {
123        self.port = Some(port);
124        self
125    }
126
127    /// User name to connect as
128    #[must_use]
129    pub fn username<S: AsRef<OsStr>>(mut self, username: S) -> Self {
130        self.username = Some(username.as_ref().to_os_string());
131        self
132    }
133
134    /// Never prompt for password
135    #[must_use]
136    pub fn no_password(mut self) -> Self {
137        self.no_password = true;
138        self
139    }
140
141    /// Force password prompt
142    #[must_use]
143    pub fn password(mut self) -> Self {
144        self.password = true;
145        self
146    }
147
148    /// user password
149    #[must_use]
150    pub fn pg_password<S: AsRef<OsStr>>(mut self, pg_password: S) -> Self {
151        self.pg_password = Some(pg_password.as_ref().to_os_string());
152        self
153    }
154
155    /// Alternate maintenance database
156    #[must_use]
157    pub fn maintenance_db<S: AsRef<OsStr>>(mut self, db: S) -> Self {
158        self.maintenance_db = Some(db.as_ref().to_os_string());
159        self
160    }
161}
162
163impl CommandBuilder for ClusterDbBuilder {
164    /// Get the program name
165    fn get_program(&self) -> &'static OsStr {
166        "clusterdb".as_ref()
167    }
168
169    /// Location of the program binary
170    fn get_program_dir(&self) -> &Option<PathBuf> {
171        &self.program_dir
172    }
173
174    /// Get the arguments for the command
175    fn get_args(&self) -> Vec<OsString> {
176        let mut args: Vec<OsString> = Vec::new();
177
178        if self.all {
179            args.push("--all".into());
180        }
181
182        if let Some(dbname) = &self.dbname {
183            args.push("--dbname".into());
184            args.push(dbname.into());
185        }
186
187        if self.echo {
188            args.push("--echo".into());
189        }
190
191        if self.quiet {
192            args.push("--quiet".into());
193        }
194
195        if let Some(table) = &self.table {
196            args.push("--table".into());
197            args.push(table.into());
198        }
199
200        if self.verbose {
201            args.push("--verbose".into());
202        }
203
204        if self.version {
205            args.push("--version".into());
206        }
207
208        if self.help {
209            args.push("--help".into());
210        }
211
212        if let Some(host) = &self.host {
213            args.push("--host".into());
214            args.push(host.into());
215        }
216
217        if let Some(port) = &self.port {
218            args.push("--port".into());
219            args.push(port.to_string().into());
220        }
221
222        if let Some(username) = &self.username {
223            args.push("--username".into());
224            args.push(username.into());
225        }
226
227        if self.no_password {
228            args.push("--no-password".into());
229        }
230
231        if self.password {
232            args.push("--password".into());
233        }
234
235        if let Some(maintenance_db) = &self.maintenance_db {
236            args.push("--maintenance-db".into());
237            args.push(maintenance_db.into());
238        }
239
240        args
241    }
242
243    /// Get the environment variables for the command
244    fn get_envs(&self) -> Vec<(OsString, OsString)> {
245        let mut envs: Vec<(OsString, OsString)> = self.envs.clone();
246
247        if let Some(password) = &self.pg_password {
248            envs.push(("PGPASSWORD".into(), password.into()));
249        }
250
251        envs
252    }
253
254    /// Set an environment variable for the command
255    fn env<S: AsRef<OsStr>>(mut self, key: S, value: S) -> Self {
256        self.envs
257            .push((key.as_ref().to_os_string(), value.as_ref().to_os_string()));
258        self
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265    use crate::TestSettings;
266    use crate::TestSocketSettings;
267    use crate::traits::CommandToString;
268    use test_log::test;
269
270    #[test]
271    fn test_builder_new() {
272        let command = ClusterDbBuilder::new().program_dir(".").build();
273        assert_eq!(
274            PathBuf::from(".").join("clusterdb"),
275            PathBuf::from(command.to_command_string().replace('"', ""))
276        );
277    }
278
279    #[test]
280    fn test_builder_from() {
281        let command = ClusterDbBuilder::from(&TestSettings).build();
282        #[cfg(not(target_os = "windows"))]
283        let command_prefix = r#"PGPASSWORD="password" "./clusterdb" "#;
284        #[cfg(target_os = "windows")]
285        let command_prefix = r#"".\\clusterdb" "#;
286
287        assert_eq!(
288            format!(
289                r#"{command_prefix}"--host" "localhost" "--port" "5432" "--username" "postgres""#
290            ),
291            command.to_command_string()
292        );
293    }
294
295    #[test]
296    fn test_builder() {
297        let command = ClusterDbBuilder::new()
298            .env("PGDATABASE", "database")
299            .all()
300            .dbname("dbname")
301            .echo()
302            .quiet()
303            .table("table")
304            .verbose()
305            .version()
306            .help()
307            .host("localhost")
308            .port(5432)
309            .username("postgres")
310            .no_password()
311            .password()
312            .pg_password("password")
313            .maintenance_db("postgres")
314            .build();
315        #[cfg(not(target_os = "windows"))]
316        let command_prefix = r#"PGDATABASE="database" PGPASSWORD="password" "#;
317        #[cfg(target_os = "windows")]
318        let command_prefix = String::new();
319
320        assert_eq!(
321            format!(
322                r#"{command_prefix}"clusterdb" "--all" "--dbname" "dbname" "--echo" "--quiet" "--table" "table" "--verbose" "--version" "--help" "--host" "localhost" "--port" "5432" "--username" "postgres" "--no-password" "--password" "--maintenance-db" "postgres""#
323            ),
324            command.to_command_string()
325        );
326    }
327
328    #[test]
329    fn test_builder_from_socket() {
330        let command = ClusterDbBuilder::from(&TestSocketSettings).build();
331        #[cfg(not(target_os = "windows"))]
332        let command_prefix = r#"PGPASSWORD="password" "./clusterdb" "#;
333        #[cfg(target_os = "windows")]
334        let command_prefix = r#"".\\clusterdb" "#;
335
336        assert_eq!(
337            format!(
338                r#"{command_prefix}"--host" "/tmp/pg_socket" "--port" "5432" "--username" "postgres""#
339            ),
340            command.to_command_string()
341        );
342    }
343}