postgresql_commands/
pg_restore.rs

1use crate::Settings;
2use crate::traits::CommandBuilder;
3use std::convert::AsRef;
4use std::ffi::{OsStr, OsString};
5use std::path::PathBuf;
6
7/// `pg_restore` restores a `PostgreSQL` database from an archive created by `pg_dump`.
8#[derive(Clone, Debug, Default)]
9pub struct PgRestoreBuilder {
10    program_dir: Option<PathBuf>,
11    envs: Vec<(OsString, OsString)>,
12    dbname: Option<OsString>,
13    file: Option<OsString>,
14    format: Option<OsString>,
15    list: bool,
16    verbose: bool,
17    version: bool,
18    help: bool,
19    data_only: bool,
20    clean: bool,
21    create: bool,
22    exit_on_error: bool,
23    index: Option<OsString>,
24    jobs: Option<OsString>,
25    use_list: Option<OsString>,
26    schema: Option<OsString>,
27    exclude_schema: Option<OsString>,
28    no_owner: bool,
29    function: Option<OsString>,
30    schema_only: bool,
31    superuser: Option<OsString>,
32    table: Option<OsString>,
33    trigger: Option<OsString>,
34    no_privileges: bool,
35    single_transaction: bool,
36    disable_triggers: bool,
37    enable_row_security: bool,
38    if_exists: bool,
39    no_comments: bool,
40    no_data_for_failed_tables: bool,
41    no_publications: bool,
42    no_security_labels: bool,
43    no_subscriptions: bool,
44    no_table_access_method: bool,
45    no_tablespaces: bool,
46    section: Option<OsString>,
47    strict_names: bool,
48    use_set_session_authorization: bool,
49    host: Option<OsString>,
50    port: Option<u16>,
51    username: Option<OsString>,
52    no_password: bool,
53    password: bool,
54    pg_password: Option<OsString>,
55    role: Option<OsString>,
56}
57
58impl PgRestoreBuilder {
59    /// Create a new [`PgRestoreBuilder`]
60    #[must_use]
61    pub fn new() -> Self {
62        Self::default()
63    }
64
65    /// Create a new [`PgRestoreBuilder`] from [Settings]
66    pub fn from(settings: &dyn Settings) -> Self {
67        Self::new()
68            .program_dir(settings.get_binary_dir())
69            .host(settings.get_host())
70            .port(settings.get_port())
71            .username(settings.get_username())
72            .pg_password(settings.get_password())
73    }
74
75    /// Location of the program binary
76    #[must_use]
77    pub fn program_dir<P: Into<PathBuf>>(mut self, path: P) -> Self {
78        self.program_dir = Some(path.into());
79        self
80    }
81
82    /// connect to database name
83    #[must_use]
84    pub fn dbname<S: AsRef<OsStr>>(mut self, name: S) -> Self {
85        self.dbname = Some(name.as_ref().to_os_string());
86        self
87    }
88
89    /// output file name (- for stdout)
90    #[must_use]
91    pub fn file<S: AsRef<OsStr>>(mut self, filename: S) -> Self {
92        self.file = Some(filename.as_ref().to_os_string());
93        self
94    }
95
96    /// backup file format (should be automatic)
97    #[must_use]
98    pub fn format<S: AsRef<OsStr>>(mut self, format: S) -> Self {
99        self.format = Some(format.as_ref().to_os_string());
100        self
101    }
102
103    /// print summarized TOC of the archive
104    #[must_use]
105    pub fn list(mut self) -> Self {
106        self.list = true;
107        self
108    }
109
110    /// verbose mode
111    #[must_use]
112    pub fn verbose(mut self) -> Self {
113        self.verbose = true;
114        self
115    }
116
117    /// output version information, then exit
118    #[must_use]
119    pub fn version(mut self) -> Self {
120        self.version = true;
121        self
122    }
123
124    /// show help, then exit
125    #[must_use]
126    pub fn help(mut self) -> Self {
127        self.help = true;
128        self
129    }
130
131    /// restore only the data, no schema
132    #[must_use]
133    pub fn data_only(mut self) -> Self {
134        self.data_only = true;
135        self
136    }
137
138    /// clean (drop) database objects before recreating
139    #[must_use]
140    pub fn clean(mut self) -> Self {
141        self.clean = true;
142        self
143    }
144
145    /// create the target database
146    #[must_use]
147    pub fn create(mut self) -> Self {
148        self.create = true;
149        self
150    }
151
152    /// exit on error, default is to continue
153    #[must_use]
154    pub fn exit_on_error(mut self) -> Self {
155        self.exit_on_error = true;
156        self
157    }
158
159    /// restore named index
160    #[must_use]
161    pub fn index<S: AsRef<OsStr>>(mut self, name: S) -> Self {
162        self.index = Some(name.as_ref().to_os_string());
163        self
164    }
165
166    /// use this many parallel jobs to restore
167    #[must_use]
168    pub fn jobs<S: AsRef<OsStr>>(mut self, num: S) -> Self {
169        self.jobs = Some(num.as_ref().to_os_string());
170        self
171    }
172
173    /// use table of contents from this file for selecting/ordering output
174    #[must_use]
175    pub fn use_list<S: AsRef<OsStr>>(mut self, filename: S) -> Self {
176        self.use_list = Some(filename.as_ref().to_os_string());
177        self
178    }
179
180    /// restore only objects in this schema
181    #[must_use]
182    pub fn schema<S: AsRef<OsStr>>(mut self, name: S) -> Self {
183        self.schema = Some(name.as_ref().to_os_string());
184        self
185    }
186
187    /// do not restore objects in this schema
188    #[must_use]
189    pub fn exclude_schema<S: AsRef<OsStr>>(mut self, name: S) -> Self {
190        self.exclude_schema = Some(name.as_ref().to_os_string());
191        self
192    }
193
194    /// skip restoration of object ownership
195    #[must_use]
196    pub fn no_owner(mut self) -> Self {
197        self.no_owner = true;
198        self
199    }
200
201    /// restore named function
202    #[must_use]
203    pub fn function<S: AsRef<OsStr>>(mut self, name: S) -> Self {
204        self.function = Some(name.as_ref().to_os_string());
205        self
206    }
207
208    /// restore only the schema, no data
209    #[must_use]
210    pub fn schema_only(mut self) -> Self {
211        self.schema_only = true;
212        self
213    }
214
215    /// superuser user name to use for disabling triggers
216    #[must_use]
217    pub fn superuser<S: AsRef<OsStr>>(mut self, name: S) -> Self {
218        self.superuser = Some(name.as_ref().to_os_string());
219        self
220    }
221
222    /// restore named relation (table, view, etc.)
223    #[must_use]
224    pub fn table<S: AsRef<OsStr>>(mut self, name: S) -> Self {
225        self.table = Some(name.as_ref().to_os_string());
226        self
227    }
228
229    /// restore named trigger
230    #[must_use]
231    pub fn trigger<S: AsRef<OsStr>>(mut self, name: S) -> Self {
232        self.trigger = Some(name.as_ref().to_os_string());
233        self
234    }
235
236    /// skip restoration of access privileges (grant/revoke)
237    #[must_use]
238    pub fn no_privileges(mut self) -> Self {
239        self.no_privileges = true;
240        self
241    }
242
243    /// restore as a single transaction
244    #[must_use]
245    pub fn single_transaction(mut self) -> Self {
246        self.single_transaction = true;
247        self
248    }
249
250    /// disable triggers during data-only restore
251    #[must_use]
252    pub fn disable_triggers(mut self) -> Self {
253        self.disable_triggers = true;
254        self
255    }
256
257    /// enable row security
258    #[must_use]
259    pub fn enable_row_security(mut self) -> Self {
260        self.enable_row_security = true;
261        self
262    }
263
264    /// use IF EXISTS when dropping objects
265    #[must_use]
266    pub fn if_exists(mut self) -> Self {
267        self.if_exists = true;
268        self
269    }
270
271    /// do not restore comments
272    #[must_use]
273    pub fn no_comments(mut self) -> Self {
274        self.no_comments = true;
275        self
276    }
277
278    /// do not restore data of tables that could not be created
279    #[must_use]
280    pub fn no_data_for_failed_tables(mut self) -> Self {
281        self.no_data_for_failed_tables = true;
282        self
283    }
284
285    /// do not restore publications
286    #[must_use]
287    pub fn no_publications(mut self) -> Self {
288        self.no_publications = true;
289        self
290    }
291
292    /// do not restore security labels
293    #[must_use]
294    pub fn no_security_labels(mut self) -> Self {
295        self.no_security_labels = true;
296        self
297    }
298
299    /// do not restore subscriptions
300    #[must_use]
301    pub fn no_subscriptions(mut self) -> Self {
302        self.no_subscriptions = true;
303        self
304    }
305
306    /// do not restore table access methods
307    #[must_use]
308    pub fn no_table_access_method(mut self) -> Self {
309        self.no_table_access_method = true;
310        self
311    }
312
313    /// do not restore tablespace assignments
314    #[must_use]
315    pub fn no_tablespaces(mut self) -> Self {
316        self.no_tablespaces = true;
317        self
318    }
319
320    /// restore named section (pre-data, data, or post-data)
321    #[must_use]
322    pub fn section<S: AsRef<OsStr>>(mut self, section: S) -> Self {
323        self.section = Some(section.as_ref().to_os_string());
324        self
325    }
326
327    /// require table and/or schema include patterns to match at least one entity each
328    #[must_use]
329    pub fn strict_names(mut self) -> Self {
330        self.strict_names = true;
331        self
332    }
333
334    /// use SET SESSION AUTHORIZATION commands instead of ALTER OWNER commands to set ownership
335    #[must_use]
336    pub fn use_set_session_authorization(mut self) -> Self {
337        self.use_set_session_authorization = true;
338        self
339    }
340
341    /// database server host or socket directory
342    #[must_use]
343    pub fn host<S: AsRef<OsStr>>(mut self, hostname: S) -> Self {
344        self.host = Some(hostname.as_ref().to_os_string());
345        self
346    }
347
348    /// database server port number
349    #[must_use]
350    pub fn port(mut self, port: u16) -> Self {
351        self.port = Some(port);
352        self
353    }
354
355    /// connect as specified database user
356    #[must_use]
357    pub fn username<S: AsRef<OsStr>>(mut self, name: S) -> Self {
358        self.username = Some(name.as_ref().to_os_string());
359        self
360    }
361
362    /// never prompt for password
363    #[must_use]
364    pub fn no_password(mut self) -> Self {
365        self.no_password = true;
366        self
367    }
368
369    /// force password prompt (should happen automatically)
370    #[must_use]
371    pub fn password(mut self) -> Self {
372        self.password = true;
373        self
374    }
375
376    /// user password
377    #[must_use]
378    pub fn pg_password<S: AsRef<OsStr>>(mut self, pg_password: S) -> Self {
379        self.pg_password = Some(pg_password.as_ref().to_os_string());
380        self
381    }
382
383    /// do SET ROLE before restore
384    #[must_use]
385    pub fn role<S: AsRef<OsStr>>(mut self, rolename: S) -> Self {
386        self.role = Some(rolename.as_ref().to_os_string());
387        self
388    }
389}
390
391impl CommandBuilder for PgRestoreBuilder {
392    /// Get the program name
393    fn get_program(&self) -> &'static OsStr {
394        "pg_restore".as_ref()
395    }
396
397    /// Location of the program binary
398    fn get_program_dir(&self) -> &Option<PathBuf> {
399        &self.program_dir
400    }
401
402    /// Get the arguments for the command
403    #[expect(clippy::too_many_lines)]
404    fn get_args(&self) -> Vec<OsString> {
405        let mut args: Vec<OsString> = Vec::new();
406
407        if let Some(name) = &self.dbname {
408            args.push("--dbname".into());
409            args.push(name.into());
410        }
411
412        if let Some(filename) = &self.file {
413            args.push("--file".into());
414            args.push(filename.into());
415        }
416
417        if let Some(format) = &self.format {
418            args.push("--format".into());
419            args.push(format.into());
420        }
421
422        if self.list {
423            args.push("--list".into());
424        }
425
426        if self.verbose {
427            args.push("--verbose".into());
428        }
429
430        if self.version {
431            args.push("--version".into());
432        }
433
434        if self.help {
435            args.push("--help".into());
436        }
437
438        if self.data_only {
439            args.push("--data-only".into());
440        }
441
442        if self.clean {
443            args.push("--clean".into());
444        }
445
446        if self.create {
447            args.push("--create".into());
448        }
449
450        if self.exit_on_error {
451            args.push("--exit-on-error".into());
452        }
453
454        if let Some(name) = &self.index {
455            args.push("--index".into());
456            args.push(name.into());
457        }
458
459        if let Some(num) = &self.jobs {
460            args.push("--jobs".into());
461            args.push(num.into());
462        }
463
464        if let Some(filename) = &self.use_list {
465            args.push("--use-list".into());
466            args.push(filename.into());
467        }
468
469        if let Some(name) = &self.schema {
470            args.push("--schema".into());
471            args.push(name.into());
472        }
473
474        if let Some(name) = &self.exclude_schema {
475            args.push("--exclude-schema".into());
476            args.push(name.into());
477        }
478
479        if self.no_owner {
480            args.push("--no-owner".into());
481        }
482
483        if let Some(name) = &self.function {
484            args.push("--function".into());
485            args.push(name.into());
486        }
487
488        if self.schema_only {
489            args.push("--schema-only".into());
490        }
491
492        if let Some(name) = &self.superuser {
493            args.push("--superuser".into());
494            args.push(name.into());
495        }
496
497        if let Some(name) = &self.table {
498            args.push("--table".into());
499            args.push(name.into());
500        }
501
502        if let Some(name) = &self.trigger {
503            args.push("--trigger".into());
504            args.push(name.into());
505        }
506
507        if self.no_privileges {
508            args.push("--no-privileges".into());
509        }
510
511        if self.single_transaction {
512            args.push("--single-transaction".into());
513        }
514
515        if self.disable_triggers {
516            args.push("--disable-triggers".into());
517        }
518
519        if self.enable_row_security {
520            args.push("--enable-row-security".into());
521        }
522
523        if self.if_exists {
524            args.push("--if-exists".into());
525        }
526
527        if self.no_comments {
528            args.push("--no-comments".into());
529        }
530
531        if self.no_data_for_failed_tables {
532            args.push("--no-data-for-failed-tables".into());
533        }
534
535        if self.no_publications {
536            args.push("--no-publications".into());
537        }
538
539        if self.no_security_labels {
540            args.push("--no-security-labels".into());
541        }
542
543        if self.no_subscriptions {
544            args.push("--no-subscriptions".into());
545        }
546
547        if self.no_table_access_method {
548            args.push("--no-table-access-method".into());
549        }
550
551        if self.no_tablespaces {
552            args.push("--no-tablespaces".into());
553        }
554
555        if let Some(section) = &self.section {
556            args.push("--section".into());
557            args.push(section.into());
558        }
559
560        if self.strict_names {
561            args.push("--strict-names".into());
562        }
563
564        if self.use_set_session_authorization {
565            args.push("--use-set-session-authorization".into());
566        }
567
568        if let Some(hostname) = &self.host {
569            args.push("--host".into());
570            args.push(hostname.into());
571        }
572
573        if let Some(port) = &self.port {
574            args.push("--port".into());
575            args.push(port.to_string().into());
576        }
577
578        if let Some(name) = &self.username {
579            args.push("--username".into());
580            args.push(name.into());
581        }
582
583        if self.no_password {
584            args.push("--no-password".into());
585        }
586
587        if self.password {
588            args.push("--password".into());
589        }
590
591        if let Some(role) = &self.role {
592            args.push("--role".into());
593            args.push(role.into());
594        }
595
596        args
597    }
598
599    /// Get the environment variables for the command
600    fn get_envs(&self) -> Vec<(OsString, OsString)> {
601        let mut envs: Vec<(OsString, OsString)> = self.envs.clone();
602
603        if let Some(password) = &self.pg_password {
604            envs.push(("PGPASSWORD".into(), password.into()));
605        }
606
607        envs
608    }
609
610    /// Set an environment variable for the command
611    fn env<S: AsRef<OsStr>>(mut self, key: S, value: S) -> Self {
612        self.envs
613            .push((key.as_ref().to_os_string(), value.as_ref().to_os_string()));
614        self
615    }
616}
617
618#[cfg(test)]
619mod tests {
620    use super::*;
621    use crate::TestSettings;
622    use crate::traits::CommandToString;
623    use test_log::test;
624
625    #[test]
626    fn test_builder_new() {
627        let command = PgRestoreBuilder::new().program_dir(".").build();
628        assert_eq!(
629            PathBuf::from(".").join("pg_restore"),
630            PathBuf::from(command.to_command_string().replace('"', ""))
631        );
632    }
633
634    #[test]
635    fn test_builder_from() {
636        let command = PgRestoreBuilder::from(&TestSettings).build();
637        #[cfg(not(target_os = "windows"))]
638        let command_prefix = r#"PGPASSWORD="password" "./pg_restore" "#;
639        #[cfg(target_os = "windows")]
640        let command_prefix = r#"".\\pg_restore" "#;
641
642        assert_eq!(
643            format!(
644                r#"{command_prefix}"--host" "localhost" "--port" "5432" "--username" "postgres""#
645            ),
646            command.to_command_string()
647        );
648    }
649
650    #[test]
651    fn test_builder() {
652        let command = PgRestoreBuilder::new()
653            .env("PGDATABASE", "database")
654            .dbname("dbname")
655            .file("file")
656            .format("format")
657            .list()
658            .verbose()
659            .version()
660            .help()
661            .data_only()
662            .clean()
663            .create()
664            .exit_on_error()
665            .index("index")
666            .jobs("jobs")
667            .use_list("use_list")
668            .schema("schema")
669            .exclude_schema("exclude_schema")
670            .no_owner()
671            .function("function")
672            .schema_only()
673            .superuser("superuser")
674            .table("table")
675            .trigger("trigger")
676            .no_privileges()
677            .single_transaction()
678            .disable_triggers()
679            .enable_row_security()
680            .if_exists()
681            .no_comments()
682            .no_data_for_failed_tables()
683            .no_publications()
684            .no_security_labels()
685            .no_subscriptions()
686            .no_table_access_method()
687            .no_tablespaces()
688            .section("section")
689            .strict_names()
690            .use_set_session_authorization()
691            .host("localhost")
692            .port(5432)
693            .username("username")
694            .no_password()
695            .password()
696            .pg_password("password")
697            .role("role")
698            .build();
699        #[cfg(not(target_os = "windows"))]
700        let command_prefix = r#"PGDATABASE="database" PGPASSWORD="password" "#;
701        #[cfg(target_os = "windows")]
702        let command_prefix = String::new();
703
704        assert_eq!(
705            format!(
706                r#"{command_prefix}"pg_restore" "--dbname" "dbname" "--file" "file" "--format" "format" "--list" "--verbose" "--version" "--help" "--data-only" "--clean" "--create" "--exit-on-error" "--index" "index" "--jobs" "jobs" "--use-list" "use_list" "--schema" "schema" "--exclude-schema" "exclude_schema" "--no-owner" "--function" "function" "--schema-only" "--superuser" "superuser" "--table" "table" "--trigger" "trigger" "--no-privileges" "--single-transaction" "--disable-triggers" "--enable-row-security" "--if-exists" "--no-comments" "--no-data-for-failed-tables" "--no-publications" "--no-security-labels" "--no-subscriptions" "--no-table-access-method" "--no-tablespaces" "--section" "section" "--strict-names" "--use-set-session-authorization" "--host" "localhost" "--port" "5432" "--username" "username" "--no-password" "--password" "--role" "role""#
707            ),
708            command.to_command_string()
709        );
710    }
711}