postgresql_commands/
pg_basebackup.rs1use crate::Settings;
2use crate::traits::CommandBuilder;
3use std::convert::AsRef;
4use std::ffi::{OsStr, OsString};
5use std::path::PathBuf;
6
7#[derive(Clone, Debug, Default)]
9pub struct PgBaseBackupBuilder {
10 program_dir: Option<PathBuf>,
11 envs: Vec<(OsString, OsString)>,
12 pgdata: Option<PathBuf>,
13 format: Option<OsString>,
14 max_rate: Option<OsString>,
15 write_recovery_conf: bool,
16 target: Option<OsString>,
17 tablespace_mapping: Option<OsString>,
18 waldir: Option<OsString>,
19 wal_method: Option<OsString>,
20 gzip: bool,
21 compress: Option<OsString>,
22 checkpoint: Option<OsString>,
23 create_slot: bool,
24 label: Option<OsString>,
25 no_clean: bool,
26 no_sync: bool,
27 progress: bool,
28 slot: Option<OsString>,
29 verbose: bool,
30 version: bool,
31 manifest_checksums: Option<OsString>,
32 manifest_force_encode: bool,
33 no_estimate_size: bool,
34 no_manifest: bool,
35 no_slot: bool,
36 no_verify_checksums: bool,
37 help: bool,
38 dbname: Option<OsString>,
39 host: Option<OsString>,
40 port: Option<u16>,
41 status_interval: Option<OsString>,
42 username: Option<OsString>,
43 no_password: bool,
44 password: bool,
45 pg_password: Option<OsString>,
46}
47
48impl PgBaseBackupBuilder {
49 #[must_use]
51 pub fn new() -> Self {
52 Self::default()
53 }
54
55 pub fn from(settings: &dyn Settings) -> Self {
57 let mut builder = Self::new()
58 .program_dir(settings.get_binary_dir())
59 .host(settings.get_host())
60 .port(settings.get_port())
61 .username(settings.get_username())
62 .pg_password(settings.get_password());
63 if let Some(socket_dir) = settings.get_socket_dir() {
64 builder = builder.host(socket_dir.to_string_lossy().to_string());
65 }
66 builder
67 }
68
69 #[must_use]
71 pub fn program_dir<P: Into<PathBuf>>(mut self, path: P) -> Self {
72 self.program_dir = Some(path.into());
73 self
74 }
75
76 #[must_use]
78 pub fn pgdata<P: Into<PathBuf>>(mut self, pgdata: P) -> Self {
79 self.pgdata = Some(pgdata.into());
80 self
81 }
82
83 #[must_use]
85 pub fn format<S: AsRef<OsStr>>(mut self, format: S) -> Self {
86 self.format = Some(format.as_ref().to_os_string());
87 self
88 }
89
90 #[must_use]
92 pub fn max_rate<S: AsRef<OsStr>>(mut self, max_rate: S) -> Self {
93 self.max_rate = Some(max_rate.as_ref().to_os_string());
94 self
95 }
96
97 #[must_use]
99 pub fn write_recovery_conf(mut self) -> Self {
100 self.write_recovery_conf = true;
101 self
102 }
103
104 #[must_use]
106 pub fn target<S: AsRef<OsStr>>(mut self, target: S) -> Self {
107 self.target = Some(target.as_ref().to_os_string());
108 self
109 }
110
111 #[must_use]
113 pub fn tablespace_mapping<S: AsRef<OsStr>>(mut self, tablespace_mapping: S) -> Self {
114 self.tablespace_mapping = Some(tablespace_mapping.as_ref().to_os_string());
115 self
116 }
117
118 #[must_use]
120 pub fn waldir<S: AsRef<OsStr>>(mut self, waldir: S) -> Self {
121 self.waldir = Some(waldir.as_ref().to_os_string());
122 self
123 }
124
125 #[must_use]
127 pub fn wal_method<S: AsRef<OsStr>>(mut self, wal_method: S) -> Self {
128 self.wal_method = Some(wal_method.as_ref().to_os_string());
129 self
130 }
131
132 #[must_use]
134 pub fn gzip(mut self) -> Self {
135 self.gzip = true;
136 self
137 }
138
139 #[must_use]
141 pub fn compress<S: AsRef<OsStr>>(mut self, compress: S) -> Self {
142 self.compress = Some(compress.as_ref().to_os_string());
143 self
144 }
145
146 #[must_use]
148 pub fn checkpoint<S: AsRef<OsStr>>(mut self, checkpoint: S) -> Self {
149 self.checkpoint = Some(checkpoint.as_ref().to_os_string());
150 self
151 }
152
153 #[must_use]
155 pub fn create_slot(mut self) -> Self {
156 self.create_slot = true;
157 self
158 }
159
160 #[must_use]
162 pub fn label<S: AsRef<OsStr>>(mut self, label: S) -> Self {
163 self.label = Some(label.as_ref().to_os_string());
164 self
165 }
166
167 #[must_use]
169 pub fn no_clean(mut self) -> Self {
170 self.no_clean = true;
171 self
172 }
173
174 #[must_use]
176 pub fn no_sync(mut self) -> Self {
177 self.no_sync = true;
178 self
179 }
180
181 #[must_use]
183 pub fn progress(mut self) -> Self {
184 self.progress = true;
185 self
186 }
187
188 #[must_use]
190 pub fn slot<S: AsRef<OsStr>>(mut self, slot: S) -> Self {
191 self.slot = Some(slot.as_ref().to_os_string());
192 self
193 }
194
195 #[must_use]
197 pub fn verbose(mut self) -> Self {
198 self.verbose = true;
199 self
200 }
201
202 #[must_use]
204 pub fn version(mut self) -> Self {
205 self.version = true;
206 self
207 }
208
209 #[must_use]
211 pub fn manifest_checksums<S: AsRef<OsStr>>(mut self, manifest_checksums: S) -> Self {
212 self.manifest_checksums = Some(manifest_checksums.as_ref().to_os_string());
213 self
214 }
215
216 #[must_use]
218 pub fn manifest_force_encode(mut self) -> Self {
219 self.manifest_force_encode = true;
220 self
221 }
222
223 #[must_use]
225 pub fn no_estimate_size(mut self) -> Self {
226 self.no_estimate_size = true;
227 self
228 }
229
230 #[must_use]
232 pub fn no_manifest(mut self) -> Self {
233 self.no_manifest = true;
234 self
235 }
236
237 #[must_use]
239 pub fn no_slot(mut self) -> Self {
240 self.no_slot = true;
241 self
242 }
243
244 #[must_use]
246 pub fn no_verify_checksums(mut self) -> Self {
247 self.no_verify_checksums = true;
248 self
249 }
250
251 #[must_use]
253 pub fn help(mut self) -> Self {
254 self.help = true;
255 self
256 }
257
258 #[must_use]
260 pub fn dbname<S: AsRef<OsStr>>(mut self, dbname: S) -> Self {
261 self.dbname = Some(dbname.as_ref().to_os_string());
262 self
263 }
264
265 #[must_use]
267 pub fn host<S: AsRef<OsStr>>(mut self, host: S) -> Self {
268 self.host = Some(host.as_ref().to_os_string());
269 self
270 }
271
272 #[must_use]
274 pub fn port(mut self, port: u16) -> Self {
275 self.port = Some(port);
276 self
277 }
278
279 #[must_use]
281 pub fn status_interval<S: AsRef<OsStr>>(mut self, status_interval: S) -> Self {
282 self.status_interval = Some(status_interval.as_ref().to_os_string());
283 self
284 }
285
286 #[must_use]
288 pub fn username<S: AsRef<OsStr>>(mut self, username: S) -> Self {
289 self.username = Some(username.as_ref().to_os_string());
290 self
291 }
292
293 #[must_use]
295 pub fn no_password(mut self) -> Self {
296 self.no_password = true;
297 self
298 }
299
300 #[must_use]
302 pub fn password(mut self) -> Self {
303 self.password = true;
304 self
305 }
306
307 #[must_use]
309 pub fn pg_password<S: AsRef<OsStr>>(mut self, pg_password: S) -> Self {
310 self.pg_password = Some(pg_password.as_ref().to_os_string());
311 self
312 }
313}
314
315impl CommandBuilder for PgBaseBackupBuilder {
316 fn get_program(&self) -> &'static OsStr {
318 "pg_basebackup".as_ref()
319 }
320
321 fn get_program_dir(&self) -> &Option<PathBuf> {
323 &self.program_dir
324 }
325
326 #[expect(clippy::too_many_lines)]
328 fn get_args(&self) -> Vec<OsString> {
329 let mut args: Vec<OsString> = Vec::new();
330
331 if let Some(pgdata) = &self.pgdata {
332 args.push("--pgdata".into());
333 args.push(pgdata.into());
334 }
335
336 if let Some(format) = &self.format {
337 args.push("--format".into());
338 args.push(format.into());
339 }
340
341 if let Some(max_rate) = &self.max_rate {
342 args.push("--max-rate".into());
343 args.push(max_rate.into());
344 }
345
346 if self.write_recovery_conf {
347 args.push("--write-recovery-conf".into());
348 }
349
350 if let Some(target) = &self.target {
351 args.push("--target".into());
352 args.push(target.into());
353 }
354
355 if let Some(tablespace_mapping) = &self.tablespace_mapping {
356 args.push("--tablespace-mapping".into());
357 args.push(tablespace_mapping.into());
358 }
359
360 if let Some(waldir) = &self.waldir {
361 args.push("--waldir".into());
362 args.push(waldir.into());
363 }
364
365 if let Some(wal_method) = &self.wal_method {
366 args.push("--wal-method".into());
367 args.push(wal_method.into());
368 }
369
370 if self.gzip {
371 args.push("--gzip".into());
372 }
373
374 if let Some(compress) = &self.compress {
375 args.push("--compress".into());
376 args.push(compress.into());
377 }
378
379 if let Some(checkpoint) = &self.checkpoint {
380 args.push("--checkpoint".into());
381 args.push(checkpoint.into());
382 }
383
384 if self.create_slot {
385 args.push("--create-slot".into());
386 }
387
388 if let Some(label) = &self.label {
389 args.push("--label".into());
390 args.push(label.into());
391 }
392
393 if self.no_clean {
394 args.push("--no-clean".into());
395 }
396
397 if self.no_sync {
398 args.push("--no-sync".into());
399 }
400
401 if self.progress {
402 args.push("--progress".into());
403 }
404
405 if let Some(slot) = &self.slot {
406 args.push("--slot".into());
407 args.push(slot.into());
408 }
409
410 if self.verbose {
411 args.push("--verbose".into());
412 }
413
414 if self.version {
415 args.push("--version".into());
416 }
417
418 if let Some(manifest_checksums) = &self.manifest_checksums {
419 args.push("--manifest-checksums".into());
420 args.push(manifest_checksums.into());
421 }
422
423 if self.manifest_force_encode {
424 args.push("--manifest-force-encode".into());
425 }
426
427 if self.no_estimate_size {
428 args.push("--no-estimate-size".into());
429 }
430
431 if self.no_manifest {
432 args.push("--no-manifest".into());
433 }
434
435 if self.no_slot {
436 args.push("--no-slot".into());
437 }
438
439 if self.no_verify_checksums {
440 args.push("--no-verify-checksums".into());
441 }
442
443 if self.help {
444 args.push("--help".into());
445 }
446
447 if let Some(dbname) = &self.dbname {
448 args.push("--dbname".into());
449 args.push(dbname.into());
450 }
451
452 if let Some(host) = &self.host {
453 args.push("--host".into());
454 args.push(host.into());
455 }
456
457 if let Some(port) = &self.port {
458 args.push("--port".into());
459 args.push(port.to_string().into());
460 }
461
462 if let Some(status_interval) = &self.status_interval {
463 args.push("--status-interval".into());
464 args.push(status_interval.into());
465 }
466
467 if let Some(username) = &self.username {
468 args.push("--username".into());
469 args.push(username.into());
470 }
471
472 if self.no_password {
473 args.push("--no-password".into());
474 }
475
476 if self.password {
477 args.push("--password".into());
478 }
479
480 args
481 }
482
483 fn get_envs(&self) -> Vec<(OsString, OsString)> {
485 let mut envs: Vec<(OsString, OsString)> = self.envs.clone();
486
487 if let Some(password) = &self.pg_password {
488 envs.push(("PGPASSWORD".into(), password.into()));
489 }
490
491 envs
492 }
493
494 fn env<S: AsRef<OsStr>>(mut self, key: S, value: S) -> Self {
496 self.envs
497 .push((key.as_ref().to_os_string(), value.as_ref().to_os_string()));
498 self
499 }
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505 use crate::TestSettings;
506 use crate::TestSocketSettings;
507 use crate::traits::CommandToString;
508 use test_log::test;
509
510 #[test]
511 fn test_builder_new() {
512 let command = PgBaseBackupBuilder::new().program_dir(".").build();
513 assert_eq!(
514 PathBuf::from(".").join("pg_basebackup"),
515 PathBuf::from(command.to_command_string().replace('"', ""))
516 );
517 }
518
519 #[test]
520 fn test_builder_from() {
521 let command = PgBaseBackupBuilder::from(&TestSettings).build();
522 #[cfg(not(target_os = "windows"))]
523 let command_prefix = r#"PGPASSWORD="password" "./pg_basebackup" "#;
524 #[cfg(target_os = "windows")]
525 let command_prefix = r#"".\\pg_basebackup" "#;
526
527 assert_eq!(
528 format!(
529 r#"{command_prefix}"--host" "localhost" "--port" "5432" "--username" "postgres""#
530 ),
531 command.to_command_string()
532 );
533 }
534
535 #[test]
536 fn test_builder_from_socket() {
537 let command = PgBaseBackupBuilder::from(&TestSocketSettings).build();
538 #[cfg(not(target_os = "windows"))]
539 let command_prefix = r#"PGPASSWORD="password" "./pg_basebackup" "#;
540 #[cfg(target_os = "windows")]
541 let command_prefix = r#"".\\pg_basebackup" "#;
542
543 assert_eq!(
544 format!(
545 r#"{command_prefix}"--host" "/tmp/pg_socket" "--port" "5432" "--username" "postgres""#
546 ),
547 command.to_command_string()
548 );
549 }
550
551 #[test]
552 fn test_builder() {
553 let command = PgBaseBackupBuilder::new()
554 .env("PGDATABASE", "database")
555 .pgdata("pgdata")
556 .format("plain")
557 .max_rate("100M")
558 .write_recovery_conf()
559 .target("localhost")
560 .tablespace_mapping("tablespace_mapping")
561 .waldir("waldir")
562 .wal_method("stream")
563 .gzip()
564 .compress("client")
565 .checkpoint("fast")
566 .create_slot()
567 .label("my_backup")
568 .no_clean()
569 .no_sync()
570 .progress()
571 .slot("my_slot")
572 .verbose()
573 .version()
574 .manifest_checksums("sha256")
575 .manifest_force_encode()
576 .no_estimate_size()
577 .no_manifest()
578 .no_slot()
579 .no_verify_checksums()
580 .help()
581 .dbname("postgres")
582 .host("localhost")
583 .port(5432)
584 .status_interval("10")
585 .username("postgres")
586 .no_password()
587 .password()
588 .pg_password("password")
589 .build();
590 #[cfg(not(target_os = "windows"))]
591 let command_prefix = r#"PGDATABASE="database" PGPASSWORD="password" "#;
592 #[cfg(target_os = "windows")]
593 let command_prefix = String::new();
594
595 assert_eq!(
596 format!(
597 r#"{command_prefix}"pg_basebackup" "--pgdata" "pgdata" "--format" "plain" "--max-rate" "100M" "--write-recovery-conf" "--target" "localhost" "--tablespace-mapping" "tablespace_mapping" "--waldir" "waldir" "--wal-method" "stream" "--gzip" "--compress" "client" "--checkpoint" "fast" "--create-slot" "--label" "my_backup" "--no-clean" "--no-sync" "--progress" "--slot" "my_slot" "--verbose" "--version" "--manifest-checksums" "sha256" "--manifest-force-encode" "--no-estimate-size" "--no-manifest" "--no-slot" "--no-verify-checksums" "--help" "--dbname" "postgres" "--host" "localhost" "--port" "5432" "--status-interval" "10" "--username" "postgres" "--no-password" "--password""#
598 ),
599 command.to_command_string()
600 );
601 }
602}