Skip to main content

postgresql_commands/
createdb.rs

1use crate::Settings;
2use crate::traits::CommandBuilder;
3use std::convert::AsRef;
4use std::ffi::{OsStr, OsString};
5use std::path::PathBuf;
6
7/// `createdb` creates a `PostgreSQL` database.
8#[derive(Clone, Debug, Default)]
9pub struct CreateDbBuilder {
10    program_dir: Option<PathBuf>,
11    envs: Vec<(OsString, OsString)>,
12    tablespace: Option<OsString>,
13    echo: bool,
14    encoding: Option<OsString>,
15    locale: Option<OsString>,
16    lc_collate: Option<OsString>,
17    lc_ctype: Option<OsString>,
18    icu_locale: Option<OsString>,
19    icu_rules: Option<OsString>,
20    locale_provider: Option<OsString>,
21    owner: Option<OsString>,
22    strategy: Option<OsString>,
23    template: Option<OsString>,
24    version: bool,
25    help: bool,
26    host: Option<OsString>,
27    port: Option<u16>,
28    username: Option<OsString>,
29    no_password: bool,
30    password: bool,
31    pg_password: Option<OsString>,
32    maintenance_db: Option<OsString>,
33    dbname: Option<OsString>,
34    description: Option<OsString>,
35}
36
37impl CreateDbBuilder {
38    /// Create a new [`CreateDbBuilder`]
39    #[must_use]
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    /// Create a new [`CreateDbBuilder`] from [Settings]
45    pub fn from(settings: &dyn Settings) -> Self {
46        let mut builder = Self::new()
47            .program_dir(settings.get_binary_dir())
48            .host(settings.get_host())
49            .port(settings.get_port())
50            .username(settings.get_username())
51            .pg_password(settings.get_password());
52        if let Some(socket_dir) = settings.get_socket_dir() {
53            builder = builder.host(socket_dir.to_string_lossy().to_string());
54        }
55        builder
56    }
57
58    /// Location of the program binary
59    #[must_use]
60    pub fn program_dir<P: Into<PathBuf>>(mut self, path: P) -> Self {
61        self.program_dir = Some(path.into());
62        self
63    }
64
65    /// Default tablespace for the database
66    #[must_use]
67    pub fn tablespace<S: AsRef<OsStr>>(mut self, tablespace: S) -> Self {
68        self.tablespace = Some(tablespace.as_ref().to_os_string());
69        self
70    }
71
72    /// Show the commands being sent to the server
73    #[must_use]
74    pub fn echo(mut self) -> Self {
75        self.echo = true;
76        self
77    }
78
79    /// Encoding for the database
80    #[must_use]
81    pub fn encoding<S: AsRef<OsStr>>(mut self, encoding: S) -> Self {
82        self.encoding = Some(encoding.as_ref().to_os_string());
83        self
84    }
85
86    /// Locale settings for the database
87    #[must_use]
88    pub fn locale<S: AsRef<OsStr>>(mut self, locale: S) -> Self {
89        self.locale = Some(locale.as_ref().to_os_string());
90        self
91    }
92
93    /// `LC_COLLATE` setting for the database
94    #[must_use]
95    pub fn lc_collate<S: AsRef<OsStr>>(mut self, lc_collate: S) -> Self {
96        self.lc_collate = Some(lc_collate.as_ref().to_os_string());
97        self
98    }
99
100    /// `LC_CTYPE` setting for the database
101    #[must_use]
102    pub fn lc_ctype<S: AsRef<OsStr>>(mut self, lc_ctype: S) -> Self {
103        self.lc_ctype = Some(lc_ctype.as_ref().to_os_string());
104        self
105    }
106
107    /// ICU locale setting for the database
108    #[must_use]
109    pub fn icu_locale<S: AsRef<OsStr>>(mut self, icu_locale: S) -> Self {
110        self.icu_locale = Some(icu_locale.as_ref().to_os_string());
111        self
112    }
113
114    /// ICU rules setting for the database
115    #[must_use]
116    pub fn icu_rules<S: AsRef<OsStr>>(mut self, icu_rules: S) -> Self {
117        self.icu_rules = Some(icu_rules.as_ref().to_os_string());
118        self
119    }
120
121    /// Locale provider for the database's default collation
122    #[must_use]
123    pub fn locale_provider<S: AsRef<OsStr>>(mut self, locale_provider: S) -> Self {
124        self.locale_provider = Some(locale_provider.as_ref().to_os_string());
125        self
126    }
127
128    /// Database user to own the new database
129    #[must_use]
130    pub fn owner<S: AsRef<OsStr>>(mut self, owner: S) -> Self {
131        self.owner = Some(owner.as_ref().to_os_string());
132        self
133    }
134
135    /// Database creation strategy `wal_log` or `file_copy`
136    #[must_use]
137    pub fn strategy<S: AsRef<OsStr>>(mut self, strategy: S) -> Self {
138        self.strategy = Some(strategy.as_ref().to_os_string());
139        self
140    }
141
142    /// Template database to copy
143    #[must_use]
144    pub fn template<S: AsRef<OsStr>>(mut self, template: S) -> Self {
145        self.template = Some(template.as_ref().to_os_string());
146        self
147    }
148
149    /// Output version information, then exit
150    #[must_use]
151    pub fn version(mut self) -> Self {
152        self.version = true;
153        self
154    }
155
156    /// Show help, then exit
157    #[must_use]
158    pub fn help(mut self) -> Self {
159        self.help = true;
160        self
161    }
162
163    /// Database server host or socket directory
164    #[must_use]
165    pub fn host<S: AsRef<OsStr>>(mut self, host: S) -> Self {
166        self.host = Some(host.as_ref().to_os_string());
167        self
168    }
169
170    /// Database server port
171    #[must_use]
172    pub fn port(mut self, port: u16) -> Self {
173        self.port = Some(port);
174        self
175    }
176
177    /// User name to connect as
178    #[must_use]
179    pub fn username<S: AsRef<OsStr>>(mut self, username: S) -> Self {
180        self.username = Some(username.as_ref().to_os_string());
181        self
182    }
183
184    /// Never prompt for password
185    #[must_use]
186    pub fn no_password(mut self) -> Self {
187        self.no_password = true;
188        self
189    }
190
191    /// Force password prompt
192    #[must_use]
193    pub fn password(mut self) -> Self {
194        self.password = true;
195        self
196    }
197
198    /// user password
199    #[must_use]
200    pub fn pg_password<S: AsRef<OsStr>>(mut self, pg_password: S) -> Self {
201        self.pg_password = Some(pg_password.as_ref().to_os_string());
202        self
203    }
204
205    /// Alternate maintenance database
206    #[must_use]
207    pub fn maintenance_db<S: AsRef<OsStr>>(mut self, db: S) -> Self {
208        self.maintenance_db = Some(db.as_ref().to_os_string());
209        self
210    }
211
212    /// Database name
213    #[must_use]
214    pub fn dbname<S: AsRef<OsStr>>(mut self, dbname: S) -> Self {
215        self.dbname = Some(dbname.as_ref().to_os_string());
216        self
217    }
218
219    /// Database description
220    #[must_use]
221    pub fn description<S: AsRef<OsStr>>(mut self, description: S) -> Self {
222        self.description = Some(description.as_ref().to_os_string());
223        self
224    }
225}
226
227impl CommandBuilder for CreateDbBuilder {
228    /// Get the program name
229    fn get_program(&self) -> &'static OsStr {
230        "createdb".as_ref()
231    }
232
233    /// Location of the program binary
234    fn get_program_dir(&self) -> &Option<PathBuf> {
235        &self.program_dir
236    }
237
238    /// Get the arguments for the command
239    fn get_args(&self) -> Vec<OsString> {
240        let mut args: Vec<OsString> = Vec::new();
241
242        if let Some(tablespace) = &self.tablespace {
243            args.push("--tablespace".into());
244            args.push(tablespace.into());
245        }
246
247        if self.echo {
248            args.push("--echo".into());
249        }
250
251        if let Some(encoding) = &self.encoding {
252            args.push("--encoding".into());
253            args.push(encoding.into());
254        }
255
256        if let Some(locale) = &self.locale {
257            args.push("--locale".into());
258            args.push(locale.into());
259        }
260
261        if let Some(lc_collate) = &self.lc_collate {
262            args.push("--lc-collate".into());
263            args.push(lc_collate.into());
264        }
265
266        if let Some(lc_ctype) = &self.lc_ctype {
267            args.push("--lc-ctype".into());
268            args.push(lc_ctype.into());
269        }
270
271        if let Some(icu_locale) = &self.icu_locale {
272            args.push("--icu-locale".into());
273            args.push(icu_locale.into());
274        }
275
276        if let Some(icu_rules) = &self.icu_rules {
277            args.push("--icu-rules".into());
278            args.push(icu_rules.into());
279        }
280
281        if let Some(locale_provider) = &self.locale_provider {
282            args.push("--locale-provider".into());
283            args.push(locale_provider.into());
284        }
285
286        if let Some(owner) = &self.owner {
287            args.push("--owner".into());
288            args.push(owner.into());
289        }
290
291        if let Some(strategy) = &self.strategy {
292            args.push("--strategy".into());
293            args.push(strategy.into());
294        }
295
296        if let Some(template) = &self.template {
297            args.push("--template".into());
298            args.push(template.into());
299        }
300
301        if self.version {
302            args.push("--version".into());
303        }
304
305        if self.help {
306            args.push("--help".into());
307        }
308
309        if let Some(host) = &self.host {
310            args.push("--host".into());
311            args.push(host.into());
312        }
313
314        if let Some(port) = &self.port {
315            args.push("--port".into());
316            args.push(port.to_string().into());
317        }
318
319        if let Some(username) = &self.username {
320            args.push("--username".into());
321            args.push(username.into());
322        }
323
324        if self.no_password {
325            args.push("--no-password".into());
326        }
327
328        if self.password {
329            args.push("--password".into());
330        }
331
332        if let Some(maintenance_db) = &self.maintenance_db {
333            args.push("--maintenance-db".into());
334            args.push(maintenance_db.into());
335        }
336
337        if let Some(dbname) = &self.dbname {
338            args.push(dbname.into());
339        }
340
341        if let Some(description) = &self.description {
342            args.push(description.into());
343        }
344
345        args
346    }
347
348    /// Get the environment variables for the command
349    fn get_envs(&self) -> Vec<(OsString, OsString)> {
350        let mut envs: Vec<(OsString, OsString)> = self.envs.clone();
351
352        if let Some(password) = &self.pg_password {
353            envs.push(("PGPASSWORD".into(), password.into()));
354        }
355
356        envs
357    }
358
359    /// Set an environment variable for the command
360    fn env<S: AsRef<OsStr>>(mut self, key: S, value: S) -> Self {
361        self.envs
362            .push((key.as_ref().to_os_string(), value.as_ref().to_os_string()));
363        self
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370    use crate::TestSettings;
371    use crate::TestSocketSettings;
372    use crate::traits::CommandToString;
373    use test_log::test;
374
375    #[test]
376    fn test_builder_new() {
377        let command = CreateDbBuilder::new().program_dir(".").build();
378        assert_eq!(
379            PathBuf::from(".").join("createdb"),
380            PathBuf::from(command.to_command_string().replace('"', ""))
381        );
382    }
383
384    #[test]
385    fn test_builder_from() {
386        let command = CreateDbBuilder::from(&TestSettings).build();
387        #[cfg(not(target_os = "windows"))]
388        let command_prefix = r#"PGPASSWORD="password" "./createdb" "#;
389        #[cfg(target_os = "windows")]
390        let command_prefix = r#"".\\createdb" "#;
391
392        assert_eq!(
393            format!(
394                r#"{command_prefix}"--host" "localhost" "--port" "5432" "--username" "postgres""#
395            ),
396            command.to_command_string()
397        );
398    }
399
400    #[test]
401    fn test_builder() {
402        let command = CreateDbBuilder::new()
403            .env("PGDATABASE", "database")
404            .tablespace("pg_default")
405            .echo()
406            .encoding("UTF8")
407            .locale("en_US.UTF-8")
408            .lc_collate("en_US.UTF-8")
409            .lc_ctype("en_US.UTF-8")
410            .icu_locale("en_US")
411            .icu_rules("standard")
412            .locale_provider("icu")
413            .owner("postgres")
414            .strategy("wal_log")
415            .template("template0")
416            .version()
417            .help()
418            .host("localhost")
419            .port(5432)
420            .username("postgres")
421            .no_password()
422            .password()
423            .pg_password("password")
424            .maintenance_db("postgres")
425            .dbname("testdb")
426            .description("Test Database")
427            .build();
428        #[cfg(not(target_os = "windows"))]
429        let command_prefix = r#"PGDATABASE="database" PGPASSWORD="password" "#;
430        #[cfg(target_os = "windows")]
431        let command_prefix = String::new();
432
433        assert_eq!(
434            format!(
435                r#"{command_prefix}"createdb" "--tablespace" "pg_default" "--echo" "--encoding" "UTF8" "--locale" "en_US.UTF-8" "--lc-collate" "en_US.UTF-8" "--lc-ctype" "en_US.UTF-8" "--icu-locale" "en_US" "--icu-rules" "standard" "--locale-provider" "icu" "--owner" "postgres" "--strategy" "wal_log" "--template" "template0" "--version" "--help" "--host" "localhost" "--port" "5432" "--username" "postgres" "--no-password" "--password" "--maintenance-db" "postgres" "testdb" "Test Database""#
436            ),
437            command.to_command_string()
438        );
439    }
440
441    #[test]
442    fn test_builder_from_socket() {
443        let command = CreateDbBuilder::from(&TestSocketSettings).build();
444        #[cfg(not(target_os = "windows"))]
445        let command_prefix = r#"PGPASSWORD="password" "./createdb" "#;
446        #[cfg(target_os = "windows")]
447        let command_prefix = r#"".\\createdb" "#;
448
449        assert_eq!(
450            format!(
451                r#"{command_prefix}"--host" "/tmp/pg_socket" "--port" "5432" "--username" "postgres""#
452            ),
453            command.to_command_string()
454        );
455    }
456}