From 567ba5f5416d7a6f619cd6d71642c0d1effc7e2e Mon Sep 17 00:00:00 2001 From: brianheineman Date: Tue, 18 Jun 2024 09:30:25 -0600 Subject: [PATCH] fix: correct errors when PGDATABASE envar is set --- Cargo.lock | 85 +++++++++++++++---- postgresql_commands/src/clusterdb.rs | 37 ++++++-- postgresql_commands/src/createdb.rs | 51 +++++++++-- postgresql_commands/src/createuser.rs | 52 ++++++++++-- postgresql_commands/src/dropdb.rs | 36 ++++++-- postgresql_commands/src/dropuser.rs | 33 +++++-- postgresql_commands/src/ecpg.rs | 38 ++++++++- postgresql_commands/src/error.rs | 4 +- postgresql_commands/src/initdb.rs | 63 ++++++++++++-- postgresql_commands/src/lib.rs | 3 +- postgresql_commands/src/oid2name.rs | 39 ++++++++- postgresql_commands/src/pg_amcheck.rs | 59 +++++++++++-- postgresql_commands/src/pg_archivecleanup.rs | 32 ++++++- postgresql_commands/src/pg_basebackup.rs | 57 +++++++++++-- postgresql_commands/src/pg_checksums.rs | 36 +++++++- postgresql_commands/src/pg_config.rs | 58 +++++++++++-- postgresql_commands/src/pg_controldata.rs | 27 +++++- postgresql_commands/src/pg_ctl.rs | 48 +++++++++-- postgresql_commands/src/pg_dump.rs | 83 +++++++++++++++++- postgresql_commands/src/pg_dumpall.rs | 70 +++++++++++++-- postgresql_commands/src/pg_isready.rs | 32 ++++++- postgresql_commands/src/pg_receivewal.rs | 43 ++++++++-- postgresql_commands/src/pg_recvlogical.rs | 48 +++++++++-- postgresql_commands/src/pg_resetwal.rs | 39 ++++++++- postgresql_commands/src/pg_restore.rs | 68 +++++++++++++-- postgresql_commands/src/pg_rewind.rs | 41 +++++++-- postgresql_commands/src/pg_test_fsync.rs | 27 +++++- postgresql_commands/src/pg_test_timing.rs | 33 +++++-- postgresql_commands/src/pg_upgrade.rs | 46 +++++++++- postgresql_commands/src/pg_verifybackup.rs | 33 ++++++- postgresql_commands/src/pg_waldump.rs | 43 +++++++++- postgresql_commands/src/pgbench.rs | 78 +++++++++++++++-- postgresql_commands/src/postgres.rs | 58 ++++++++++++- postgresql_commands/src/psql.rs | 62 ++++++++++++-- postgresql_commands/src/reindexdb.rs | 43 ++++++++-- postgresql_commands/src/traits.rs | 42 +++++++-- postgresql_commands/src/vacuumdb.rs | 59 +++++++++++-- postgresql_commands/src/vacuumlo.rs | 31 ++++++- postgresql_embedded/src/postgresql.rs | 8 +- .../tests/environment_variables.rs | 25 ++++++ 40 files changed, 1574 insertions(+), 196 deletions(-) create mode 100644 postgresql_embedded/tests/environment_variables.rs diff --git a/Cargo.lock b/Cargo.lock index 945300a..86fc9f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -909,9 +909,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.3" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "human_bytes" @@ -940,15 +940,16 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.26.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", "http", "hyper", "hyper-util", "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", @@ -1318,9 +1319,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] @@ -1815,6 +1816,53 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.36" @@ -1947,9 +1995,9 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ "base64 0.22.1", "bytes", @@ -1970,6 +2018,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "quinn", "rustls", "rustls-native-certs", "rustls-pemfile", @@ -2094,6 +2143,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.38.34" @@ -2109,11 +2164,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.4" +version = "0.23.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" dependencies = [ - "log", + "once_cell", "ring", "rustls-pki-types", "rustls-webpki", @@ -2624,9 +2679,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" [[package]] name = "synstructure" @@ -2832,9 +2887,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ "rustls", "rustls-pki-types", diff --git a/postgresql_commands/src/clusterdb.rs b/postgresql_commands/src/clusterdb.rs index 029ffb6..2a25b47 100644 --- a/postgresql_commands/src/clusterdb.rs +++ b/postgresql_commands/src/clusterdb.rs @@ -6,8 +6,10 @@ use std::path::PathBuf; /// `clusterdb` clusters all previously clustered tables in a database. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct ClusterDbBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, all: bool, dbname: Option, echo: bool, @@ -26,12 +28,13 @@ pub struct ClusterDbBuilder { } impl ClusterDbBuilder { - /// Create a new [ClusterDbBuilder] + /// Create a new [`ClusterDbBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [ClusterDbBuilder] from [Settings] + /// Create a new [`ClusterDbBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -42,96 +45,112 @@ impl ClusterDbBuilder { } /// Location of the program binary + #[must_use] fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// Cluster all databases + #[must_use] pub fn all(mut self) -> Self { self.all = true; self } /// Database to cluster + #[must_use] pub fn dbname>(mut self, dbname: S) -> Self { self.dbname = Some(dbname.as_ref().to_os_string()); self } /// Show the commands being sent to the server + #[must_use] pub fn echo(mut self) -> Self { self.echo = true; self } /// Don't write any messages + #[must_use] pub fn quiet(mut self) -> Self { self.quiet = true; self } /// Cluster specific table(s) only + #[must_use] pub fn table>(mut self, table: S) -> Self { self.table = Some(table.as_ref().to_os_string()); self } /// Write a lot of output + #[must_use] pub fn verbose(mut self) -> Self { self.verbose = true; self } /// Output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// Show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// Database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// Database server port + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// User name to connect as + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// Never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// Force password prompt + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self } /// Alternate maintenance database + #[must_use] pub fn maintenance_db>(mut self, db: S) -> Self { self.maintenance_db = Some(db.as_ref().to_os_string()); self @@ -189,7 +208,7 @@ impl CommandBuilder for ClusterDbBuilder { if let Some(host) = &self.host { args.push("--host".into()); - args.push(host.into()) + args.push(host.into()); } if let Some(port) = &self.port { @@ -220,7 +239,7 @@ impl CommandBuilder for ClusterDbBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -228,6 +247,13 @@ impl CommandBuilder for ClusterDbBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -258,6 +284,7 @@ mod tests { #[test] fn test_builder() { let command = ClusterDbBuilder::new() + .env("PGDATABASE", "database") .all() .dbname("dbname") .echo() @@ -276,7 +303,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "clusterdb" "--all" "--dbname" "dbname" "--echo" "--quiet" "--table" "table" "--verbose" "--version" "--help" "--host" "localhost" "--port" "5432" "--username" "postgres" "--no-password" "--password" "--maintenance-db" "postgres""#, + r#"PGDATABASE="database" PGPASSWORD="password" "clusterdb" "--all" "--dbname" "dbname" "--echo" "--quiet" "--table" "table" "--verbose" "--version" "--help" "--host" "localhost" "--port" "5432" "--username" "postgres" "--no-password" "--password" "--maintenance-db" "postgres""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/createdb.rs b/postgresql_commands/src/createdb.rs index d4e8544..a810374 100644 --- a/postgresql_commands/src/createdb.rs +++ b/postgresql_commands/src/createdb.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `createdb` creates a PostgreSQL database. +/// `createdb` creates a `PostgreSQL` database. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct CreateDbBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, tablespace: Option, echo: bool, encoding: Option, @@ -34,12 +36,13 @@ pub struct CreateDbBuilder { } impl CreateDbBuilder { - /// Create a new [CreateDbBuilder] + /// Create a new [`CreateDbBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [CreateDbBuilder] from [Settings] + /// Create a new [`CreateDbBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -50,144 +53,168 @@ impl CreateDbBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// Default tablespace for the database + #[must_use] pub fn tablespace>(mut self, tablespace: S) -> Self { self.tablespace = Some(tablespace.as_ref().to_os_string()); self } /// Show the commands being sent to the server + #[must_use] pub fn echo(mut self) -> Self { self.echo = true; self } /// Encoding for the database + #[must_use] pub fn encoding>(mut self, encoding: S) -> Self { self.encoding = Some(encoding.as_ref().to_os_string()); self } /// Locale settings for the database + #[must_use] pub fn locale>(mut self, locale: S) -> Self { self.locale = Some(locale.as_ref().to_os_string()); self } - /// LC_COLLATE setting for the database + /// `LC_COLLATE` setting for the database + #[must_use] pub fn lc_collate>(mut self, lc_collate: S) -> Self { self.lc_collate = Some(lc_collate.as_ref().to_os_string()); self } - /// LC_CTYPE setting for the database + /// `LC_CTYPE` setting for the database + #[must_use] pub fn lc_ctype>(mut self, lc_ctype: S) -> Self { self.lc_ctype = Some(lc_ctype.as_ref().to_os_string()); self } /// ICU locale setting for the database + #[must_use] pub fn icu_locale>(mut self, icu_locale: S) -> Self { self.icu_locale = Some(icu_locale.as_ref().to_os_string()); self } /// ICU rules setting for the database + #[must_use] pub fn icu_rules>(mut self, icu_rules: S) -> Self { self.icu_rules = Some(icu_rules.as_ref().to_os_string()); self } /// Locale provider for the database's default collation + #[must_use] pub fn locale_provider>(mut self, locale_provider: S) -> Self { self.locale_provider = Some(locale_provider.as_ref().to_os_string()); self } /// Database user to own the new database + #[must_use] pub fn owner>(mut self, owner: S) -> Self { self.owner = Some(owner.as_ref().to_os_string()); self } - /// Database creation strategy wal_log or file_copy + /// Database creation strategy `wal_log` or `file_copy` + #[must_use] pub fn strategy>(mut self, strategy: S) -> Self { self.strategy = Some(strategy.as_ref().to_os_string()); self } /// Template database to copy + #[must_use] pub fn template>(mut self, template: S) -> Self { self.template = Some(template.as_ref().to_os_string()); self } /// Output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// Show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// Database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// Database server port + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// User name to connect as + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// Never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// Force password prompt + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self } /// Alternate maintenance database + #[must_use] pub fn maintenance_db>(mut self, db: S) -> Self { self.maintenance_db = Some(db.as_ref().to_os_string()); self } /// Database name + #[must_use] pub fn dbname>(mut self, dbname: S) -> Self { self.dbname = Some(dbname.as_ref().to_os_string()); self } /// Database description + #[must_use] pub fn description>(mut self, description: S) -> Self { self.description = Some(description.as_ref().to_os_string()); self @@ -317,7 +344,7 @@ impl CommandBuilder for CreateDbBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -325,6 +352,13 @@ impl CommandBuilder for CreateDbBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -355,6 +389,7 @@ mod tests { #[test] fn test_builder() { let command = CreateDbBuilder::new() + .env("PGDATABASE", "database") .tablespace("pg_default") .echo() .encoding("UTF8") @@ -381,7 +416,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "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""#, + r#"PGDATABASE="database" PGPASSWORD="password" "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""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/createuser.rs b/postgresql_commands/src/createuser.rs index 455e742..770e8ab 100644 --- a/postgresql_commands/src/createuser.rs +++ b/postgresql_commands/src/createuser.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `createuser` creates a new PostgreSQL role. +/// `createuser` creates a new `PostgreSQL` role. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct CreateUserBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, with_admin: Option, connection_limit: Option, createdb: bool, @@ -41,12 +43,13 @@ pub struct CreateUserBuilder { } impl CreateUserBuilder { - /// Create a new [CreateUserBuilder] + /// Create a new [`CreateUserBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [CreateUserBuilder] from [Settings] + /// Create a new [`CreateUserBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -57,186 +60,217 @@ impl CreateUserBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// ROLE will be a member of new role with admin option + #[must_use] pub fn with_admin>(mut self, role: S) -> Self { self.with_admin = Some(role.as_ref().to_os_string()); self } /// Connection limit for role (default: no limit) + #[must_use] pub fn connection_limit(mut self, limit: u32) -> Self { self.connection_limit = Some(limit); self } /// Role can create new databases + #[must_use] pub fn createdb(mut self) -> Self { self.createdb = true; self } /// Role cannot create databases (default) + #[must_use] pub fn no_createdb(mut self) -> Self { self.no_createdb = true; self } /// Show the commands being sent to the server + #[must_use] pub fn echo(mut self) -> Self { self.echo = true; self } /// New role will be a member of ROLE + #[must_use] pub fn member_of>(mut self, role: S) -> Self { self.member_of = Some(role.as_ref().to_os_string()); self } /// Role inherits privileges of roles it is a member of (default) + #[must_use] pub fn inherit(mut self) -> Self { self.inherit = true; self } /// Role does not inherit privileges + #[must_use] pub fn no_inherit(mut self) -> Self { self.no_inherit = true; self } /// Role can login (default) + #[must_use] pub fn login(mut self) -> Self { self.login = true; self } /// Role cannot login + #[must_use] pub fn no_login(mut self) -> Self { self.no_login = true; self } /// ROLE will be a member of new role + #[must_use] pub fn with_member>(mut self, role: S) -> Self { self.with_member = Some(role.as_ref().to_os_string()); self } /// Assign a password to new role + #[must_use] pub fn pwprompt(mut self) -> Self { self.pwprompt = true; self } /// Role can create new roles + #[must_use] pub fn createrole(mut self) -> Self { self.createrole = true; self } /// Role cannot create roles (default) + #[must_use] pub fn no_createrole(mut self) -> Self { self.no_createrole = true; self } /// Role will be superuser + #[must_use] pub fn superuser(mut self) -> Self { self.superuser = true; self } /// Role will not be superuser (default) + #[must_use] pub fn no_superuser(mut self) -> Self { self.no_superuser = true; self } /// Password expiration date and time for role + #[must_use] pub fn valid_until>(mut self, timestamp: S) -> Self { self.valid_until = Some(timestamp.as_ref().to_os_string()); self } /// Output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// Prompt for missing role name and attributes rather than using defaults + #[must_use] pub fn interactive(mut self) -> Self { self.interactive = true; self } /// Role can bypass row-level security (RLS) policy + #[must_use] pub fn bypassrls(mut self) -> Self { self.bypassrls = true; self } /// Role cannot bypass row-level security (RLS) policy (default) + #[must_use] pub fn no_bypassrls(mut self) -> Self { self.no_bypassrls = true; self } /// Role can initiate replication + #[must_use] pub fn replication(mut self) -> Self { self.replication = true; self } /// Role cannot initiate replication (default) + #[must_use] pub fn no_replication(mut self) -> Self { self.no_replication = true; self } /// Show this help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// Database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// Database server port + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// User name to connect as (not the one to create) + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// Never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// Force password prompt + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self @@ -387,7 +421,7 @@ impl CommandBuilder for CreateUserBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -395,6 +429,13 @@ impl CommandBuilder for CreateUserBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -425,6 +466,7 @@ mod tests { #[test] fn test_builder() { let command = CreateUserBuilder::new() + .env("PGDATABASE", "database") .with_admin("admin") .connection_limit(10) .createdb() @@ -458,7 +500,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "createuser" "--with-admin" "admin" "--connection-limit" "10" "--createdb" "--no-createdb" "--echo" "--member-of" "member" "--inherit" "--no-inherit" "--login" "--no-login" "--with-member" "member" "--pwprompt" "--createrole" "--no-createrole" "--superuser" "--no-superuser" "--valid-until" "2021-12-31" "--version" "--interactive" "--bypassrls" "--no-bypassrls" "--replication" "--no-replication" "--help" "--host" "localhost" "--port" "5432" "--username" "username" "--no-password" "--password""#, + r#"PGDATABASE="database" PGPASSWORD="password" "createuser" "--with-admin" "admin" "--connection-limit" "10" "--createdb" "--no-createdb" "--echo" "--member-of" "member" "--inherit" "--no-inherit" "--login" "--no-login" "--with-member" "member" "--pwprompt" "--createrole" "--no-createrole" "--superuser" "--no-superuser" "--valid-until" "2021-12-31" "--version" "--interactive" "--bypassrls" "--no-bypassrls" "--replication" "--no-replication" "--help" "--host" "localhost" "--port" "5432" "--username" "username" "--no-password" "--password""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/dropdb.rs b/postgresql_commands/src/dropdb.rs index 0ae7915..9daf690 100644 --- a/postgresql_commands/src/dropdb.rs +++ b/postgresql_commands/src/dropdb.rs @@ -3,10 +3,12 @@ use crate::Settings; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `dropdb` removes a PostgreSQL database. +/// `dropdb` removes a `PostgreSQL` database. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct DropDbBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, echo: bool, force: bool, interactive: bool, @@ -24,12 +26,13 @@ pub struct DropDbBuilder { } impl DropDbBuilder { - /// Create a new [DropDbBuilder] + /// Create a new [`DropDbBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [DropDbBuilder] from [Settings] + /// Create a new [`DropDbBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -40,90 +43,105 @@ impl DropDbBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// Show the commands being sent to the server + #[must_use] pub fn echo(mut self) -> Self { self.echo = true; self } /// Try to terminate other connections before dropping + #[must_use] pub fn force(mut self) -> Self { self.force = true; self } /// Prompt before deleting anything + #[must_use] pub fn interactive(mut self) -> Self { self.interactive = true; self } /// Output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// Don't report error if database doesn't exist + #[must_use] pub fn if_exists(mut self) -> Self { self.if_exists = true; self } /// Show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// Database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// Database server port + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// User name to connect as + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// Never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// Force password prompt + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self } /// Alternate maintenance database + #[must_use] pub fn maintenance_db>(mut self, db: S) -> Self { self.maintenance_db = Some(db.as_ref().to_os_string()); self } /// Database name + #[must_use] pub fn dbname>(mut self, dbname: S) -> Self { self.dbname = Some(dbname.as_ref().to_os_string()); self @@ -206,7 +224,7 @@ impl CommandBuilder for DropDbBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -214,6 +232,13 @@ impl CommandBuilder for DropDbBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -244,6 +269,7 @@ mod tests { #[test] fn test_builder() { let command = DropDbBuilder::new() + .env("PGDATABASE", "database") .echo() .force() .interactive() @@ -261,7 +287,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "dropdb" "--echo" "--force" "--interactive" "--version" "--if-exists" "--help" "--host" "localhost" "--port" "5432" "--username" "postgres" "--no-password" "--password" "--maintenance-db" "postgres" "dbname""#, + r#"PGDATABASE="database" PGPASSWORD="password" "dropdb" "--echo" "--force" "--interactive" "--version" "--if-exists" "--help" "--host" "localhost" "--port" "5432" "--username" "postgres" "--no-password" "--password" "--maintenance-db" "postgres" "dbname""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/dropuser.rs b/postgresql_commands/src/dropuser.rs index 6f7da00..aa1f7c5 100644 --- a/postgresql_commands/src/dropuser.rs +++ b/postgresql_commands/src/dropuser.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `dropuser` removes a PostgreSQL role. +/// `dropuser` removes a `PostgreSQL` role. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct DropUserBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, echo: bool, interactive: bool, version: bool, @@ -22,12 +24,13 @@ pub struct DropUserBuilder { } impl DropUserBuilder { - /// Create a new [DropUserBuilder] + /// Create a new [`DropUserBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [DropUserBuilder] from [Settings] + /// Create a new [`DropUserBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -38,72 +41,84 @@ impl DropUserBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// Show the commands being sent to the server + #[must_use] pub fn echo(mut self) -> Self { self.echo = true; self } /// Prompt before deleting anything, and prompt for role name if not specified + #[must_use] pub fn interactive(mut self) -> Self { self.interactive = true; self } /// Output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// Don't report error if user doesn't exist + #[must_use] pub fn if_exists(mut self) -> Self { self.if_exists = true; self } /// Show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// Database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// Database server port + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// User name to connect as (not the one to drop) + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// Never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// Force password prompt + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self @@ -173,7 +188,7 @@ impl CommandBuilder for DropUserBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -181,6 +196,13 @@ impl CommandBuilder for DropUserBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -211,6 +233,7 @@ mod tests { #[test] fn test_builder() { let command = DropUserBuilder::new() + .env("PGDATABASE", "database") .echo() .interactive() .version() @@ -225,7 +248,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "dropuser" "--echo" "--interactive" "--version" "--if-exists" "--help" "--host" "localhost" "--port" "5432" "--username" "postgres" "--no-password" "--password""#, + r#"PGDATABASE="database" PGPASSWORD="password" "dropuser" "--echo" "--interactive" "--version" "--if-exists" "--help" "--host" "localhost" "--port" "5432" "--username" "postgres" "--no-password" "--password""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/ecpg.rs b/postgresql_commands/src/ecpg.rs index 6ab88b9..a823b7e 100644 --- a/postgresql_commands/src/ecpg.rs +++ b/postgresql_commands/src/ecpg.rs @@ -4,10 +4,13 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `ecpg` is the PostgreSQL embedded SQL preprocessor for C programs. +/// `ecpg` is the `PostgreSQL` embedded SQL preprocessor for C programs. #[derive(Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] +#[allow(clippy::struct_excessive_bools)] pub struct EcpgBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, c: bool, compatibility_mode: Option, symbol: Option, @@ -23,89 +26,103 @@ pub struct EcpgBuilder { } impl EcpgBuilder { - /// Create a new [EcpgBuilder] + /// Create a new [`EcpgBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [EcpgBuilder] from [Settings] + /// Create a new [`EcpgBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new().program_dir(settings.get_binary_dir()) } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// Automatically generate C code from embedded SQL code + #[must_use] pub fn c(mut self) -> Self { self.c = true; self } /// Set compatibility mode + #[must_use] pub fn compatibility_mode>(mut self, compatibility_mode: S) -> Self { self.compatibility_mode = Some(compatibility_mode.as_ref().to_os_string()); self } /// Define SYMBOL + #[must_use] pub fn symbol>(mut self, symbol: S) -> Self { self.symbol = Some(symbol.as_ref().to_os_string()); self } /// Parse a header file + #[must_use] pub fn header_file(mut self) -> Self { self.header_file = true; self.c() } /// Parse system include files as well + #[must_use] pub fn system_include_files(mut self) -> Self { self.system_include_files = true; self } /// Search DIRECTORY for include files + #[must_use] pub fn directory>(mut self, directory: S) -> Self { self.directory = Some(directory.as_ref().to_os_string()); self } /// Write result to OUTFILE + #[must_use] pub fn outfile>(mut self, outfile: S) -> Self { self.outfile = Some(outfile.as_ref().to_os_string()); self } /// Specify run-time behavior + #[must_use] pub fn runtime_behavior>(mut self, runtime_behavior: S) -> Self { self.runtime_behavior = Some(runtime_behavior.as_ref().to_os_string()); self } /// Run in regression testing mode + #[must_use] pub fn regression(mut self) -> Self { self.regression = true; self } /// Turn on autocommit of transactions + #[must_use] pub fn autocommit(mut self) -> Self { self.autocommit = true; self } /// Output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// Show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self @@ -182,6 +199,18 @@ impl CommandBuilder for EcpgBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -209,6 +238,7 @@ mod tests { #[test] fn test_builder() { let command = EcpgBuilder::new() + .env("PGDATABASE", "database") .c() .compatibility_mode("mode") .symbol("symbol") @@ -224,7 +254,7 @@ mod tests { .build(); assert_eq!( - r#""ecpg" "-c" "-C" "mode" "-D" "symbol" "-h" "-i" "-I" "directory" "-o" "outfile" "-r" "behavior" "--regression" "-t" "--version" "--help""#, + r#"PGDATABASE="database" "ecpg" "-c" "-C" "mode" "-D" "symbol" "-h" "-i" "-I" "directory" "-o" "outfile" "-r" "behavior" "--regression" "-t" "--version" "--help""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/error.rs b/postgresql_commands/src/error.rs index 6053ba9..0adf8fd 100644 --- a/postgresql_commands/src/error.rs +++ b/postgresql_commands/src/error.rs @@ -1,7 +1,7 @@ -/// PostgreSQL command result type +/// `PostgreSQL` command result type pub type Result = core::result::Result; -/// PostgreSQL command errors +/// `PostgreSQL` command errors #[derive(Debug, thiserror::Error)] pub enum Error { /// Error when a command fails diff --git a/postgresql_commands/src/initdb.rs b/postgresql_commands/src/initdb.rs index f2666af..8bf3685 100644 --- a/postgresql_commands/src/initdb.rs +++ b/postgresql_commands/src/initdb.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `initdb` initializes a PostgreSQL database cluster. +/// `initdb` initializes a `PostgreSQL` database cluster. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct InitDbBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, auth: Option, auth_host: Option, auth_local: Option, @@ -46,12 +48,13 @@ pub struct InitDbBuilder { } impl InitDbBuilder { - /// Create a new [InitDbBuilder] + /// Create a new [`InitDbBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [InitDbBuilder] from [Settings] + /// Create a new [`InitDbBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -59,216 +62,252 @@ impl InitDbBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// Set the default authentication method for local connections + #[must_use] pub fn auth>(mut self, auth: S) -> Self { self.auth = Some(auth.as_ref().to_os_string()); self } /// Set the default authentication method for local TCP/IP connections + #[must_use] pub fn auth_host>(mut self, auth_host: S) -> Self { self.auth_host = Some(auth_host.as_ref().to_os_string()); self } /// Set the default authentication method for local-socket connections + #[must_use] pub fn auth_local>(mut self, auth_local: S) -> Self { self.auth_local = Some(auth_local.as_ref().to_os_string()); self } /// Set the location for this database cluster + #[must_use] pub fn pgdata>(mut self, pgdata: P) -> Self { self.pgdata = Some(pgdata.into()); self } /// Set the default encoding for new databases + #[must_use] pub fn encoding>(mut self, encoding: S) -> Self { self.encoding = Some(encoding.as_ref().to_os_string()); self } /// Allow group read/execute on data directory + #[must_use] pub fn allow_group_access(mut self) -> Self { self.allow_group_access = true; self } /// Set the ICU locale ID for new databases + #[must_use] pub fn icu_locale>(mut self, icu_locale: S) -> Self { self.icu_locale = Some(icu_locale.as_ref().to_os_string()); self } /// Set additional ICU collation rules for new databases + #[must_use] pub fn icu_rules>(mut self, icu_rules: S) -> Self { self.icu_rules = Some(icu_rules.as_ref().to_os_string()); self } /// Use data page checksums + #[must_use] pub fn data_checksums(mut self) -> Self { self.data_checksums = true; self } /// Set the default locale for new databases + #[must_use] pub fn locale>(mut self, locale: S) -> Self { self.locale = Some(locale.as_ref().to_os_string()); self } /// Set the default locale in the respective category for new databases + #[must_use] pub fn lc_collate>(mut self, lc_collate: S) -> Self { self.lc_collate = Some(lc_collate.as_ref().to_os_string()); self } /// Set the default locale in the respective category for new databases + #[must_use] pub fn lc_ctype>(mut self, lc_ctype: S) -> Self { self.lc_ctype = Some(lc_ctype.as_ref().to_os_string()); self } /// Set the default locale in the respective category for new databases + #[must_use] pub fn lc_messages>(mut self, lc_messages: S) -> Self { self.lc_messages = Some(lc_messages.as_ref().to_os_string()); self } /// Set the default locale in the respective category for new databases + #[must_use] pub fn lc_monetary>(mut self, lc_monetary: S) -> Self { self.lc_monetary = Some(lc_monetary.as_ref().to_os_string()); self } /// Set the default locale in the respective category for new databases + #[must_use] pub fn lc_numeric>(mut self, lc_numeric: S) -> Self { self.lc_numeric = Some(lc_numeric.as_ref().to_os_string()); self } /// Set the default locale in the respective category for new databases + #[must_use] pub fn lc_time>(mut self, lc_time: S) -> Self { self.lc_time = Some(lc_time.as_ref().to_os_string()); self } /// Equivalent to --locale=C + #[must_use] pub fn no_locale(mut self) -> Self { self.no_locale = true; self } /// Set the default locale provider for new databases + #[must_use] pub fn locale_provider>(mut self, locale_provider: S) -> Self { self.locale_provider = Some(locale_provider.as_ref().to_os_string()); self } /// Read password for the new superuser from file + #[must_use] pub fn pwfile>(mut self, pwfile: P) -> Self { self.pwfile = Some(pwfile.into()); self } /// Set the default text search configuration + #[must_use] pub fn text_search_config>(mut self, text_search_config: S) -> Self { self.text_search_config = Some(text_search_config.as_ref().to_os_string()); self } /// Set the database superuser name + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// Prompt for a password for the new superuser + #[must_use] pub fn pwprompt(mut self) -> Self { self.pwprompt = true; self } /// Set the location for the write-ahead log directory + #[must_use] pub fn waldir>(mut self, waldir: S) -> Self { self.waldir = Some(waldir.as_ref().to_os_string()); self } /// Set the size of WAL segments, in megabytes + #[must_use] pub fn wal_segsize>(mut self, wal_segsize: S) -> Self { self.wal_segsize = Some(wal_segsize.as_ref().to_os_string()); self } /// Override default setting for server parameter + #[must_use] pub fn set>(mut self, set: S) -> Self { self.set = Some(set.as_ref().to_os_string()); self } /// Generate lots of debugging output + #[must_use] pub fn debug(mut self) -> Self { self.debug = true; self } - /// Set debug_discard_caches=1 + /// Set `debug_discard_caches=1` + #[must_use] pub fn discard_caches(mut self) -> Self { self.discard_caches = true; self } /// Set where to find the input files + #[must_use] pub fn directory>(mut self, directory: S) -> Self { self.directory = Some(directory.as_ref().to_os_string()); self } /// Do not clean up after errors + #[must_use] pub fn no_clean(mut self) -> Self { self.no_clean = true; self } /// Do not wait for changes to be written safely to disk + #[must_use] pub fn no_sync(mut self) -> Self { self.no_sync = true; self } /// Do not print instructions for next steps + #[must_use] pub fn no_instructions(mut self) -> Self { self.no_instructions = true; self } /// Show internal settings + #[must_use] pub fn show(mut self) -> Self { self.show = true; self } /// Only sync database files to disk, then exit + #[must_use] pub fn sync_only(mut self) -> Self { self.sync_only = true; self } /// Output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// Show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self @@ -287,6 +326,7 @@ impl CommandBuilder for InitDbBuilder { } /// Get the arguments for the command + #[allow(clippy::too_many_lines)] fn get_args(&self) -> Vec { let mut args: Vec = Vec::new(); @@ -454,6 +494,18 @@ impl CommandBuilder for InitDbBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -484,6 +536,7 @@ mod tests { #[test] fn test_builder() { let command = InitDbBuilder::new() + .env("PGDATABASE", "database") .auth("md5") .auth_host("md5") .auth_local("md5") @@ -522,7 +575,7 @@ mod tests { .build(); assert_eq!( - r#""initdb" "--auth" "md5" "--auth-host" "md5" "--auth-local" "md5" "--pgdata" "pgdata" "--encoding" "UTF8" "--allow-group-access" "--icu-locale" "en_US" "--icu-rules" "phonebook" "--data-checksums" "--locale" "en_US" "--lc-collate" "en_US" "--lc-ctype" "en_US" "--lc-messages" "en_US" "--lc-monetary" "en_US" "--lc-numeric" "en_US" "--lc-time" "en_US" "--no-locale" "--locale-provider" "icu" "--pwfile" ".pwfile" "--text-search-config" "english" "--username" "postgres" "--pwprompt" "--waldir" "waldir" "--wal-segsize" "1" "--set" "timezone=UTC" "--debug" "--discard-caches" "--directory" "directory" "--no-clean" "--no-sync" "--no-instructions" "--show" "--sync-only" "--version" "--help""#, + r#"PGDATABASE="database" "initdb" "--auth" "md5" "--auth-host" "md5" "--auth-local" "md5" "--pgdata" "pgdata" "--encoding" "UTF8" "--allow-group-access" "--icu-locale" "en_US" "--icu-rules" "phonebook" "--data-checksums" "--locale" "en_US" "--lc-collate" "en_US" "--lc-ctype" "en_US" "--lc-messages" "en_US" "--lc-monetary" "en_US" "--lc-numeric" "en_US" "--lc-time" "en_US" "--no-locale" "--locale-provider" "icu" "--pwfile" ".pwfile" "--text-search-config" "english" "--username" "postgres" "--pwprompt" "--waldir" "waldir" "--wal-segsize" "1" "--set" "timezone=UTC" "--debug" "--discard-caches" "--directory" "directory" "--no-clean" "--no-sync" "--no-instructions" "--show" "--sync-only" "--version" "--help""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/lib.rs b/postgresql_commands/src/lib.rs index 8b0cc8d..62e8c88 100644 --- a/postgresql_commands/src/lib.rs +++ b/postgresql_commands/src/lib.rs @@ -1,7 +1,8 @@ #![forbid(unsafe_code)] #![allow(async_fn_in_trait)] +#![deny(clippy::pedantic)] -//! Command builders for interacting with PostgreSQL via CLI. +//! Command builders for interacting with `PostgreSQL` via CLI. //! //! The commands are implemented as builders, which can be used to construct a //! [standard Command](std::process::Command) or [tokio Command](tokio::process::Command). diff --git a/postgresql_commands/src/oid2name.rs b/postgresql_commands/src/oid2name.rs index 693d213..584bea1 100644 --- a/postgresql_commands/src/oid2name.rs +++ b/postgresql_commands/src/oid2name.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `oid2name` helps to examine the file structure used by PostgreSQL. +/// `oid2name` helps to examine the file structure used by `PostgreSQL`. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct Oid2NameBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, filenode: Option, indexes: bool, oid: Option, @@ -25,12 +27,13 @@ pub struct Oid2NameBuilder { } impl Oid2NameBuilder { - /// Create a new [Oid2NameBuilder] + /// Create a new [`Oid2NameBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [Oid2NameBuilder] from [Settings] + /// Create a new [`Oid2NameBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -40,90 +43,105 @@ impl Oid2NameBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// show info for table with given file node + #[must_use] pub fn filenode>(mut self, filenode: S) -> Self { self.filenode = Some(filenode.as_ref().to_os_string()); self } /// show indexes and sequences too + #[must_use] pub fn indexes(mut self) -> Self { self.indexes = true; self } /// show info for table with given OID + #[must_use] pub fn oid>(mut self, oid: S) -> Self { self.oid = Some(oid.as_ref().to_os_string()); self } /// quiet (don't show headers) + #[must_use] pub fn quiet(mut self) -> Self { self.quiet = true; self } /// show all tablespaces + #[must_use] pub fn tablespaces(mut self) -> Self { self.tablespaces = true; self } /// show system objects too + #[must_use] pub fn system_objects(mut self) -> Self { self.system_objects = true; self } /// show info for named table + #[must_use] pub fn table>(mut self, table: S) -> Self { self.table = Some(table.as_ref().to_os_string()); self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// extended (show additional columns) + #[must_use] pub fn extended(mut self) -> Self { self.extended = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// database to connect to + #[must_use] pub fn dbname>(mut self, dbname: S) -> Self { self.dbname = Some(dbname.as_ref().to_os_string()); self } /// database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// database server port number + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// connect as specified database user + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self @@ -210,6 +228,18 @@ impl CommandBuilder for Oid2NameBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -240,6 +270,7 @@ mod tests { #[test] fn test_builder() { let command = Oid2NameBuilder::new() + .env("PGDATABASE", "database") .filenode("filenode") .indexes() .oid("oid") @@ -257,7 +288,7 @@ mod tests { .build(); assert_eq!( - r#""oid2name" "--filenode" "filenode" "--indexes" "--oid" "oid" "--quiet" "--tablespaces" "--system-objects" "--table" "table" "--version" "--extended" "--help" "--dbname" "dbname" "--host" "localhost" "--port" "5432" "--username" "username""#, + r#"PGDATABASE="database" "oid2name" "--filenode" "filenode" "--indexes" "--oid" "oid" "--quiet" "--tablespaces" "--system-objects" "--table" "table" "--version" "--extended" "--help" "--dbname" "dbname" "--host" "localhost" "--port" "5432" "--username" "username""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_amcheck.rs b/postgresql_commands/src/pg_amcheck.rs index 16a44f5..5e63624 100644 --- a/postgresql_commands/src/pg_amcheck.rs +++ b/postgresql_commands/src/pg_amcheck.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_amcheck` checks objects in a PostgreSQL database for corruption. +/// `pg_amcheck` checks objects in a `PostgreSQL` database for corruption. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct PgAmCheckBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, all: bool, database: Option, exclude_database: Option, @@ -47,12 +49,13 @@ pub struct PgAmCheckBuilder { } impl PgAmCheckBuilder { - /// Create a new [PgAmCheckBuilder] + /// Create a new [`PgAmCheckBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgAmCheckBuilder] from [Settings] + /// Create a new [`PgAmCheckBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -63,222 +66,259 @@ impl PgAmCheckBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// check all databases + #[must_use] pub fn all(mut self) -> Self { self.all = true; self } /// check matching database(s) + #[must_use] pub fn database>(mut self, database: S) -> Self { self.database = Some(database.as_ref().to_os_string()); self } /// do NOT check matching database(s) + #[must_use] pub fn exclude_database>(mut self, exclude_database: S) -> Self { self.exclude_database = Some(exclude_database.as_ref().to_os_string()); self } /// check matching index(es) + #[must_use] pub fn index>(mut self, index: S) -> Self { self.index = Some(index.as_ref().to_os_string()); self } /// do NOT check matching index(es) + #[must_use] pub fn exclude_index>(mut self, exclude_index: S) -> Self { self.exclude_index = Some(exclude_index.as_ref().to_os_string()); self } /// check matching relation(s) + #[must_use] pub fn relation>(mut self, relation: S) -> Self { self.relation = Some(relation.as_ref().to_os_string()); self } /// do NOT check matching relation(s) + #[must_use] pub fn exclude_relation>(mut self, exclude_relation: S) -> Self { self.exclude_relation = Some(exclude_relation.as_ref().to_os_string()); self } /// check matching schema(s) + #[must_use] pub fn schema>(mut self, schema: S) -> Self { self.schema = Some(schema.as_ref().to_os_string()); self } /// do NOT check matching schema(s) + #[must_use] pub fn exclude_schema>(mut self, exclude_schema: S) -> Self { self.exclude_schema = Some(exclude_schema.as_ref().to_os_string()); self } /// check matching table(s) + #[must_use] pub fn table>(mut self, table: S) -> Self { self.table = Some(table.as_ref().to_os_string()); self } /// do NOT check matching table(s) + #[must_use] pub fn exclude_table>(mut self, exclude_table: S) -> Self { self.exclude_table = Some(exclude_table.as_ref().to_os_string()); self } /// do NOT expand list of relations to include indexes + #[must_use] pub fn no_dependent_indexes(mut self) -> Self { self.no_dependent_indexes = true; self } /// do NOT expand list of relations to include TOAST tables + #[must_use] pub fn no_dependent_toast(mut self) -> Self { self.no_dependent_toast = true; self } /// do NOT require patterns to match objects + #[must_use] pub fn no_strict_names(mut self) -> Self { self.no_strict_names = true; self } /// do NOT follow relation TOAST pointers + #[must_use] pub fn exclude_toast_pointers(mut self) -> Self { self.exclude_toast_pointers = true; self } /// stop checking at end of first corrupt page + #[must_use] pub fn on_error_stop(mut self) -> Self { self.on_error_stop = true; self } /// do NOT check "all-frozen" or "all-visible" blocks + #[must_use] pub fn skip>(mut self, skip: S) -> Self { self.skip = Some(skip.as_ref().to_os_string()); self } /// begin checking table(s) at the given block number + #[must_use] pub fn start_block>(mut self, start_block: S) -> Self { self.start_block = Some(start_block.as_ref().to_os_string()); self } /// check table(s) only up to the given block number + #[must_use] pub fn end_block>(mut self, end_block: S) -> Self { self.end_block = Some(end_block.as_ref().to_os_string()); self } /// check that all heap tuples are found within indexes + #[must_use] pub fn heap_all_indexed(mut self) -> Self { self.heap_all_indexed = true; self } /// check index parent/child relationships + #[must_use] pub fn parent_check(mut self) -> Self { self.parent_check = true; self } /// search from root page to refind tuples + #[must_use] pub fn root_descend(mut self) -> Self { self.root_descend = true; self } /// database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// database server port + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// user name to connect as + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// force password prompt + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self } /// alternate maintenance database + #[must_use] pub fn maintenance_db>(mut self, maintenance_db: S) -> Self { self.maintenance_db = Some(maintenance_db.as_ref().to_os_string()); self } /// show the commands being sent to the server + #[must_use] pub fn echo(mut self) -> Self { self.echo = true; self } /// use this many concurrent connections to the server + #[must_use] pub fn jobs>(mut self, jobs: S) -> Self { self.jobs = Some(jobs.as_ref().to_os_string()); self } /// show progress information + #[must_use] pub fn progress(mut self) -> Self { self.progress = true; self } /// write a lot of output + #[must_use] pub fn verbose(mut self) -> Self { self.verbose = true; self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// install missing extensions + #[must_use] pub fn install_missing(mut self) -> Self { self.install_missing = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self @@ -297,6 +337,7 @@ impl CommandBuilder for PgAmCheckBuilder { } /// Get the arguments for the command + #[allow(clippy::too_many_lines)] fn get_args(&self) -> Vec { let mut args: Vec = Vec::new(); @@ -463,7 +504,7 @@ impl CommandBuilder for PgAmCheckBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -471,6 +512,13 @@ impl CommandBuilder for PgAmCheckBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -501,6 +549,7 @@ mod tests { #[test] fn test_builder() { let command = PgAmCheckBuilder::new() + .env("PGDATABASE", "database") .all() .database("database") .exclude_database("exclude_database") @@ -540,7 +589,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "pg_amcheck" "--all" "--database" "database" "--exclude-database" "exclude_database" "--index" "index" "--exclude-index" "exclude_index" "--relation" "relation" "--exclude-relation" "exclude_relation" "--schema" "schema" "--exclude-schema" "exclude_schema" "--table" "table" "--exclude-table" "exclude_table" "--no-dependent-indexes" "--no-dependent-toast" "--no-strict-names" "--exclude-toast-pointers" "--on-error-stop" "--skip" "skip" "--startblock" "start_block" "--endblock" "end_block" "--heapallindexed" "--parent-check" "--rootdescend" "--host" "localhost" "--port" "5432" "--username" "username" "--no-password" "--password" "--maintenance-db" "maintenance_db" "--echo" "--jobs" "jobs" "--progress" "--verbose" "--version" "--install-missing" "--help""#, + r#"PGDATABASE="database" PGPASSWORD="password" "pg_amcheck" "--all" "--database" "database" "--exclude-database" "exclude_database" "--index" "index" "--exclude-index" "exclude_index" "--relation" "relation" "--exclude-relation" "exclude_relation" "--schema" "schema" "--exclude-schema" "exclude_schema" "--table" "table" "--exclude-table" "exclude_table" "--no-dependent-indexes" "--no-dependent-toast" "--no-strict-names" "--exclude-toast-pointers" "--on-error-stop" "--skip" "skip" "--startblock" "start_block" "--endblock" "end_block" "--heapallindexed" "--parent-check" "--rootdescend" "--host" "localhost" "--port" "5432" "--username" "username" "--no-password" "--password" "--maintenance-db" "maintenance_db" "--echo" "--jobs" "jobs" "--progress" "--verbose" "--version" "--install-missing" "--help""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_archivecleanup.rs b/postgresql_commands/src/pg_archivecleanup.rs index 617a0b4..e0727a7 100644 --- a/postgresql_commands/src/pg_archivecleanup.rs +++ b/postgresql_commands/src/pg_archivecleanup.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_archivecleanup` removes older WAL files from PostgreSQL archives. +/// `pg_archivecleanup` removes older WAL files from `PostgreSQL` archives. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct PgArchiveCleanupBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, debug: bool, dry_run: bool, version: bool, @@ -18,59 +20,68 @@ pub struct PgArchiveCleanupBuilder { } impl PgArchiveCleanupBuilder { - /// Create a new [PgArchiveCleanupBuilder] + /// Create a new [`PgArchiveCleanupBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgArchiveCleanupBuilder] from [Settings] + /// Create a new [`PgArchiveCleanupBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new().program_dir(settings.get_binary_dir()) } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// generate debug output (verbose mode) + #[must_use] pub fn debug(mut self) -> Self { self.debug = true; self } /// dry run, show the names of the files that would be removed + #[must_use] pub fn dry_run(mut self) -> Self { self.dry_run = true; self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// clean up files if they have this extension + #[must_use] pub fn ext>(mut self, ext: S) -> Self { self.ext = Some(ext.as_ref().to_os_string()); self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// archive location + #[must_use] pub fn archive_location>(mut self, archive_location: S) -> Self { self.archive_location = Some(archive_location.as_ref().to_os_string()); self } /// oldest kept WAL file + #[must_use] pub fn oldest_kept_wal_file>(mut self, oldest_kept_wal_file: S) -> Self { self.oldest_kept_wal_file = Some(oldest_kept_wal_file.as_ref().to_os_string()); self @@ -123,6 +134,18 @@ impl CommandBuilder for PgArchiveCleanupBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -150,6 +173,7 @@ mod tests { #[test] fn test_builder() { let command = PgArchiveCleanupBuilder::new() + .env("PGDATABASE", "database") .debug() .dry_run() .version() @@ -160,7 +184,7 @@ mod tests { .build(); assert_eq!( - r#""pg_archivecleanup" "-d" "-n" "--version" "-x" "partial" "--help" "archive_location" "000000010000000000000001""#, + r#"PGDATABASE="database" "pg_archivecleanup" "-d" "-n" "--version" "-x" "partial" "--help" "archive_location" "000000010000000000000001""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_basebackup.rs b/postgresql_commands/src/pg_basebackup.rs index 6506e8d..d485b00 100644 --- a/postgresql_commands/src/pg_basebackup.rs +++ b/postgresql_commands/src/pg_basebackup.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_basebackup` takes a base backup of a running PostgreSQL server. +/// `pg_basebackup` takes a base backup of a running `PostgreSQL` server. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct PgBaseBackupBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, pgdata: Option, format: Option, max_rate: Option, @@ -45,12 +47,13 @@ pub struct PgBaseBackupBuilder { } impl PgBaseBackupBuilder { - /// Create a new [PgBaseBackupBuilder] + /// Create a new [`PgBaseBackupBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgBaseBackupBuilder] from [Settings] + /// Create a new [`PgBaseBackupBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -61,210 +64,245 @@ impl PgBaseBackupBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// receive base backup into directory + #[must_use] pub fn pgdata>(mut self, pgdata: P) -> Self { self.pgdata = Some(pgdata.into()); self } /// output format (plain (default), tar) + #[must_use] pub fn format>(mut self, format: S) -> Self { self.format = Some(format.as_ref().to_os_string()); self } /// maximum transfer rate to transfer data directory (in kB/s, or use suffix "k" or "M") + #[must_use] pub fn max_rate>(mut self, max_rate: S) -> Self { self.max_rate = Some(max_rate.as_ref().to_os_string()); self } /// write configuration for replication + #[must_use] pub fn write_recovery_conf(mut self) -> Self { self.write_recovery_conf = true; self } /// backup target (if other than client) + #[must_use] pub fn target>(mut self, target: S) -> Self { self.target = Some(target.as_ref().to_os_string()); self } /// relocate tablespace in OLDDIR to NEWDIR + #[must_use] pub fn tablespace_mapping>(mut self, tablespace_mapping: S) -> Self { self.tablespace_mapping = Some(tablespace_mapping.as_ref().to_os_string()); self } /// location for the write-ahead log directory + #[must_use] pub fn waldir>(mut self, waldir: S) -> Self { self.waldir = Some(waldir.as_ref().to_os_string()); self } /// include required WAL files with specified method + #[must_use] pub fn wal_method>(mut self, wal_method: S) -> Self { self.wal_method = Some(wal_method.as_ref().to_os_string()); self } /// compress tar output + #[must_use] pub fn gzip(mut self) -> Self { self.gzip = true; self } /// compress on client or server as specified + #[must_use] pub fn compress>(mut self, compress: S) -> Self { self.compress = Some(compress.as_ref().to_os_string()); self } /// set fast or spread checkpointing + #[must_use] pub fn checkpoint>(mut self, checkpoint: S) -> Self { self.checkpoint = Some(checkpoint.as_ref().to_os_string()); self } /// create replication slot + #[must_use] pub fn create_slot(mut self) -> Self { self.create_slot = true; self } /// set backup label + #[must_use] pub fn label>(mut self, label: S) -> Self { self.label = Some(label.as_ref().to_os_string()); self } /// do not clean up after errors + #[must_use] pub fn no_clean(mut self) -> Self { self.no_clean = true; self } /// do not wait for changes to be written safely to disk + #[must_use] pub fn no_sync(mut self) -> Self { self.no_sync = true; self } /// show progress information + #[must_use] pub fn progress(mut self) -> Self { self.progress = true; self } /// replication slot to use + #[must_use] pub fn slot>(mut self, slot: S) -> Self { self.slot = Some(slot.as_ref().to_os_string()); self } /// output verbose messages + #[must_use] pub fn verbose(mut self) -> Self { self.verbose = true; self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// use algorithm for manifest checksums + #[must_use] pub fn manifest_checksums>(mut self, manifest_checksums: S) -> Self { self.manifest_checksums = Some(manifest_checksums.as_ref().to_os_string()); self } /// hex encode all file names in manifest + #[must_use] pub fn manifest_force_encode(mut self) -> Self { self.manifest_force_encode = true; self } /// do not estimate backup size in server side + #[must_use] pub fn no_estimate_size(mut self) -> Self { self.no_estimate_size = true; self } /// suppress generation of backup manifest + #[must_use] pub fn no_manifest(mut self) -> Self { self.no_manifest = true; self } /// prevent creation of temporary replication slot + #[must_use] pub fn no_slot(mut self) -> Self { self.no_slot = true; self } /// do not verify checksums + #[must_use] pub fn no_verify_checksums(mut self) -> Self { self.no_verify_checksums = true; self } /// show this help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// connection string + #[must_use] pub fn dbname>(mut self, dbname: S) -> Self { self.dbname = Some(dbname.as_ref().to_os_string()); self } /// database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// database server port number + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// time between status packets sent to server (in seconds) + #[must_use] pub fn status_interval>(mut self, status_interval: S) -> Self { self.status_interval = Some(status_interval.as_ref().to_os_string()); self } /// connect as specified database user + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// force password prompt (should happen automatically) + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self @@ -283,6 +321,7 @@ impl CommandBuilder for PgBaseBackupBuilder { } /// Get the arguments for the command + #[allow(clippy::too_many_lines)] fn get_args(&self) -> Vec { let mut args: Vec = Vec::new(); @@ -440,7 +479,7 @@ impl CommandBuilder for PgBaseBackupBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -448,6 +487,13 @@ impl CommandBuilder for PgBaseBackupBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -478,6 +524,7 @@ mod tests { #[test] fn test_builder() { let command = PgBaseBackupBuilder::new() + .env("PGDATABASE", "database") .pgdata("pgdata") .format("plain") .max_rate("100M") @@ -515,7 +562,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "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""#, + r#"PGDATABASE="database" PGPASSWORD="password" "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""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_checksums.rs b/postgresql_commands/src/pg_checksums.rs index eb9611b..42b436a 100644 --- a/postgresql_commands/src/pg_checksums.rs +++ b/postgresql_commands/src/pg_checksums.rs @@ -4,10 +4,13 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_checksums` enables, disables, or verifies data checksums in a PostgreSQL database cluster. +/// `pg_checksums` enables, disables, or verifies data checksums in a `PostgreSQL` database cluster. #[derive(Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] +#[allow(clippy::struct_excessive_bools)] pub struct PgChecksumsBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, pgdata: Option, check: bool, disable: bool, @@ -21,77 +24,89 @@ pub struct PgChecksumsBuilder { } impl PgChecksumsBuilder { - /// Create a new [PgChecksumsBuilder] + /// Create a new [`PgChecksumsBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgChecksumsBuilder] from [Settings] + /// Create a new [`PgChecksumsBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new().program_dir(settings.get_binary_dir()) } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// data directory + #[must_use] pub fn pgdata>(mut self, pgdata: P) -> Self { self.pgdata = Some(pgdata.into()); self } /// check data checksums (default) + #[must_use] pub fn check(mut self) -> Self { self.check = true; self } /// disable data checksums + #[must_use] pub fn disable(mut self) -> Self { self.disable = true; self } /// enable data checksums + #[must_use] pub fn enable(mut self) -> Self { self.enable = true; self } /// check only relation with specified filenode + #[must_use] pub fn filenode>(mut self, filenode: S) -> Self { self.filenode = Some(filenode.as_ref().to_os_string()); self } /// do not wait for changes to be written safely to disk + #[must_use] pub fn no_sync(mut self) -> Self { self.no_sync = true; self } /// show progress information + #[must_use] pub fn progress(mut self) -> Self { self.progress = true; self } /// output verbose messages + #[must_use] pub fn verbose(mut self) -> Self { self.verbose = true; self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self @@ -157,6 +172,18 @@ impl CommandBuilder for PgChecksumsBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -184,6 +211,7 @@ mod tests { #[test] fn test_builder() { let command = PgChecksumsBuilder::new() + .env("PGDATABASE", "database") .pgdata("pgdata") .check() .disable() @@ -197,7 +225,7 @@ mod tests { .build(); assert_eq!( - r#""pg_checksums" "--pgdata" "pgdata" "--check" "--disable" "--enable" "--filenode" "12345" "--no-sync" "--progress" "--verbose" "--version" "--help""#, + r#"PGDATABASE="database" "pg_checksums" "--pgdata" "pgdata" "--check" "--disable" "--enable" "--filenode" "12345" "--no-sync" "--progress" "--verbose" "--version" "--help""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_config.rs b/postgresql_commands/src/pg_config.rs index 0a912f1..1fd29bb 100644 --- a/postgresql_commands/src/pg_config.rs +++ b/postgresql_commands/src/pg_config.rs @@ -4,10 +4,13 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_config` provides information about the installed version of PostgreSQL. +/// `pg_config` provides information about the installed version of `PostgreSQL`. #[derive(Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] +#[allow(clippy::struct_excessive_bools)] pub struct PgConfigBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, bindir: Option, docdir: Option, htmldir: Option, @@ -35,161 +38,187 @@ pub struct PgConfigBuilder { } impl PgConfigBuilder { - /// Create a new [PgConfigBuilder] + /// Create a new [`PgConfigBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgConfigBuilder] from [Settings] + /// Create a new [`PgConfigBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new().program_dir(settings.get_binary_dir()) } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// Set the bindir + #[must_use] pub fn bindir>(mut self, bindir: S) -> Self { self.bindir = Some(bindir.as_ref().to_os_string()); self } /// Set the docdir + #[must_use] pub fn docdir>(mut self, docdir: S) -> Self { self.docdir = Some(docdir.as_ref().to_os_string()); self } /// Set the htmldir + #[must_use] pub fn htmldir>(mut self, htmldir: S) -> Self { self.htmldir = Some(htmldir.as_ref().to_os_string()); self } /// Set the includedir + #[must_use] pub fn includedir>(mut self, includedir: S) -> Self { self.includedir = Some(includedir.as_ref().to_os_string()); self } /// Set the pkgincludedir + #[must_use] pub fn pkgincludedir>(mut self, pkgincludedir: S) -> Self { self.pkgincludedir = Some(pkgincludedir.as_ref().to_os_string()); self } - /// Set the includedir_server + /// Set the `includedir_server` + #[must_use] pub fn includedir_server>(mut self, includedir_server: S) -> Self { self.includedir_server = Some(includedir_server.as_ref().to_os_string()); self } /// Set the libdir + #[must_use] pub fn libdir>(mut self, libdir: S) -> Self { self.libdir = Some(libdir.as_ref().to_os_string()); self } /// Set the pkglibdir + #[must_use] pub fn pkglibdir>(mut self, pkglibdir: S) -> Self { self.pkglibdir = Some(pkglibdir.as_ref().to_os_string()); self } /// Set the localedir + #[must_use] pub fn localedir>(mut self, localedir: S) -> Self { self.localedir = Some(localedir.as_ref().to_os_string()); self } /// Set the mandir + #[must_use] pub fn mandir>(mut self, mandir: S) -> Self { self.mandir = Some(mandir.as_ref().to_os_string()); self } /// Set the sharedir + #[must_use] pub fn sharedir>(mut self, sharedir: S) -> Self { self.sharedir = Some(sharedir.as_ref().to_os_string()); self } /// Set the sysconfdir + #[must_use] pub fn sysconfdir>(mut self, sysconfdir: S) -> Self { self.sysconfdir = Some(sysconfdir.as_ref().to_os_string()); self } /// Set the pgxs + #[must_use] pub fn pgxs>(mut self, pgxs: S) -> Self { self.pgxs = Some(pgxs.as_ref().to_os_string()); self } /// Set the configure flag + #[must_use] pub fn configure(mut self) -> Self { self.configure = true; self } /// Set the cc flag + #[must_use] pub fn cc(mut self) -> Self { self.cc = true; self } /// Set the cppflags flag + #[must_use] pub fn cppflags(mut self) -> Self { self.cppflags = true; self } /// Set the cflags flag + #[must_use] pub fn cflags(mut self) -> Self { self.cflags = true; self } - /// Set the cflags_sl flag + /// Set the `cflags_sl` flag + #[must_use] pub fn cflags_sl(mut self) -> Self { self.cflags_sl = true; self } /// Set the ldflags flag + #[must_use] pub fn ldflags(mut self) -> Self { self.ldflags = true; self } - /// Set the ldflags_ex flag + /// Set the `ldflags_ex` flag + #[must_use] pub fn ldflags_ex(mut self) -> Self { self.ldflags_ex = true; self } - /// Set the ldflags_sl flag + /// Set the `ldflags_sl` flag + #[must_use] pub fn ldflags_sl(mut self) -> Self { self.ldflags_sl = true; self } /// Set the libs flag + #[must_use] pub fn libs(mut self) -> Self { self.libs = true; self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self @@ -322,6 +351,18 @@ impl CommandBuilder for PgConfigBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -349,6 +390,7 @@ mod tests { #[test] fn test_builder() { let command = PgConfigBuilder::new() + .env("PGDATABASE", "database") .bindir("bindir") .docdir("docdir") .htmldir("htmldir") @@ -376,7 +418,7 @@ mod tests { .build(); assert_eq!( - r#""pg_config" "--bindir" "bindir" "--docdir" "docdir" "--htmldir" "htmldir" "--includedir" "includedir" "--pkgincludedir" "pkgincludedir" "--includedir-server" "includedir_server" "--libdir" "libdir" "--pkglibdir" "pkglibdir" "--localedir" "localedir" "--mandir" "mandir" "--sharedir" "sharedir" "--sysconfdir" "sysconfdir" "--pgxs" "pgxs" "--configure" "--cc" "--cppflags" "--cflags" "--cflags_sl" "--ldflags" "--ldflags_ex" "--ldflags_sl" "--libs" "--version" "--help""#, + r#"PGDATABASE="database" "pg_config" "--bindir" "bindir" "--docdir" "docdir" "--htmldir" "htmldir" "--includedir" "includedir" "--pkgincludedir" "pkgincludedir" "--includedir-server" "includedir_server" "--libdir" "libdir" "--pkglibdir" "pkglibdir" "--localedir" "localedir" "--mandir" "mandir" "--sharedir" "sharedir" "--sysconfdir" "sysconfdir" "--pgxs" "pgxs" "--configure" "--cc" "--cppflags" "--cflags" "--cflags_sl" "--ldflags" "--ldflags_ex" "--ldflags_sl" "--libs" "--version" "--help""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_controldata.rs b/postgresql_commands/src/pg_controldata.rs index 4bee0c9..57903f0 100644 --- a/postgresql_commands/src/pg_controldata.rs +++ b/postgresql_commands/src/pg_controldata.rs @@ -3,45 +3,51 @@ use crate::Settings; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_controldata` displays control information of a PostgreSQL database cluster. +/// `pg_controldata` displays control information of a `PostgreSQL` database cluster. #[derive(Clone, Debug, Default)] pub struct PgControlDataBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, pgdata: Option, version: bool, help: bool, } impl PgControlDataBuilder { - /// Create a new [PgControlDataBuilder] + /// Create a new [`PgControlDataBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgControlDataBuilder] from [Settings] + /// Create a new [`PgControlDataBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new().program_dir(settings.get_binary_dir()) } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// Set the data directory + #[must_use] pub fn pgdata>(mut self, pgdata: P) -> Self { self.pgdata = Some(pgdata.into()); self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self @@ -78,6 +84,18 @@ impl CommandBuilder for PgControlDataBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -105,13 +123,14 @@ mod tests { #[test] fn test_builder() { let command = PgControlDataBuilder::new() + .env("PGDATABASE", "database") .pgdata("pgdata") .version() .help() .build(); assert_eq!( - r#""pg_controldata" "--pgdata" "pgdata" "--version" "--help""#, + r#"PGDATABASE="database" "pg_controldata" "--pgdata" "pgdata" "--version" "--help""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_ctl.rs b/postgresql_commands/src/pg_ctl.rs index 0d9c86f..2002070 100644 --- a/postgresql_commands/src/pg_ctl.rs +++ b/postgresql_commands/src/pg_ctl.rs @@ -5,10 +5,13 @@ use std::ffi::{OsStr, OsString}; use std::fmt::Display; use std::path::PathBuf; -/// `pg_ctl` is a utility to initialize, start, stop, or control a PostgreSQL server. +/// `pg_ctl` is a utility to initialize, start, stop, or control a `PostgreSQL` server. #[derive(Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] +#[allow(clippy::struct_excessive_bools)] pub struct PgCtlBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, mode: Option, pgdata: Option, silent: bool, @@ -73,106 +76,124 @@ impl Display for ShutdownMode { } impl PgCtlBuilder { - /// Create a new [PgCtlBuilder] + /// Create a new [`PgCtlBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgCtlBuilder] from [Settings] + /// Create a new [`PgCtlBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new().program_dir(settings.get_binary_dir()) } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } + /// mode + #[must_use] pub fn mode(mut self, mode: Mode) -> Self { self.mode = Some(mode); self } /// location of the database storage area + #[must_use] pub fn pgdata>(mut self, pgdata: P) -> Self { self.pgdata = Some(pgdata.into()); self } /// only print errors, no informational messages + #[must_use] pub fn silent(mut self) -> Self { self.silent = true; self } /// seconds to wait when using -w option + #[must_use] pub fn timeout(mut self, timeout: u16) -> Self { self.timeout = Some(timeout); self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// wait until operation completes (default) + #[must_use] pub fn wait(mut self) -> Self { self.wait = true; self } /// do not wait until operation completes + #[must_use] pub fn no_wait(mut self) -> Self { self.no_wait = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// allow postgres to produce core files + #[must_use] pub fn core_files(mut self) -> Self { self.core_files = true; self } /// write (or append) server log to FILENAME + #[must_use] pub fn log>(mut self, log: P) -> Self { self.log = Some(log.into()); self } - /// command line options to pass to postgres (PostgreSQL server executable) or initdb - pub fn options>(mut self, options: Vec) -> Self { + /// command line options to pass to postgres (`PostgreSQL` server executable) or initdb + #[must_use] + pub fn options>(mut self, options: &[S]) -> Self { self.options = options.iter().map(|s| s.as_ref().to_os_string()).collect(); self } /// normally not necessary + #[must_use] pub fn path_to_postgres>(mut self, path_to_postgres: S) -> Self { self.path_to_postgres = Some(path_to_postgres.as_ref().to_os_string()); self } /// MODE can be "smart", "fast", or "immediate" + #[must_use] pub fn shutdown_mode(mut self, shutdown_mode: ShutdownMode) -> Self { self.shutdown_mode = Some(shutdown_mode); self } /// SIGNALNAME + #[must_use] pub fn signal>(mut self, signal: S) -> Self { self.signal = Some(signal.as_ref().to_os_string()); self } /// PID + #[must_use] pub fn pid>(mut self, pid: S) -> Self { self.pid = Some(pid.as_ref().to_os_string()); self @@ -262,6 +283,18 @@ impl CommandBuilder for PgCtlBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -309,6 +342,7 @@ mod tests { #[test] fn test_builder() { let command = PgCtlBuilder::new() + .env("PGDATABASE", "database") .mode(Mode::Start) .pgdata("pgdata") .silent() @@ -319,7 +353,7 @@ mod tests { .help() .core_files() .log("log") - .options(vec!["-c log_connections=on"]) + .options(&["-c log_connections=on"]) .path_to_postgres("path_to_postgres") .shutdown_mode(ShutdownMode::Smart) .signal("HUP") @@ -327,7 +361,7 @@ mod tests { .build(); assert_eq!( - r#""pg_ctl" "start" "--pgdata" "pgdata" "--silent" "--timeout" "60" "--version" "--wait" "--no-wait" "--help" "--core-files" "--log" "log" "-o" "-c log_connections=on" "-p" "path_to_postgres" "--mode" "smart" "HUP" "12345""#, + r#"PGDATABASE="database" "pg_ctl" "start" "--pgdata" "pgdata" "--silent" "--timeout" "60" "--version" "--wait" "--no-wait" "--help" "--core-files" "--log" "log" "-o" "-c log_connections=on" "-p" "path_to_postgres" "--mode" "smart" "HUP" "12345""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_dump.rs b/postgresql_commands/src/pg_dump.rs index 2151c64..469a815 100644 --- a/postgresql_commands/src/pg_dump.rs +++ b/postgresql_commands/src/pg_dump.rs @@ -6,8 +6,11 @@ use std::path::PathBuf; /// `pg_dump` dumps a database as a text file or to other formats. #[derive(Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] +#[allow(clippy::struct_excessive_bools)] pub struct PgDumpBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, data_only: bool, large_objects: bool, no_large_objects: bool, @@ -72,12 +75,13 @@ pub struct PgDumpBuilder { } impl PgDumpBuilder { - /// Create a new [PgDumpBuilder] + /// Create a new [`PgDumpBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgDumpBuilder] from [Settings] + /// Create a new [`PgDumpBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -88,180 +92,210 @@ impl PgDumpBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// Dump only the data, not the schema + #[must_use] pub fn data_only(mut self) -> Self { self.data_only = true; self } /// Dump large objects in binary format + #[must_use] pub fn large_objects(mut self) -> Self { self.large_objects = true; self } /// Do not dump large objects + #[must_use] pub fn no_large_objects(mut self) -> Self { self.no_large_objects = true; self } /// Output commands to clean (drop) database objects prior to outputting the commands for creating them + #[must_use] pub fn clean(mut self) -> Self { self.clean = true; self } /// Output commands to create the database objects (data definition) + #[must_use] pub fn create(mut self) -> Self { self.create = true; self } /// Dump data for the named extension + #[must_use] pub fn extension>(mut self, extension: S) -> Self { self.extension = Some(extension.as_ref().to_os_string()); self } /// Dump data in encoding ENCODING + #[must_use] pub fn encoding>(mut self, encoding: S) -> Self { self.encoding = Some(encoding.as_ref().to_os_string()); self } /// Set the output file or directory name + #[must_use] pub fn file>(mut self, file: S) -> Self { self.file = Some(file.as_ref().to_os_string()); self } /// Set the output file format (custom, directory, tar, plain text (default)) + #[must_use] pub fn format>(mut self, format: S) -> Self { self.format = Some(format.as_ref().to_os_string()); self } /// Use this many parallel jobs to dump + #[must_use] pub fn jobs>(mut self, jobs: S) -> Self { self.jobs = Some(jobs.as_ref().to_os_string()); self } /// Dump data for the named schema(s) only + #[must_use] pub fn schema>(mut self, schema: S) -> Self { self.schema = Some(schema.as_ref().to_os_string()); self } /// Do not output commands to set ownership of objects to match the original database + #[must_use] pub fn exclude_schema>(mut self, exclude_schema: S) -> Self { self.exclude_schema = Some(exclude_schema.as_ref().to_os_string()); self } /// Do not output commands to set ownership of objects to match the original database + #[must_use] pub fn no_owner(mut self) -> Self { self.no_owner = true; self } /// Do not reconnect to the database + #[must_use] pub fn no_reconnect(mut self) -> Self { self.no_reconnect = true; self } /// Dump only the schema, no data + #[must_use] pub fn schema_only(mut self) -> Self { self.schema_only = true; self } /// Dump data as a superuser + #[must_use] pub fn superuser>(mut self, superuser: S) -> Self { self.superuser = Some(superuser.as_ref().to_os_string()); self } /// Dump data for the named table(s) only + #[must_use] pub fn table>(mut self, table: S) -> Self { self.table = Some(table.as_ref().to_os_string()); self } /// Do not output commands to create the table(s) containing the data + #[must_use] pub fn exclude_table>(mut self, exclude_table: S) -> Self { self.exclude_table = Some(exclude_table.as_ref().to_os_string()); self } /// Enable verbose mode + #[must_use] pub fn verbose(mut self) -> Self { self.verbose = true; self } /// Output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// Do not output commands to set object privileges + #[must_use] pub fn no_privileges(mut self) -> Self { self.no_privileges = true; self } /// Set the compression level to use + #[must_use] pub fn compression>(mut self, compress: S) -> Self { self.compression = Some(compress.as_ref().to_os_string()); self } /// Dump data in a format suitable for binary upgrade + #[must_use] pub fn binary_upgrade(mut self) -> Self { self.binary_upgrade = true; self } /// Dump data as INSERT commands with column names + #[must_use] pub fn column_inserts(mut self) -> Self { self.column_inserts = true; self } /// Dump data as INSERT commands with column names + #[must_use] pub fn attribute_inserts(mut self) -> Self { self.attribute_inserts = true; self } /// Disable dollar quoting, use SQL standard quoting + #[must_use] pub fn disable_dollar_quoting(mut self) -> Self { self.disable_dollar_quoting = true; self } /// Disable triggers during data-only restore + #[must_use] pub fn disable_triggers(mut self) -> Self { self.disable_triggers = true; self } /// Dump data with row security enabled + #[must_use] pub fn enable_row_security(mut self) -> Self { self.enable_row_security = true; self } /// Dump data for the named table(s) but exclude data for their child tables + #[must_use] pub fn exclude_table_data_and_children>( mut self, exclude_table_data_and_children: S, @@ -272,192 +306,224 @@ impl PgDumpBuilder { } /// Set the number of digits displayed for floating-point values + #[must_use] pub fn extra_float_digits>(mut self, extra_float_digits: S) -> Self { self.extra_float_digits = Some(extra_float_digits.as_ref().to_os_string()); self } /// Use IF EXISTS when dropping objects + #[must_use] pub fn if_exists(mut self) -> Self { self.if_exists = true; self } /// Include foreign-data wrappers in the dump + #[must_use] pub fn include_foreign_data>(mut self, include_foreign_data: S) -> Self { self.include_foreign_data = Some(include_foreign_data.as_ref().to_os_string()); self } /// Dump data as INSERT commands + #[must_use] pub fn inserts(mut self) -> Self { self.inserts = true; self } /// Load data via the partition root table + #[must_use] pub fn load_via_partition_root(mut self) -> Self { self.load_via_partition_root = true; self } /// Fail after waiting TIMEOUT for a table lock + #[must_use] pub fn lock_wait_timeout(mut self, lock_wait_timeout: u16) -> Self { self.lock_wait_timeout = Some(lock_wait_timeout); self } /// Do not output comments + #[must_use] pub fn no_comments(mut self) -> Self { self.no_comments = true; self } /// Do not output publications + #[must_use] pub fn no_publications(mut self) -> Self { self.no_publications = true; self } /// Do not output security labels + #[must_use] pub fn no_security_labels(mut self) -> Self { self.no_security_labels = true; self } /// Do not output subscriptions + #[must_use] pub fn no_subscriptions(mut self) -> Self { self.no_subscriptions = true; self } /// Do not output table access method + #[must_use] pub fn no_table_access_method(mut self) -> Self { self.no_table_access_method = true; self } /// Do not output tablespace assignments + #[must_use] pub fn no_tablespaces(mut self) -> Self { self.no_tablespaces = true; self } /// Do not output TOAST table compression + #[must_use] pub fn no_toast_compression(mut self) -> Self { self.no_toast_compression = true; self } /// Do not output unlogged table data + #[must_use] pub fn no_unlogged_table_data(mut self) -> Self { self.no_unlogged_table_data = true; self } /// Use ON CONFLICT DO NOTHING for INSERTs + #[must_use] pub fn on_conflict_do_nothing(mut self) -> Self { self.on_conflict_do_nothing = true; self } /// Quote all identifiers, even if not key words + #[must_use] pub fn quote_all_identifiers(mut self) -> Self { self.quote_all_identifiers = true; self } /// Set the number of rows per INSERT + #[must_use] pub fn rows_per_insert(mut self, rows_per_insert: u64) -> Self { self.rows_per_insert = Some(rows_per_insert); self } /// Dump data for the named section(s) only + #[must_use] pub fn section>(mut self, section: S) -> Self { self.section = Some(section.as_ref().to_os_string()); self } /// Dump data as a serializable transaction + #[must_use] pub fn serializable_deferrable(mut self) -> Self { self.serializable_deferrable = true; self } /// Use a snapshot with the specified name + #[must_use] pub fn snapshot>(mut self, snapshot: S) -> Self { self.snapshot = Some(snapshot.as_ref().to_os_string()); self } /// Use strict SQL identifier syntax + #[must_use] pub fn strict_names(mut self) -> Self { self.strict_names = true; self } /// Dump data for the named table(s) and their children + #[must_use] pub fn table_and_children>(mut self, table_and_children: S) -> Self { self.table_and_children = Some(table_and_children.as_ref().to_os_string()); self } /// Use SET SESSION AUTHORIZATION commands instead of ALTER OWNER + #[must_use] pub fn use_set_session_authorization(mut self) -> Self { self.use_set_session_authorization = true; self } /// Show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// database to connect to + #[must_use] pub fn dbname>(mut self, dbname: S) -> Self { self.dbname = Some(dbname.as_ref().to_os_string()); self } /// database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// database server port + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// database user name + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// force password prompt (should happen automatically) + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self } /// Specifies a role name to be used to create the dump + #[must_use] pub fn role>(mut self, rolename: S) -> Self { self.role = Some(rolename.as_ref().to_os_string()); self @@ -476,6 +542,7 @@ impl CommandBuilder for PgDumpBuilder { } /// Get the arguments for the command + #[allow(clippy::too_many_lines)] fn get_args(&self) -> Vec { let mut args: Vec = Vec::new(); @@ -748,7 +815,7 @@ impl CommandBuilder for PgDumpBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -756,6 +823,13 @@ impl CommandBuilder for PgDumpBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -786,6 +860,7 @@ mod tests { #[test] fn test_builder() { let command = PgDumpBuilder::new() + .env("PGDATABASE", "database") .data_only() .large_objects() .no_large_objects() @@ -849,7 +924,7 @@ mod tests { .role("role") .build(); assert_eq!( - r#"PGPASSWORD="password" "pg_dump" "--data-only" "--large-objects" "--no-large-objects" "--clean" "--create" "--extension" "extension" "--encoding" "UTF8" "--file" "file" "--format" "format" "--jobs" "jobs" "--schema" "schema" "--exclude-schema" "exclude_schema" "--no-owner" "--no-reconnect" "--schema-only" "--superuser" "superuser" "--table" "table" "--exclude-table" "exclude_table" "--verbose" "--version" "--no-privileges" "--compression" "compression" "--binary-upgrade" "--column-inserts" "--attribute-inserts" "--disable-dollar-quoting" "--disable-triggers" "--enable-row-security" "--exclude-table-data-and-children" "exclude_table_data_and_children" "--extra-float-digits" "extra_float_digits" "--if-exists" "--include-foreign-data" "include_foreign_data" "--inserts" "--load-via-partition-root" "--lock-wait-timeout" "10" "--no-comments" "--no-publications" "--no-security-labels" "--no-subscriptions" "--no-table-access-method" "--no-tablespaces" "--no-toast-compression" "--no-unlogged-table-data" "--on-conflict-do-nothing" "--quote-all-identifiers" "--rows-per-insert" "100" "--section" "section" "--serializable-deferrable" "--snapshot" "snapshot" "--strict-names" "--table-and-children" "table_and_children" "--use-set-session-authorization" "--help" "--dbname" "dbname" "--host" "localhost" "--port" "5432" "--username" "postgres" "--no-password" "--password" "--role" "role""#, + r#"PGDATABASE="database" PGPASSWORD="password" "pg_dump" "--data-only" "--large-objects" "--no-large-objects" "--clean" "--create" "--extension" "extension" "--encoding" "UTF8" "--file" "file" "--format" "format" "--jobs" "jobs" "--schema" "schema" "--exclude-schema" "exclude_schema" "--no-owner" "--no-reconnect" "--schema-only" "--superuser" "superuser" "--table" "table" "--exclude-table" "exclude_table" "--verbose" "--version" "--no-privileges" "--compression" "compression" "--binary-upgrade" "--column-inserts" "--attribute-inserts" "--disable-dollar-quoting" "--disable-triggers" "--enable-row-security" "--exclude-table-data-and-children" "exclude_table_data_and_children" "--extra-float-digits" "extra_float_digits" "--if-exists" "--include-foreign-data" "include_foreign_data" "--inserts" "--load-via-partition-root" "--lock-wait-timeout" "10" "--no-comments" "--no-publications" "--no-security-labels" "--no-subscriptions" "--no-table-access-method" "--no-tablespaces" "--no-toast-compression" "--no-unlogged-table-data" "--on-conflict-do-nothing" "--quote-all-identifiers" "--rows-per-insert" "100" "--section" "section" "--serializable-deferrable" "--snapshot" "snapshot" "--strict-names" "--table-and-children" "table_and_children" "--use-set-session-authorization" "--help" "--dbname" "dbname" "--host" "localhost" "--port" "5432" "--username" "postgres" "--no-password" "--password" "--role" "role""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_dumpall.rs b/postgresql_commands/src/pg_dumpall.rs index 7922746..68a46fe 100644 --- a/postgresql_commands/src/pg_dumpall.rs +++ b/postgresql_commands/src/pg_dumpall.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_dumpall` extracts a PostgreSQL database cluster into an SQL script file. +/// `pg_dumpall` extracts a `PostgreSQL` database cluster into an SQL script file. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct PgDumpAllBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, file: Option, verbose: bool, version: bool, @@ -58,12 +60,13 @@ pub struct PgDumpAllBuilder { } impl PgDumpAllBuilder { - /// Create a new [PgDumpAllBuilder] + /// Create a new [`PgDumpAllBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgDumpAllBuilder] from [Settings] + /// Create a new [`PgDumpAllBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -74,288 +77,336 @@ impl PgDumpAllBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// output file name + #[must_use] pub fn file>(mut self, file: S) -> Self { self.file = Some(file.as_ref().to_os_string()); self } /// verbose mode + #[must_use] pub fn verbose(mut self) -> Self { self.verbose = true; self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// fail after waiting TIMEOUT for a table lock + #[must_use] pub fn lock_wait_timeout(mut self, lock_wait_timeout: u16) -> Self { self.lock_wait_timeout = Some(lock_wait_timeout); self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// dump only the data, not the schema + #[must_use] pub fn data_only(mut self) -> Self { self.data_only = true; self } /// clean (drop) database objects before recreating them + #[must_use] pub fn clean(mut self) -> Self { self.clean = true; self } /// encoding for the dump + #[must_use] pub fn encoding>(mut self, encoding: S) -> Self { self.encoding = Some(encoding.as_ref().to_os_string()); self } /// dump only global objects, not database-specific objects + #[must_use] pub fn globals_only(mut self) -> Self { self.globals_only = true; self } /// do not output commands to set object ownership + #[must_use] pub fn no_owner(mut self) -> Self { self.no_owner = true; self } /// dump only the roles, not the role memberships or privileges + #[must_use] pub fn roles_only(mut self) -> Self { self.roles_only = true; self } /// dump only the object definitions (schema), not data + #[must_use] pub fn schema_only(mut self) -> Self { self.schema_only = true; self } /// superuser user name to use in the dump + #[must_use] pub fn superuser>(mut self, superuser: S) -> Self { self.superuser = Some(superuser.as_ref().to_os_string()); self } /// dump only the tablespace definitions + #[must_use] pub fn tablespaces_only(mut self) -> Self { self.tablespaces_only = true; self } /// do not dump object privileges (grant/revoke commands) + #[must_use] pub fn no_privileges(mut self) -> Self { self.no_privileges = true; self } /// dump in a format suitable for binary upgrade + #[must_use] pub fn binary_upgrade(mut self) -> Self { self.binary_upgrade = true; self } /// dump data as INSERT commands with column names + #[must_use] pub fn column_inserts(mut self) -> Self { self.column_inserts = true; self } /// disable dollar quoting, use SQL standard quoting + #[must_use] pub fn disable_dollar_quoting(mut self) -> Self { self.disable_dollar_quoting = true; self } /// disable triggers during data-only restore + #[must_use] pub fn disable_triggers(mut self) -> Self { self.disable_triggers = true; self } /// exclude the named database from the dump + #[must_use] pub fn exclude_database>(mut self, exclude_database: S) -> Self { self.exclude_database = Some(exclude_database.as_ref().to_os_string()); self } /// set the number of digits displayed for floating-point values + #[must_use] pub fn extra_float_digits>(mut self, extra_float_digits: S) -> Self { self.extra_float_digits = Some(extra_float_digits.as_ref().to_os_string()); self } /// use IF EXISTS when dropping objects + #[must_use] pub fn if_exists(mut self) -> Self { self.if_exists = true; self } /// dump data as proper INSERT commands + #[must_use] pub fn inserts(mut self) -> Self { self.inserts = true; self } /// load data via the partition root table + #[must_use] pub fn load_via_partition_root(mut self) -> Self { self.load_via_partition_root = true; self } /// do not dump comments + #[must_use] pub fn no_comments(mut self) -> Self { self.no_comments = true; self } /// do not dump publications + #[must_use] pub fn no_publications(mut self) -> Self { self.no_publications = true; self } /// do not dump passwords for roles + #[must_use] pub fn no_role_passwords(mut self) -> Self { self.no_role_passwords = true; self } /// do not dump security labels + #[must_use] pub fn no_security_labels(mut self) -> Self { self.no_security_labels = true; self } /// do not dump subscriptions + #[must_use] pub fn no_subscriptions(mut self) -> Self { self.no_subscriptions = true; self } /// do not wait for changes to be written safely to disk + #[must_use] pub fn no_sync(mut self) -> Self { self.no_sync = true; self } /// do not dump table access method information + #[must_use] pub fn no_table_access_method(mut self) -> Self { self.no_table_access_method = true; self } /// do not dump tablespace assignments + #[must_use] pub fn no_tablespaces(mut self) -> Self { self.no_tablespaces = true; self } /// do not dump TOAST compression information + #[must_use] pub fn no_toast_compression(mut self) -> Self { self.no_toast_compression = true; self } /// do not dump unlogged table data + #[must_use] pub fn no_unlogged_table_data(mut self) -> Self { self.no_unlogged_table_data = true; self } /// use ON CONFLICT DO NOTHING for INSERTs + #[must_use] pub fn on_conflict_do_nothing(mut self) -> Self { self.on_conflict_do_nothing = true; self } /// quote all identifiers, even if not key words + #[must_use] pub fn quote_all_identifiers(mut self) -> Self { self.quote_all_identifiers = true; self } /// set the number of rows per INSERT command + #[must_use] pub fn rows_per_insert>(mut self, rows_per_insert: S) -> Self { self.rows_per_insert = Some(rows_per_insert.as_ref().to_os_string()); self } /// use SET SESSION AUTHORIZATION commands instead of ALTER OWNER + #[must_use] pub fn use_set_session_authorization(mut self) -> Self { self.use_set_session_authorization = true; self } /// database name to connect to + #[must_use] pub fn dbname>(mut self, dbname: S) -> Self { self.dbname = Some(dbname.as_ref().to_os_string()); self } /// database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// database name to connect to + #[must_use] pub fn database>(mut self, database: S) -> Self { self.database = Some(database.as_ref().to_os_string()); self } /// database server port number + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// user name to connect as + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// force password prompt + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self } /// role name to use in the dump + #[must_use] pub fn role>(mut self, role: S) -> Self { self.role = Some(role.as_ref().to_os_string()); self @@ -374,6 +425,7 @@ impl CommandBuilder for PgDumpAllBuilder { } /// Get the arguments for the command + #[allow(clippy::too_many_lines)] fn get_args(&self) -> Vec { let mut args: Vec = Vec::new(); @@ -579,7 +631,7 @@ impl CommandBuilder for PgDumpAllBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -587,6 +639,13 @@ impl CommandBuilder for PgDumpAllBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -617,6 +676,7 @@ mod tests { #[test] fn test_builder() { let command = PgDumpAllBuilder::new() + .env("PGDATABASE", "database") .file("dump.sql") .verbose() .version() @@ -667,7 +727,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "pg_dumpall" "--file" "dump.sql" "--verbose" "--version" "--lock-wait-timeout" "10" "--help" "--data-only" "--clean" "--encoding" "UTF8" "--globals-only" "--no-owner" "--roles-only" "--schema-only" "--superuser" "postgres" "--tablespaces-only" "--no-privileges" "--binary-upgrade" "--column-inserts" "--disable-dollar-quoting" "--disable-triggers" "--exclude-database" "exclude" "--extra-float-digits" "2" "--if-exists" "--inserts" "--load-via-partition-root" "--no-comments" "--no-publications" "--no-role-passwords" "--no-security-labels" "--no-subscriptions" "--no-sync" "--no-table-access-method" "--no-tablespaces" "--no-toast-compression" "--no-unlogged-table-data" "--on-conflict-do-nothing" "--quote-all-identifiers" "--rows-per-insert" "1000" "--use-set-session-authorization" "--dbname" "postgres" "--host" "localhost" "--database" "postgres" "--port" "5432" "--username" "postgres" "--no-password" "--password" "--role" "postgres""#, + r#"PGDATABASE="database" PGPASSWORD="password" "pg_dumpall" "--file" "dump.sql" "--verbose" "--version" "--lock-wait-timeout" "10" "--help" "--data-only" "--clean" "--encoding" "UTF8" "--globals-only" "--no-owner" "--roles-only" "--schema-only" "--superuser" "postgres" "--tablespaces-only" "--no-privileges" "--binary-upgrade" "--column-inserts" "--disable-dollar-quoting" "--disable-triggers" "--exclude-database" "exclude" "--extra-float-digits" "2" "--if-exists" "--inserts" "--load-via-partition-root" "--no-comments" "--no-publications" "--no-role-passwords" "--no-security-labels" "--no-subscriptions" "--no-sync" "--no-table-access-method" "--no-tablespaces" "--no-toast-compression" "--no-unlogged-table-data" "--on-conflict-do-nothing" "--quote-all-identifiers" "--rows-per-insert" "1000" "--use-set-session-authorization" "--dbname" "postgres" "--host" "localhost" "--database" "postgres" "--port" "5432" "--username" "postgres" "--no-password" "--password" "--role" "postgres""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_isready.rs b/postgresql_commands/src/pg_isready.rs index 20d4069..1fcde53 100644 --- a/postgresql_commands/src/pg_isready.rs +++ b/postgresql_commands/src/pg_isready.rs @@ -4,10 +4,11 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_isready` issues a connection check to a PostgreSQL database. +/// `pg_isready` issues a connection check to a `PostgreSQL` database. #[derive(Clone, Debug, Default)] pub struct PgIsReadyBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, dbname: Option, quiet: bool, version: bool, @@ -19,12 +20,13 @@ pub struct PgIsReadyBuilder { } impl PgIsReadyBuilder { - /// Create a new [PgIsReadyBuilder] + /// Create a new [`PgIsReadyBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgIsReadyBuilder] from [Settings] + /// Create a new [`PgIsReadyBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -34,54 +36,63 @@ impl PgIsReadyBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// Set the database name + #[must_use] pub fn dbname>(mut self, dbname: S) -> Self { self.dbname = Some(dbname.as_ref().to_os_string()); self } /// Run quietly + #[must_use] pub fn quiet(mut self) -> Self { self.quiet = true; self } /// Output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// Show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// Set the database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// Set the database server port + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// Set the seconds to wait when attempting connection, 0 disables (default: 3) + #[must_use] pub fn timeout(mut self, timeout: u16) -> Self { self.timeout = Some(timeout); self } /// Set the user name to connect as + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self @@ -142,6 +153,18 @@ impl CommandBuilder for PgIsReadyBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -172,6 +195,7 @@ mod tests { #[test] fn test_builder() { let command = PgIsReadyBuilder::new() + .env("PGDATABASE", "database") .dbname("postgres") .quiet() .version() @@ -183,7 +207,7 @@ mod tests { .build(); assert_eq!( - r#""pg_isready" "--dbname" "postgres" "--quiet" "--version" "--help" "--host" "localhost" "--port" "5432" "--timeout" "3" "--username" "postgres""#, + r#"PGDATABASE="database" "pg_isready" "--dbname" "postgres" "--quiet" "--version" "--help" "--host" "localhost" "--port" "5432" "--timeout" "3" "--username" "postgres""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_receivewal.rs b/postgresql_commands/src/pg_receivewal.rs index b6a4b70..8c182e7 100644 --- a/postgresql_commands/src/pg_receivewal.rs +++ b/postgresql_commands/src/pg_receivewal.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_receivewal` receives PostgreSQL streaming write-ahead logs. +/// `pg_receivewal` receives `PostgreSQL` streaming write-ahead logs. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct PgReceiveWalBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, directory: Option, endpos: Option, if_not_exists: bool, @@ -32,12 +34,13 @@ pub struct PgReceiveWalBuilder { } impl PgReceiveWalBuilder { - /// Create a new [PgReceiveWalBuilder] + /// Create a new [`PgReceiveWalBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgReceiveWalBuilder] from [Settings] + /// Create a new [`PgReceiveWalBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -48,132 +51,154 @@ impl PgReceiveWalBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// receive write-ahead log files into this directory + #[must_use] pub fn directory>(mut self, directory: S) -> Self { self.directory = Some(directory.as_ref().to_os_string()); self } /// exit after receiving the specified LSN + #[must_use] pub fn endpos>(mut self, endpos: S) -> Self { self.endpos = Some(endpos.as_ref().to_os_string()); self } /// do not error if slot already exists when creating a slot + #[must_use] pub fn if_not_exists(mut self) -> Self { self.if_not_exists = true; self } /// do not loop on connection lost + #[must_use] pub fn no_loop(mut self) -> Self { self.no_loop = true; self } /// do not wait for changes to be written safely to disk + #[must_use] pub fn no_sync(mut self) -> Self { self.no_sync = true; self } /// time between status packets sent to server (default: 10) + #[must_use] pub fn status_interval>(mut self, status_interval: S) -> Self { self.status_interval = Some(status_interval.as_ref().to_os_string()); self } /// replication slot to use + #[must_use] pub fn slot>(mut self, slot: S) -> Self { self.slot = Some(slot.as_ref().to_os_string()); self } /// flush write-ahead log immediately after writing + #[must_use] pub fn synchronous(mut self) -> Self { self.synchronous = true; self } /// output verbose messages + #[must_use] pub fn verbose(mut self) -> Self { self.verbose = true; self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// compress as specified + #[must_use] pub fn compress>(mut self, compress: S) -> Self { self.compress = Some(compress.as_ref().to_os_string()); self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// connection string + #[must_use] pub fn dbname>(mut self, dbname: S) -> Self { self.dbname = Some(dbname.as_ref().to_os_string()); self } /// database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// database server port number + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// connect as specified database user + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// force password prompt (should happen automatically) + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self } /// create a new replication slot (for the slot's name see --slot) + #[must_use] pub fn create_slot(mut self) -> Self { self.create_slot = true; self } /// drop the replication slot (for the slot's name see --slot) + #[must_use] pub fn drop_slot(mut self) -> Self { self.drop_slot = true; self @@ -289,7 +314,7 @@ impl CommandBuilder for PgReceiveWalBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -297,6 +322,13 @@ impl CommandBuilder for PgReceiveWalBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -327,6 +359,7 @@ mod tests { #[test] fn test_builder() { let command = PgReceiveWalBuilder::new() + .env("PGDATABASE", "database") .directory("directory") .endpos("endpos") .if_not_exists() @@ -351,7 +384,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "pg_receivewal" "--directory" "directory" "--endpos" "endpos" "--if-not-exists" "--no-loop" "--no-sync" "--status-interval" "status_interval" "--slot" "slot" "--synchronous" "--verbose" "--version" "--compress" "compress" "--help" "--dbname" "dbname" "--host" "localhost" "--port" "5432" "--username" "username" "--no-password" "--password" "--create-slot" "--drop-slot""#, + r#"PGDATABASE="database" PGPASSWORD="password" "pg_receivewal" "--directory" "directory" "--endpos" "endpos" "--if-not-exists" "--no-loop" "--no-sync" "--status-interval" "status_interval" "--slot" "slot" "--synchronous" "--verbose" "--version" "--compress" "compress" "--help" "--dbname" "dbname" "--host" "localhost" "--port" "5432" "--username" "username" "--no-password" "--password" "--create-slot" "--drop-slot""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_recvlogical.rs b/postgresql_commands/src/pg_recvlogical.rs index 5250c77..705be31 100644 --- a/postgresql_commands/src/pg_recvlogical.rs +++ b/postgresql_commands/src/pg_recvlogical.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_recvlogical` controls PostgreSQL logical decoding streams. +/// `pg_recvlogical` controls `PostgreSQL` logical decoding streams. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct PgRecvLogicalBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, create_slot: bool, drop_slot: bool, start: bool, @@ -35,12 +37,13 @@ pub struct PgRecvLogicalBuilder { } impl PgRecvLogicalBuilder { - /// Create a new [PgRecvLogicalBuilder] + /// Create a new [`PgRecvLogicalBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgRecvLogicalBuilder] from [Settings] + /// Create a new [`PgRecvLogicalBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -51,150 +54,175 @@ impl PgRecvLogicalBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// create a new replication slot + #[must_use] pub fn create_slot(mut self) -> Self { self.create_slot = true; self } /// drop the replication slot + #[must_use] pub fn drop_slot(mut self) -> Self { self.drop_slot = true; self } /// start streaming in a replication slot + #[must_use] pub fn start(mut self) -> Self { self.start = true; self } /// exit after receiving the specified LSN + #[must_use] pub fn endpos>(mut self, endpos: S) -> Self { self.endpos = Some(endpos.as_ref().to_os_string()); self } /// receive log into this file, - for stdout + #[must_use] pub fn file>(mut self, file: S) -> Self { self.file = Some(file.as_ref().to_os_string()); self } /// time between fsyncs to the output file (default: 10) + #[must_use] pub fn fsync_interval>(mut self, fsync_interval: S) -> Self { self.fsync_interval = Some(fsync_interval.as_ref().to_os_string()); self } /// do not error if slot already exists when creating a slot + #[must_use] pub fn if_not_exists(mut self) -> Self { self.if_not_exists = true; self } /// where in an existing slot should the streaming start + #[must_use] pub fn startpos>(mut self, startpos: S) -> Self { self.startpos = Some(startpos.as_ref().to_os_string()); self } /// do not loop on connection lost + #[must_use] pub fn no_loop(mut self) -> Self { self.no_loop = true; self } /// pass option NAME with optional value VALUE to the output plugin + #[must_use] pub fn option>(mut self, option: S) -> Self { self.option = Some(option.as_ref().to_os_string()); self } - /// use output plugin PLUGIN (default: test_decoding) + /// use output plugin PLUGIN (default: `test_decoding`) + #[must_use] pub fn plugin>(mut self, plugin: S) -> Self { self.plugin = Some(plugin.as_ref().to_os_string()); self } /// time between status packets sent to server (default: 10) + #[must_use] pub fn status_interval>(mut self, status_interval: S) -> Self { self.status_interval = Some(status_interval.as_ref().to_os_string()); self } /// name of the logical replication slot + #[must_use] pub fn slot>(mut self, slot: S) -> Self { self.slot = Some(slot.as_ref().to_os_string()); self } /// enable decoding of prepared transactions when creating a slot + #[must_use] pub fn two_phase(mut self) -> Self { self.two_phase = true; self } /// output verbose messages + #[must_use] pub fn verbose(mut self) -> Self { self.verbose = true; self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// database to connect to + #[must_use] pub fn dbname>(mut self, dbname: S) -> Self { self.dbname = Some(dbname.as_ref().to_os_string()); self } /// database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// database server port number + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// connect as specified database user + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// force password prompt (should happen automatically) + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self @@ -325,7 +353,7 @@ impl CommandBuilder for PgRecvLogicalBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -333,6 +361,13 @@ impl CommandBuilder for PgRecvLogicalBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -363,6 +398,7 @@ mod tests { #[test] fn test_builder() { let command = PgRecvLogicalBuilder::new() + .env("PGDATABASE", "database") .create_slot() .drop_slot() .start() @@ -390,7 +426,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "pg_recvlogical" "--create-slot" "--drop-slot" "--start" "--endpos" "endpos" "--file" "file" "--fsync-interval" "fsync_interval" "--if-not-exists" "--startpos" "startpos" "--no-loop" "--option" "option" "--plugin" "plugin" "--status-interval" "status_interval" "--slot" "slot" "--two-phase" "--verbose" "--version" "--help" "--dbname" "dbname" "--host" "localhost" "--port" "5432" "--username" "username" "--no-password" "--password""#, + r#"PGDATABASE="database" PGPASSWORD="password" "pg_recvlogical" "--create-slot" "--drop-slot" "--start" "--endpos" "endpos" "--file" "file" "--fsync-interval" "fsync_interval" "--if-not-exists" "--startpos" "startpos" "--no-loop" "--option" "option" "--plugin" "plugin" "--status-interval" "status_interval" "--slot" "slot" "--two-phase" "--verbose" "--version" "--help" "--dbname" "dbname" "--host" "localhost" "--port" "5432" "--username" "username" "--no-password" "--password""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_resetwal.rs b/postgresql_commands/src/pg_resetwal.rs index e6a98c9..ad7859c 100644 --- a/postgresql_commands/src/pg_resetwal.rs +++ b/postgresql_commands/src/pg_resetwal.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_resetwal` resets the PostgreSQL write-ahead log. +/// `pg_resetwal` resets the `PostgreSQL` write-ahead log. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct PgResetWalBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, commit_timestamp_ids: Option<(OsString, OsString)>, pgdata: Option, epoch: Option, @@ -25,101 +27,117 @@ pub struct PgResetWalBuilder { } impl PgResetWalBuilder { - /// Create a new [PgResetWalBuilder] + /// Create a new [`PgResetWalBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgResetWalBuilder] from [Settings] + /// Create a new [`PgResetWalBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new().program_dir(settings.get_binary_dir()) } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// set oldest and newest transactions bearing commit timestamp (zero means no change) + #[must_use] pub fn commit_timestamp_ids>(mut self, xid1: S, xid2: S) -> Self { self.commit_timestamp_ids = Some((xid1.as_ref().into(), xid2.as_ref().into())); self } /// data directory + #[must_use] pub fn pgdata>(mut self, datadir: P) -> Self { self.pgdata = Some(datadir.into()); self } /// set next transaction ID epoch + #[must_use] pub fn epoch>(mut self, xidepoch: S) -> Self { self.epoch = Some(xidepoch.as_ref().to_os_string()); self } /// force update to be done + #[must_use] pub fn force(mut self) -> Self { self.force = true; self } /// set minimum starting location for new WAL + #[must_use] pub fn next_wal_file>(mut self, walfile: S) -> Self { self.next_wal_file = Some(walfile.as_ref().to_os_string()); self } /// set next and oldest multitransaction ID + #[must_use] pub fn multixact_ids>(mut self, mxid1: S, mxid2: S) -> Self { self.multixact_ids = Some((mxid1.as_ref().into(), mxid2.as_ref().into())); self } /// no update, just show what would be done + #[must_use] pub fn dry_run(mut self) -> Self { self.dry_run = true; self } /// set next OID + #[must_use] pub fn next_oid>(mut self, oid: S) -> Self { self.next_oid = Some(oid.as_ref().to_os_string()); self } /// set next multitransaction offset + #[must_use] pub fn multixact_offset>(mut self, offset: S) -> Self { self.multixact_offset = Some(offset.as_ref().to_os_string()); self } /// set oldest transaction ID + #[must_use] pub fn oldest_transaction_id>(mut self, xid: S) -> Self { self.oldest_transaction_id = Some(xid.as_ref().to_os_string()); self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// set next transaction ID + #[must_use] pub fn next_transaction_id>(mut self, xid: S) -> Self { self.next_transaction_id = Some(xid.as_ref().to_os_string()); self } /// size of WAL segments, in megabytes + #[must_use] pub fn wal_segsize>(mut self, size: S) -> Self { self.wal_segsize = Some(size.as_ref().to_os_string()); self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self @@ -209,6 +227,18 @@ impl CommandBuilder for PgResetWalBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -236,6 +266,7 @@ mod tests { #[test] fn test_builder() { let command = PgResetWalBuilder::new() + .env("PGDATABASE", "database") .commit_timestamp_ids("1", "2") .pgdata("pgdata") .epoch("epoch") @@ -253,7 +284,7 @@ mod tests { .build(); assert_eq!( - r#""pg_resetwal" "--commit-timestamp-ids" "1,2" "--pgdata" "pgdata" "--epoch" "epoch" "--force" "--next-wal-file" "next_wal_file" "--multixact-ids" "3,4" "--dry-run" "--next-oid" "next_oid" "--multixact-offset" "multixact_offset" "--oldest-transaction-id" "oldest_transaction_id" "--version" "--next-transaction-id" "next_transaction_id" "--wal-segsize" "wal_segsize" "--help""#, + r#"PGDATABASE="database" "pg_resetwal" "--commit-timestamp-ids" "1,2" "--pgdata" "pgdata" "--epoch" "epoch" "--force" "--next-wal-file" "next_wal_file" "--multixact-ids" "3,4" "--dry-run" "--next-oid" "next_oid" "--multixact-offset" "multixact_offset" "--oldest-transaction-id" "oldest_transaction_id" "--version" "--next-transaction-id" "next_transaction_id" "--wal-segsize" "wal_segsize" "--help""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_restore.rs b/postgresql_commands/src/pg_restore.rs index 1bbc03e..12b7cef 100644 --- a/postgresql_commands/src/pg_restore.rs +++ b/postgresql_commands/src/pg_restore.rs @@ -4,10 +4,13 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_restore` restores a PostgreSQL database from an archive created by pg_dump. +/// `pg_restore` restores a `PostgreSQL` database from an archive created by `pg_dump`. #[derive(Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] +#[allow(clippy::struct_excessive_bools)] pub struct PgRestoreBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, dbname: Option, file: Option, format: Option, @@ -55,12 +58,13 @@ pub struct PgRestoreBuilder { } impl PgRestoreBuilder { - /// Create a new [PgRestoreBuilder] + /// Create a new [`PgRestoreBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgRestoreBuilder] from [Settings] + /// Create a new [`PgRestoreBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -71,270 +75,315 @@ impl PgRestoreBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// connect to database name + #[must_use] pub fn dbname>(mut self, name: S) -> Self { self.dbname = Some(name.as_ref().to_os_string()); self } /// output file name (- for stdout) + #[must_use] pub fn file>(mut self, filename: S) -> Self { self.file = Some(filename.as_ref().to_os_string()); self } /// backup file format (should be automatic) + #[must_use] pub fn format>(mut self, format: S) -> Self { self.format = Some(format.as_ref().to_os_string()); self } /// print summarized TOC of the archive + #[must_use] pub fn list(mut self) -> Self { self.list = true; self } /// verbose mode + #[must_use] pub fn verbose(mut self) -> Self { self.verbose = true; self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// restore only the data, no schema + #[must_use] pub fn data_only(mut self) -> Self { self.data_only = true; self } /// clean (drop) database objects before recreating + #[must_use] pub fn clean(mut self) -> Self { self.clean = true; self } /// create the target database + #[must_use] pub fn create(mut self) -> Self { self.create = true; self } /// exit on error, default is to continue + #[must_use] pub fn exit_on_error(mut self) -> Self { self.exit_on_error = true; self } /// restore named index + #[must_use] pub fn index>(mut self, name: S) -> Self { self.index = Some(name.as_ref().to_os_string()); self } /// use this many parallel jobs to restore + #[must_use] pub fn jobs>(mut self, num: S) -> Self { self.jobs = Some(num.as_ref().to_os_string()); self } /// use table of contents from this file for selecting/ordering output + #[must_use] pub fn use_list>(mut self, filename: S) -> Self { self.use_list = Some(filename.as_ref().to_os_string()); self } /// restore only objects in this schema + #[must_use] pub fn schema>(mut self, name: S) -> Self { self.schema = Some(name.as_ref().to_os_string()); self } /// do not restore objects in this schema + #[must_use] pub fn exclude_schema>(mut self, name: S) -> Self { self.exclude_schema = Some(name.as_ref().to_os_string()); self } /// skip restoration of object ownership + #[must_use] pub fn no_owner(mut self) -> Self { self.no_owner = true; self } /// restore named function + #[must_use] pub fn function>(mut self, name: S) -> Self { self.function = Some(name.as_ref().to_os_string()); self } /// restore only the schema, no data + #[must_use] pub fn schema_only(mut self) -> Self { self.schema_only = true; self } /// superuser user name to use for disabling triggers + #[must_use] pub fn superuser>(mut self, name: S) -> Self { self.superuser = Some(name.as_ref().to_os_string()); self } /// restore named relation (table, view, etc.) + #[must_use] pub fn table>(mut self, name: S) -> Self { self.table = Some(name.as_ref().to_os_string()); self } /// restore named trigger + #[must_use] pub fn trigger>(mut self, name: S) -> Self { self.trigger = Some(name.as_ref().to_os_string()); self } /// skip restoration of access privileges (grant/revoke) + #[must_use] pub fn no_privileges(mut self) -> Self { self.no_privileges = true; self } /// restore as a single transaction + #[must_use] pub fn single_transaction(mut self) -> Self { self.single_transaction = true; self } /// disable triggers during data-only restore + #[must_use] pub fn disable_triggers(mut self) -> Self { self.disable_triggers = true; self } /// enable row security + #[must_use] pub fn enable_row_security(mut self) -> Self { self.enable_row_security = true; self } /// use IF EXISTS when dropping objects + #[must_use] pub fn if_exists(mut self) -> Self { self.if_exists = true; self } /// do not restore comments + #[must_use] pub fn no_comments(mut self) -> Self { self.no_comments = true; self } /// do not restore data of tables that could not be created + #[must_use] pub fn no_data_for_failed_tables(mut self) -> Self { self.no_data_for_failed_tables = true; self } /// do not restore publications + #[must_use] pub fn no_publications(mut self) -> Self { self.no_publications = true; self } /// do not restore security labels + #[must_use] pub fn no_security_labels(mut self) -> Self { self.no_security_labels = true; self } /// do not restore subscriptions + #[must_use] pub fn no_subscriptions(mut self) -> Self { self.no_subscriptions = true; self } /// do not restore table access methods + #[must_use] pub fn no_table_access_method(mut self) -> Self { self.no_table_access_method = true; self } /// do not restore tablespace assignments + #[must_use] pub fn no_tablespaces(mut self) -> Self { self.no_tablespaces = true; self } /// restore named section (pre-data, data, or post-data) + #[must_use] pub fn section>(mut self, section: S) -> Self { self.section = Some(section.as_ref().to_os_string()); self } /// require table and/or schema include patterns to match at least one entity each + #[must_use] pub fn strict_names(mut self) -> Self { self.strict_names = true; self } /// use SET SESSION AUTHORIZATION commands instead of ALTER OWNER commands to set ownership + #[must_use] pub fn use_set_session_authorization(mut self) -> Self { self.use_set_session_authorization = true; self } /// database server host or socket directory + #[must_use] pub fn host>(mut self, hostname: S) -> Self { self.host = Some(hostname.as_ref().to_os_string()); self } /// database server port number + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// connect as specified database user + #[must_use] pub fn username>(mut self, name: S) -> Self { self.username = Some(name.as_ref().to_os_string()); self } /// never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// force password prompt (should happen automatically) + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self } /// do SET ROLE before restore + #[must_use] pub fn role>(mut self, rolename: S) -> Self { self.role = Some(rolename.as_ref().to_os_string()); self @@ -353,6 +402,7 @@ impl CommandBuilder for PgRestoreBuilder { } /// Get the arguments for the command + #[allow(clippy::too_many_lines)] fn get_args(&self) -> Vec { let mut args: Vec = Vec::new(); @@ -550,7 +600,7 @@ impl CommandBuilder for PgRestoreBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -558,6 +608,13 @@ impl CommandBuilder for PgRestoreBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -588,6 +645,7 @@ mod tests { #[test] fn test_builder() { let command = PgRestoreBuilder::new() + .env("PGDATABASE", "database") .dbname("dbname") .file("file") .format("format") @@ -635,7 +693,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "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""#, + r#"PGDATABASE="database" PGPASSWORD="password" "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""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_rewind.rs b/postgresql_commands/src/pg_rewind.rs index 57f2f8a..32e72c8 100644 --- a/postgresql_commands/src/pg_rewind.rs +++ b/postgresql_commands/src/pg_rewind.rs @@ -4,10 +4,13 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_rewind` synchronizes a PostgreSQL data directory with another data directory. +/// `pg_rewind` synchronizes a `PostgreSQL` data directory with another data directory. #[derive(Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] +#[allow(clippy::struct_excessive_bools)] pub struct PgRewindBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, restore_target_wal: bool, target_pgdata: Option, source_pgdata: Option, @@ -24,95 +27,110 @@ pub struct PgRewindBuilder { } impl PgRewindBuilder { - /// Create a new [PgRewindBuilder] + /// Create a new [`PgRewindBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgRewindBuilder] from [Settings] + /// Create a new [`PgRewindBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new().program_dir(settings.get_binary_dir()) } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } - /// use restore_command in target configuration to retrieve WAL files from archives + /// use `restore_command` in target configuration to retrieve WAL files from archives + #[must_use] pub fn restore_target_wal(mut self) -> Self { self.restore_target_wal = true; self } /// existing data directory to modify + #[must_use] pub fn target_pgdata>(mut self, directory: P) -> Self { self.target_pgdata = Some(directory.into()); self } /// source data directory to synchronize with + #[must_use] pub fn source_pgdata>(mut self, directory: P) -> Self { self.source_pgdata = Some(directory.into()); self } /// source server to synchronize with + #[must_use] pub fn source_server>(mut self, connstr: S) -> Self { self.source_server = Some(connstr.as_ref().to_os_string()); self } /// stop before modifying anything + #[must_use] pub fn dry_run(mut self) -> Self { self.dry_run = true; self } /// do not wait for changes to be written safely to disk + #[must_use] pub fn no_sync(mut self) -> Self { self.no_sync = true; self } /// write progress messages + #[must_use] pub fn progress(mut self) -> Self { self.progress = true; self } /// write configuration for replication (requires --source-server) + #[must_use] pub fn write_recovery_conf(mut self) -> Self { self.write_recovery_conf = true; self } /// use specified main server configuration file when running target cluster + #[must_use] pub fn config_file>(mut self, filename: S) -> Self { self.config_file = Some(filename.as_ref().to_os_string()); self } /// write a lot of debug messages + #[must_use] pub fn debug(mut self) -> Self { self.debug = true; self } /// do not automatically fix unclean shutdown + #[must_use] pub fn no_ensure_shutdown(mut self) -> Self { self.no_ensure_shutdown = true; self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self @@ -192,6 +210,18 @@ impl CommandBuilder for PgRewindBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -219,6 +249,7 @@ mod tests { #[test] fn test_builder() { let command = PgRewindBuilder::new() + .env("PGDATABASE", "database") .restore_target_wal() .target_pgdata("target_pgdata") .source_pgdata("source_pgdata") @@ -235,7 +266,7 @@ mod tests { .build(); assert_eq!( - r#""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""#, + r#"PGDATABASE="database" "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""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_test_fsync.rs b/postgresql_commands/src/pg_test_fsync.rs index b1bde82..c5b8a81 100644 --- a/postgresql_commands/src/pg_test_fsync.rs +++ b/postgresql_commands/src/pg_test_fsync.rs @@ -4,38 +4,44 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_test_fsync` command to determine fastest wal_sync_method for PostgreSQL +/// `pg_test_fsync` command to determine fastest `wal_sync_method` for `PostgreSQL` #[derive(Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] pub struct PgTestFsyncBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, filename: Option, secs_per_test: Option, } impl PgTestFsyncBuilder { - /// Create a new [PgTestFsyncBuilder] + /// Create a new [`PgTestFsyncBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgTestFsyncBuilder] from [Settings] + /// Create a new [`PgTestFsyncBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new().program_dir(settings.get_binary_dir()) } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// Set the filename + #[must_use] pub fn filename>(mut self, filename: S) -> Self { self.filename = Some(filename.as_ref().to_os_string()); self } /// Set the seconds per test + #[must_use] pub fn secs_per_test(mut self, secs: usize) -> Self { self.secs_per_test = Some(secs); self @@ -69,6 +75,18 @@ impl CommandBuilder for PgTestFsyncBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -96,12 +114,13 @@ mod tests { #[test] fn test_builder() { let command = PgTestFsyncBuilder::new() + .env("PGDATABASE", "database") .filename("filename") .secs_per_test(10) .build(); assert_eq!( - r#""pg_test_fsync" "-f" "filename" "-s" "10""#, + r#"PGDATABASE="database" "pg_test_fsync" "-f" "filename" "-s" "10""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_test_timing.rs b/postgresql_commands/src/pg_test_timing.rs index 3720fef..4700d32 100644 --- a/postgresql_commands/src/pg_test_timing.rs +++ b/postgresql_commands/src/pg_test_timing.rs @@ -4,31 +4,36 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_test_timing` tests the timing of a PostgreSQL instance. +/// `pg_test_timing` tests the timing of a `PostgreSQL` instance. #[derive(Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] pub struct PgTestTimingBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, duration: Option, } impl PgTestTimingBuilder { - /// Create a new [PgTestTimingBuilder] + /// Create a new [`PgTestTimingBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgTestTimingBuilder] from [Settings] + /// Create a new [`PgTestTimingBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new().program_dir(settings.get_binary_dir()) } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// set the duration for the test + #[must_use] pub fn duration>(mut self, duration: S) -> Self { self.duration = Some(duration.as_ref().to_os_string()); self @@ -57,6 +62,18 @@ impl CommandBuilder for PgTestTimingBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -83,8 +100,14 @@ mod tests { #[test] fn test_builder() { - let command = PgTestTimingBuilder::new().duration("10").build(); + let command = PgTestTimingBuilder::new() + .env("PGDATABASE", "database") + .duration("10") + .build(); - assert_eq!(r#""pg_test_timing" "-d" "10""#, command.to_command_string()); + assert_eq!( + r#"PGDATABASE="database" "pg_test_timing" "-d" "10""#, + command.to_command_string() + ); } } diff --git a/postgresql_commands/src/pg_upgrade.rs b/postgresql_commands/src/pg_upgrade.rs index a2f1e03..13bca1b 100644 --- a/postgresql_commands/src/pg_upgrade.rs +++ b/postgresql_commands/src/pg_upgrade.rs @@ -4,10 +4,13 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_upgrade` upgrades a PostgreSQL cluster to a different major version. +/// `pg_upgrade` upgrades a `PostgreSQL` cluster to a different major version. #[derive(Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] +#[allow(clippy::struct_excessive_bools)] pub struct PgUpgradeBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, old_bindir: Option, new_bindir: Option, check: bool, @@ -31,137 +34,159 @@ pub struct PgUpgradeBuilder { } impl PgUpgradeBuilder { - /// Create a new [PgUpgradeBuilder] + /// Create a new [`PgUpgradeBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgUpgradeBuilder] from [Settings] + /// Create a new [`PgUpgradeBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new().program_dir(settings.get_binary_dir()) } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// old cluster executable directory + #[must_use] pub fn old_bindir>(mut self, old_bindir: S) -> Self { self.old_bindir = Some(old_bindir.as_ref().to_os_string()); self } /// new cluster executable directory + #[must_use] pub fn new_bindir>(mut self, new_bindir: S) -> Self { self.new_bindir = Some(new_bindir.as_ref().to_os_string()); self } /// check clusters only, don't change any data + #[must_use] pub fn check(mut self) -> Self { self.check = true; self } /// old cluster data directory + #[must_use] pub fn old_datadir>(mut self, old_datadir: S) -> Self { self.old_datadir = Some(old_datadir.as_ref().to_os_string()); self } /// new cluster data directory + #[must_use] pub fn new_datadir>(mut self, new_datadir: S) -> Self { self.new_datadir = Some(new_datadir.as_ref().to_os_string()); self } /// number of simultaneous processes or threads to use + #[must_use] pub fn jobs>(mut self, jobs: S) -> Self { self.jobs = Some(jobs.as_ref().to_os_string()); self } /// link instead of copying files to new cluster + #[must_use] pub fn link(mut self) -> Self { self.link = true; self } /// do not wait for changes to be written safely to disk + #[must_use] pub fn no_sync(mut self) -> Self { self.no_sync = true; self } /// old cluster options to pass to the server + #[must_use] pub fn old_options>(mut self, old_options: S) -> Self { self.old_options = Some(old_options.as_ref().to_os_string()); self } /// new cluster options to pass to the server + #[must_use] pub fn new_options>(mut self, new_options: S) -> Self { self.new_options = Some(new_options.as_ref().to_os_string()); self } /// old cluster port number + #[must_use] pub fn old_port(mut self, old_port: u16) -> Self { self.old_port = Some(old_port); self } /// new cluster port number + #[must_use] pub fn new_port(mut self, new_port: u16) -> Self { self.new_port = Some(new_port); self } /// retain SQL and log files after success + #[must_use] pub fn retain(mut self) -> Self { self.retain = true; self } /// socket directory to use + #[must_use] pub fn socketdir>(mut self, socketdir: S) -> Self { self.socketdir = Some(socketdir.as_ref().to_os_string()); self } /// cluster superuser + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// enable verbose internal logging + #[must_use] pub fn verbose(mut self) -> Self { self.verbose = true; self } /// display version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// clone instead of copying files to new cluster + #[must_use] pub fn clone(mut self) -> Self { self.clone = true; self } /// copy files to new cluster + #[must_use] pub fn copy(mut self) -> Self { self.copy = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self @@ -276,6 +301,18 @@ impl CommandBuilder for PgUpgradeBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -303,6 +340,7 @@ mod tests { #[test] fn test_builder() { let command = PgUpgradeBuilder::new() + .env("PGDATABASE", "database") .old_bindir("old") .new_bindir("new") .check() @@ -326,7 +364,7 @@ mod tests { .build(); assert_eq!( - r#""pg_upgrade" "--old-bindir" "old" "--new-bindir" "new" "--check" "--old-datadir" "old_data" "--new-datadir" "new_data" "--jobs" "10" "--link" "--no-sync" "--old-options" "old" "--new-options" "new" "--old-port" "5432" "--new-port" "5433" "--retain" "--socketdir" "socket" "--username" "user" "--verbose" "--version" "--clone" "--copy" "--help""#, + r#"PGDATABASE="database" "pg_upgrade" "--old-bindir" "old" "--new-bindir" "new" "--check" "--old-datadir" "old_data" "--new-datadir" "new_data" "--jobs" "10" "--link" "--no-sync" "--old-options" "old" "--new-options" "new" "--old-port" "5432" "--new-port" "5433" "--retain" "--socketdir" "socket" "--username" "user" "--verbose" "--version" "--clone" "--copy" "--help""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_verifybackup.rs b/postgresql_commands/src/pg_verifybackup.rs index fb55503..f61f245 100644 --- a/postgresql_commands/src/pg_verifybackup.rs +++ b/postgresql_commands/src/pg_verifybackup.rs @@ -6,8 +6,10 @@ use std::path::PathBuf; /// `pg_verifybackup` verifies a backup against the backup manifest. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct PgVerifyBackupBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, exit_on_error: bool, ignore: Option, manifest_path: Option, @@ -21,77 +23,89 @@ pub struct PgVerifyBackupBuilder { } impl PgVerifyBackupBuilder { - /// Create a new [PgVerifyBackupBuilder] + /// Create a new [`PgVerifyBackupBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgVerifyBackupBuilder] from [Settings] + /// Create a new [`PgVerifyBackupBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new().program_dir(settings.get_binary_dir()) } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// exit immediately on error + #[must_use] pub fn exit_on_error(mut self) -> Self { self.exit_on_error = true; self } /// ignore indicated path + #[must_use] pub fn ignore>(mut self, ignore: S) -> Self { self.ignore = Some(ignore.as_ref().to_os_string()); self } /// use specified path for manifest + #[must_use] pub fn manifest_path>(mut self, manifest_path: S) -> Self { self.manifest_path = Some(manifest_path.as_ref().to_os_string()); self } /// do not try to parse WAL files + #[must_use] pub fn no_parse_wal(mut self) -> Self { self.no_parse_wal = true; self } /// show progress information + #[must_use] pub fn progress(mut self) -> Self { self.progress = true; self } /// do not print any output, except for errors + #[must_use] pub fn quiet(mut self) -> Self { self.quiet = true; self } /// skip checksum verification + #[must_use] pub fn skip_checksums(mut self) -> Self { self.skip_checksums = true; self } /// use specified path for WAL files + #[must_use] pub fn wal_directory>(mut self, wal_directory: S) -> Self { self.wal_directory = Some(wal_directory.as_ref().to_os_string()); self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self @@ -158,6 +172,18 @@ impl CommandBuilder for PgVerifyBackupBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -185,6 +211,7 @@ mod tests { #[test] fn test_builder() { let command = PgVerifyBackupBuilder::new() + .env("PGDATABASE", "database") .exit_on_error() .ignore("ignore") .manifest_path("manifest-path") @@ -198,7 +225,7 @@ mod tests { .build(); assert_eq!( - r#""pg_verifybackup" "--exit-on-error" "--ignore" "ignore" "--manifest-path" "manifest-path" "--no-parse-wal" "--progress" "--quiet" "--skip-checksums" "--wal-directory" "wal_directory" "--version" "--help""#, + r#"PGDATABASE="database" "pg_verifybackup" "--exit-on-error" "--ignore" "ignore" "--manifest-path" "manifest-path" "--no-parse-wal" "--progress" "--quiet" "--skip-checksums" "--wal-directory" "wal_directory" "--version" "--help""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pg_waldump.rs b/postgresql_commands/src/pg_waldump.rs index de0c373..71e7803 100644 --- a/postgresql_commands/src/pg_waldump.rs +++ b/postgresql_commands/src/pg_waldump.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pg_waldump` decodes and displays PostgreSQL write-ahead logs for debugging. +/// `pg_waldump` decodes and displays `PostgreSQL` write-ahead logs for debugging. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct PgWalDumpBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, backkup_details: bool, block: Option, end: Option, @@ -29,125 +31,145 @@ pub struct PgWalDumpBuilder { } impl PgWalDumpBuilder { - /// Create a new [PgWalDumpBuilder] + /// Create a new [`PgWalDumpBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgWalDumpBuilder] from [Settings] + /// Create a new [`PgWalDumpBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new().program_dir(settings.get_binary_dir()) } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// output detailed information about backup blocks + #[must_use] pub fn backup_details(mut self) -> Self { self.backkup_details = true; self } /// with --relation, only show records that modify block N + #[must_use] pub fn block>(mut self, block: S) -> Self { self.block = Some(block.as_ref().to_os_string()); self } /// stop reading at WAL location RECPTR + #[must_use] pub fn end>(mut self, end: S) -> Self { self.end = Some(end.as_ref().to_os_string()); self } /// keep retrying after reaching end of WAL + #[must_use] pub fn follow(mut self) -> Self { self.follow = true; self } /// only show records that modify blocks in fork FORK + #[must_use] pub fn fork>(mut self, fork: S) -> Self { self.fork = Some(fork.as_ref().to_os_string()); self } /// number of records to display + #[must_use] pub fn limit>(mut self, limit: S) -> Self { self.limit = Some(limit.as_ref().to_os_string()); self } /// directory in which to find WAL segment files + #[must_use] pub fn path>(mut self, path: S) -> Self { self.path = Some(path.as_ref().to_os_string()); self } /// do not print any output, except for errors + #[must_use] pub fn quiet(mut self) -> Self { self.quiet = true; self } /// only show records generated by resource manager RMGR + #[must_use] pub fn rmgr>(mut self, rmgr: S) -> Self { self.rmgr = Some(rmgr.as_ref().to_os_string()); self } /// only show records that modify blocks in relation T/D/R + #[must_use] pub fn relation>(mut self, relation: S) -> Self { self.relation = Some(relation.as_ref().to_os_string()); self } /// start reading at WAL location RECPTR + #[must_use] pub fn start>(mut self, start: S) -> Self { self.start = Some(start.as_ref().to_os_string()); self } /// timeline from which to read WAL records + #[must_use] pub fn timeline>(mut self, timeline: S) -> Self { self.timeline = Some(timeline.as_ref().to_os_string()); self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// only show records with a full page write + #[must_use] pub fn fullpage(mut self) -> Self { self.fullpage = true; self } /// only show records with transaction ID XID + #[must_use] pub fn xid>(mut self, xid: S) -> Self { self.xid = Some(xid.as_ref().to_os_string()); self } /// show statistics instead of records + #[must_use] pub fn stats>(mut self, stats: S) -> Self { self.stats = Some(stats.as_ref().to_os_string()); self } /// save full page images to DIR + #[must_use] pub fn save_fullpage>(mut self, save_fullpage: S) -> Self { self.save_fullpage = Some(save_fullpage.as_ref().to_os_string()); self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self @@ -255,6 +277,18 @@ impl CommandBuilder for PgWalDumpBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -282,6 +316,7 @@ mod tests { #[test] fn test_builder() { let command = PgWalDumpBuilder::new() + .env("PGDATABASE", "database") .backup_details() .block("block") .end("end") @@ -303,7 +338,7 @@ mod tests { .build(); assert_eq!( - r#""pg_waldump" "--bkp-details" "--block" "block" "--end" "end" "--follow" "--fork" "fork" "--limit" "limit" "--path" "path" "--quiet" "--rmgr" "rmgr" "--relation" "relation" "--start" "start" "--timeline" "timeline" "--version" "--fullpage" "--xid" "xid" "--stats" "stats" "--save-fullpage" "save_fullpage" "--help""#, + r#"PGDATABASE="database" "pg_waldump" "--bkp-details" "--block" "block" "--end" "end" "--follow" "--fork" "fork" "--limit" "limit" "--path" "path" "--quiet" "--rmgr" "rmgr" "--relation" "relation" "--start" "start" "--timeline" "timeline" "--version" "--fullpage" "--xid" "xid" "--stats" "stats" "--save-fullpage" "save_fullpage" "--help""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/pgbench.rs b/postgresql_commands/src/pgbench.rs index c57414d..1407382 100644 --- a/postgresql_commands/src/pgbench.rs +++ b/postgresql_commands/src/pgbench.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `pgbench` is a benchmarking tool for PostgreSQL. +/// `pgbench` is a benchmarking tool for `PostgreSQL`. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct PgBenchBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, initialize: bool, init_steps: Option, fill_factor: Option, @@ -57,12 +59,13 @@ pub struct PgBenchBuilder { } impl PgBenchBuilder { - /// Create a new [PgBenchBuilder] + /// Create a new [`PgBenchBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PgBenchBuilder] from [Settings] + /// Create a new [`PgBenchBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -72,282 +75,329 @@ impl PgBenchBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// invokes initialization mode + #[must_use] pub fn initialize(mut self) -> Self { self.initialize = true; self } /// run selected initialization steps + #[must_use] pub fn init_steps>(mut self, steps: S) -> Self { self.init_steps = Some(steps.as_ref().to_os_string()); self } /// set fill factor + #[must_use] pub fn fill_factor(mut self, factor: usize) -> Self { self.fill_factor = Some(factor); self } /// do not run VACUUM during initialization + #[must_use] pub fn no_vacuum(mut self) -> Self { self.no_vacuum = true; self } /// quiet logging (one message each 5 seconds) + #[must_use] pub fn quiet(mut self) -> Self { self.quiet = true; self } /// scaling factor + #[must_use] pub fn scale(mut self, scale: usize) -> Self { self.scale = Some(scale); self } /// create foreign key constraints between tables + #[must_use] pub fn foreign_keys(mut self) -> Self { self.foreign_keys = true; self } /// create indexes in the specified tablespace + #[must_use] pub fn index_tablespace>(mut self, tablespace: S) -> Self { self.index_tablespace = Some(tablespace.as_ref().to_os_string()); self } - /// partition pgbench_accounts with this method (default: range) + /// partition `pgbench_accounts` with this method (default: range) + #[must_use] pub fn partition_method>(mut self, method: S) -> Self { self.partition_method = Some(method.as_ref().to_os_string()); self } - /// partition pgbench_accounts into NUM parts (default: 0) + /// partition `pgbench_accounts` into NUM parts (default: 0) + #[must_use] pub fn partitions(mut self, num: usize) -> Self { self.partitions = Some(num); self } /// create tables in the specified tablespace + #[must_use] pub fn tablespace>(mut self, tablespace: S) -> Self { self.tablespace = Some(tablespace.as_ref().to_os_string()); self } /// create tables as unlogged tables + #[must_use] pub fn unlogged_tables(mut self) -> Self { self.unlogged_tables = true; self } /// add builtin script NAME weighted at W (default: 1) + #[must_use] pub fn builtin>(mut self, name: S) -> Self { self.builtin = Some(name.as_ref().to_os_string()); self } /// add script FILENAME weighted at W (default: 1) + #[must_use] pub fn file>(mut self, filename: S) -> Self { self.file = Some(filename.as_ref().to_os_string()); self } - /// skip updates of pgbench_tellers and pgbench_branches + /// skip some updates + #[must_use] pub fn skip_some_updates(mut self) -> Self { self.skip_some_updates = true; self } /// perform SELECT-only transactions + #[must_use] pub fn select_only(mut self) -> Self { self.select_only = true; self } /// number of concurrent database clients (default: 1) + #[must_use] pub fn client(mut self, num: usize) -> Self { self.client = Some(num); self } /// establish new connection for each transaction + #[must_use] pub fn connect(mut self) -> Self { self.connect = true; self } /// define variable for use by custom script + #[must_use] pub fn define>(mut self, var: S) -> Self { self.define = Some(var.as_ref().to_os_string()); self } /// number of threads (default: 1) + #[must_use] pub fn jobs(mut self, num: usize) -> Self { self.jobs = Some(num); self } /// write transaction times to log file + #[must_use] pub fn log(mut self) -> Self { self.log = true; self } /// count transactions lasting more than NUM ms as late + #[must_use] pub fn latency_limit(mut self, num: usize) -> Self { self.latency_limit = Some(num); self } /// protocol for submitting queries (default: simple) + #[must_use] pub fn protocol>(mut self, protocol: S) -> Self { self.protocol = Some(protocol.as_ref().to_os_string()); self } /// do not run VACUUM before tests + #[must_use] pub fn no_vacuum_bench(mut self) -> Self { self.no_vacuum_bench = true; self } /// show thread progress report every NUM seconds + #[must_use] pub fn progress(mut self, num: usize) -> Self { self.progress = Some(num); self } /// report latencies, failures, and retries per command + #[must_use] pub fn report_per_command(mut self) -> Self { self.report_per_command = true; self } /// target rate in transactions per second + #[must_use] pub fn rate(mut self, num: usize) -> Self { self.rate = Some(num); self } /// report this scale factor in output + #[must_use] pub fn scale_bench(mut self, scale: usize) -> Self { self.scale_bench = Some(scale); self } /// number of transactions each client runs (default: 10) + #[must_use] pub fn transactions(mut self, num: usize) -> Self { self.transactions = Some(num); self } /// duration of benchmark test in seconds + #[must_use] pub fn time(mut self, num: usize) -> Self { self.time = Some(num); self } /// vacuum all four standard tables before tests + #[must_use] pub fn vacuum_all(mut self) -> Self { self.vacuum_all = true; self } /// aggregate data over NUM seconds + #[must_use] pub fn aggregate_interval(mut self, num: usize) -> Self { self.aggregate_interval = Some(num); self } /// report the failures grouped by basic types + #[must_use] pub fn failures_detailed(mut self) -> Self { self.failures_detailed = true; self } /// prefix for transaction time log file + #[must_use] pub fn log_prefix>(mut self, prefix: S) -> Self { self.log_prefix = Some(prefix.as_ref().to_os_string()); self } /// max number of tries to run transaction (default: 1) + #[must_use] pub fn max_tries(mut self, num: usize) -> Self { self.max_tries = Some(num); self } /// use Unix epoch timestamps for progress + #[must_use] pub fn progress_timestamp(mut self) -> Self { self.progress_timestamp = true; self } /// set random seed ("time", "rand", integer) + #[must_use] pub fn random_seed>(mut self, seed: S) -> Self { self.random_seed = Some(seed.as_ref().to_os_string()); self } /// fraction of transactions to log (e.g., 0.01 for 1%) + #[must_use] pub fn sampling_rate(mut self, rate: f64) -> Self { self.sampling_rate = Some(rate); self } /// show builtin script code, then exit + #[must_use] pub fn show_script>(mut self, name: S) -> Self { self.show_script = Some(name.as_ref().to_os_string()); self } /// print messages of all errors + #[must_use] pub fn verbose_errors(mut self) -> Self { self.verbose_errors = true; self } /// print debugging output + #[must_use] pub fn debug(mut self) -> Self { self.debug = true; self } /// database server host or socket directory + #[must_use] pub fn host>(mut self, hostname: S) -> Self { self.host = Some(hostname.as_ref().to_os_string()); self } /// database server port number + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// connect as specified database user + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self @@ -366,6 +416,7 @@ impl CommandBuilder for PgBenchBuilder { } /// Get the arguments for the command + #[allow(clippy::too_many_lines)] fn get_args(&self) -> Vec { let mut args: Vec = Vec::new(); @@ -583,6 +634,18 @@ impl CommandBuilder for PgBenchBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -613,6 +676,7 @@ mod tests { #[test] fn test_builder() { let command = PgBenchBuilder::new() + .env("PGDATABASE", "database") .initialize() .init_steps("steps") .fill_factor(10) @@ -662,7 +726,7 @@ mod tests { .build(); assert_eq!( - r#""pgbench" "--initialize" "--init-steps" "steps" "--fillfactor" "10" "--no-vacuum" "--quiet" "--scale" "10" "--foreign-keys" "--index-tablespace" "tablespace" "--partition-method" "method" "--partitions" "10" "--tablespace" "tablespace" "--unlogged-tables" "--builtin" "name" "--file" "filename" "--skip-some-updates" "--select-only" "--client" "10" "--connect" "--define" "var" "--jobs" "10" "--log" "--latency-limit" "10" "--protocol" "protocol" "--no-vacuum" "--progress" "10" "--report-per-command" "--rate" "10" "--scale" "10" "--transactions" "10" "--time" "10" "--vacuum-all" "--aggregate-interval" "10" "--failures-detailed" "--log-prefix" "prefix" "--max-tries" "10" "--progress-timestamp" "--random-seed" "seed" "--sampling-rate" "10" "--show-script" "name" "--verbose-errors" "--debug" "--host" "localhost" "--port" "5432" "--username" "username" "--version" "--help""#, + r#"PGDATABASE="database" "pgbench" "--initialize" "--init-steps" "steps" "--fillfactor" "10" "--no-vacuum" "--quiet" "--scale" "10" "--foreign-keys" "--index-tablespace" "tablespace" "--partition-method" "method" "--partitions" "10" "--tablespace" "tablespace" "--unlogged-tables" "--builtin" "name" "--file" "filename" "--skip-some-updates" "--select-only" "--client" "10" "--connect" "--define" "var" "--jobs" "10" "--log" "--latency-limit" "10" "--protocol" "protocol" "--no-vacuum" "--progress" "10" "--report-per-command" "--rate" "10" "--scale" "10" "--transactions" "10" "--time" "10" "--vacuum-all" "--aggregate-interval" "10" "--failures-detailed" "--log-prefix" "prefix" "--max-tries" "10" "--progress-timestamp" "--random-seed" "seed" "--sampling-rate" "10" "--show-script" "name" "--verbose-errors" "--debug" "--host" "localhost" "--port" "5432" "--username" "username" "--version" "--help""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/postgres.rs b/postgresql_commands/src/postgres.rs index a587369..9a725d4 100644 --- a/postgresql_commands/src/postgres.rs +++ b/postgresql_commands/src/postgres.rs @@ -3,10 +3,13 @@ use crate::Settings; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `postgres` is the PostgreSQL server. +/// `postgres` is the `PostgreSQL` server. #[derive(Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] +#[allow(clippy::struct_excessive_bools)] pub struct PostgresBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, n_buffers: Option, runtime_param: Option<(OsString, OsString)>, print_runtime_param: Option, @@ -41,12 +44,13 @@ pub struct PostgresBuilder { } impl PostgresBuilder { - /// Create a new [PostgresBuilder] + /// Create a new [`PostgresBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PostgresBuilder] from [Settings] + /// Create a new [`PostgresBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -55,192 +59,224 @@ impl PostgresBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// number of shared buffers + #[must_use] pub fn n_buffers(mut self, n_buffers: u32) -> Self { self.n_buffers = Some(n_buffers); self } /// set run-time parameter + #[must_use] pub fn runtime_param>(mut self, name: S, value: S) -> Self { self.runtime_param = Some((name.as_ref().into(), value.as_ref().into())); self } /// print value of run-time parameter, then exit + #[must_use] pub fn print_runtime_param>(mut self, name: S) -> Self { self.print_runtime_param = Some(name.as_ref().to_os_string()); self } /// debugging level + #[must_use] pub fn debugging_level(mut self, level: u8) -> Self { self.debugging_level = Some(level); self } /// database directory + #[must_use] pub fn data_dir>(mut self, dir: P) -> Self { self.data_dir = Some(dir.into()); self } /// use European date input format (DMY) + #[must_use] pub fn european_date_format(mut self) -> Self { self.european_date_format = true; self } /// turn fsync off + #[must_use] pub fn fsync_off(mut self) -> Self { self.fsync_off = true; self } /// host name or IP address to listen on + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// enable TCP/IP connections (deprecated) + #[must_use] pub fn tcp_ip_connections(mut self) -> Self { self.tcp_ip_connections = true; self } /// Unix-domain socket location + #[must_use] pub fn socket_location>(mut self, dir: P) -> Self { self.socket_location = Some(dir.into()); self } /// maximum number of allowed connections + #[must_use] pub fn max_connections(mut self, max: u32) -> Self { self.max_connections = Some(max); self } /// port number to listen on + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// show statistics after each query + #[must_use] pub fn show_stats(mut self) -> Self { self.show_stats = true; self } /// set amount of memory for sorts (in kB) + #[must_use] pub fn work_mem(mut self, mem: u32) -> Self { self.work_mem = Some(mem); self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// describe configuration parameters, then exit + #[must_use] pub fn describe_config(mut self) -> Self { self.describe_config = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// forbid use of some plan types + #[must_use] pub fn forbidden_plan_types>(mut self, types: S) -> Self { self.forbidden_plan_types = Some(types.as_ref().to_os_string()); self } /// allow system table structure changes + #[must_use] pub fn allow_system_table_changes(mut self) -> Self { self.allow_system_table_changes = true; self } /// disable system indexes + #[must_use] pub fn disable_system_indexes(mut self) -> Self { self.disable_system_indexes = true; self } /// show timings after each query + #[must_use] pub fn show_timings>(mut self, timings: S) -> Self { self.show_timings = Some(timings.as_ref().to_os_string()); self } /// send SIGABRT to all backend processes if one dies + #[must_use] pub fn send_sigabrt(mut self) -> Self { self.send_sigabrt = true; self } /// wait NUM seconds to allow attach from a debugger + #[must_use] pub fn wait_seconds(mut self, seconds: u32) -> Self { self.wait_seconds = Some(seconds); self } /// selects single-user mode (must be first argument) + #[must_use] pub fn single_user_mode(mut self) -> Self { self.single_user_mode = true; self } /// database name (defaults to user name) + #[must_use] pub fn dbname>(mut self, dbname: S) -> Self { self.dbname = Some(dbname.as_ref().to_os_string()); self } /// override debugging level + #[must_use] pub fn override_debugging_level(mut self, level: u8) -> Self { self.override_debugging_level = Some(level); self } /// echo statement before execution + #[must_use] pub fn echo_statement(mut self) -> Self { self.echo_statement = true; self } /// do not use newline as interactive query delimiter + #[must_use] pub fn no_newline_delimiter(mut self) -> Self { self.no_newline_delimiter = true; self } /// send stdout and stderr to given file + #[must_use] pub fn output_file>(mut self, file: P) -> Self { self.output_file = Some(file.into()); self } /// selects bootstrapping mode (must be first argument) + #[must_use] pub fn bootstrapping_mode(mut self) -> Self { self.bootstrapping_mode = true; self } /// selects check mode (must be first argument) + #[must_use] pub fn check_mode(mut self) -> Self { self.check_mode = true; self @@ -259,6 +295,7 @@ impl CommandBuilder for PostgresBuilder { } /// Get the arguments for the command + #[allow(clippy::too_many_lines)] fn get_args(&self) -> Vec { let mut args: Vec = Vec::new(); @@ -403,6 +440,18 @@ impl CommandBuilder for PostgresBuilder { args } + + /// Get the environment variables for the command + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -433,6 +482,7 @@ mod tests { #[test] fn test_builder() { let command = PostgresBuilder::new() + .env("PGDATABASE", "database") .n_buffers(100) .runtime_param("name", "value") .print_runtime_param("name") @@ -467,7 +517,7 @@ mod tests { .build(); assert_eq!( - r#""postgres" "-B" "100" "-c" "name=value" "-C" "name" "-d" "3" "-D" "data_dir" "-e" "-F" "-h" "localhost" "-i" "-k" "socket_location" "-N" "100" "-p" "5432" "-s" "-S" "100" "--version" "--describe-config" "--help" "-f" "type" "-O" "-P" "-t" "timings" "-T" "-W" "10" "--single" "dbname" "-d" "3" "-E" "-j" "-r" "output_file" "--boot" "--check""#, + r#"PGDATABASE="database" "postgres" "-B" "100" "-c" "name=value" "-C" "name" "-d" "3" "-D" "data_dir" "-e" "-F" "-h" "localhost" "-i" "-k" "socket_location" "-N" "100" "-p" "5432" "-s" "-S" "100" "--version" "--describe-config" "--help" "-f" "type" "-O" "-P" "-t" "timings" "-T" "-W" "10" "--single" "dbname" "-d" "3" "-E" "-j" "-r" "output_file" "--boot" "--check""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/psql.rs b/postgresql_commands/src/psql.rs index a7f7136..028ce92 100644 --- a/postgresql_commands/src/psql.rs +++ b/postgresql_commands/src/psql.rs @@ -3,10 +3,13 @@ use crate::Settings; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `psql` is the PostgreSQL interactive terminal. +/// `psql` is the `PostgreSQL` interactive terminal. #[derive(Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] +#[allow(clippy::struct_excessive_bools)] pub struct PsqlBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, command: Option, dbname: Option, file: Option, @@ -46,12 +49,13 @@ pub struct PsqlBuilder { } impl PsqlBuilder { - /// Create a new [PsqlBuilder] + /// Create a new [`PsqlBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [PsqlBuilder] from [Settings] + /// Create a new [`PsqlBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -62,36 +66,42 @@ impl PsqlBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// run only single command (SQL or internal) and exit + #[must_use] pub fn command>(mut self, command: S) -> Self { self.command = Some(command.as_ref().to_os_string()); self } /// database name to connect to + #[must_use] pub fn dbname>(mut self, dbname: S) -> Self { self.dbname = Some(dbname.as_ref().to_os_string()); self } /// execute commands from file, then exit + #[must_use] pub fn file>(mut self, file: P) -> Self { self.file = Some(file.into()); self } /// list available databases, then exit + #[must_use] pub fn list(mut self) -> Self { self.list = true; self } - /// set psql variable NAME to VALUE (e.g., -v ON_ERROR_STOP=1) + /// set psql variable NAME to VALUE (e.g., `-v ON_ERROR_STOP=1`) + #[must_use] pub fn variable>(mut self, variable: (S, S)) -> Self { let (name, value) = variable; self.variable = Some((name.as_ref().into(), value.as_ref().into())); @@ -99,18 +109,21 @@ impl PsqlBuilder { } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// do not read startup file (~/.psqlrc) + #[must_use] pub fn no_psqlrc(mut self) -> Self { self.no_psqlrc = true; self } /// execute as a single transaction (if non-interactive) + #[must_use] pub fn single_transaction(mut self) -> Self { self.single_transaction = true; self @@ -118,96 +131,112 @@ impl PsqlBuilder { /// show help, then exit /// Possible values: [options, commands, variables] + #[must_use] pub fn help>(mut self, help: S) -> Self { self.help = Some(help.as_ref().to_os_string()); self } /// echo all input from script + #[must_use] pub fn echo_all(mut self) -> Self { self.echo_all = true; self } /// echo failed commands + #[must_use] pub fn echo_errors(mut self) -> Self { self.echo_errors = true; self } /// echo commands sent to server + #[must_use] pub fn echo_queries(mut self) -> Self { self.echo_queries = true; self } /// display queries that internal commands generate + #[must_use] pub fn echo_hidden(mut self) -> Self { self.echo_hidden = true; self } /// send session log to file + #[must_use] pub fn log_file>(mut self, log_file: P) -> Self { self.log_file = Some(log_file.into()); self } /// disable enhanced command line editing (readline) + #[must_use] pub fn no_readline(mut self) -> Self { self.no_readline = true; self } /// send query results to file (or |pipe) + #[must_use] pub fn output>(mut self, output: P) -> Self { self.output = Some(output.into()); self } /// run quietly (no messages, only query output) + #[must_use] pub fn quiet(mut self) -> Self { self.quiet = true; self } /// single-step mode (confirm each query) + #[must_use] pub fn single_step(mut self) -> Self { self.single_step = true; self } /// single-line mode (end of line terminates SQL command) + #[must_use] pub fn single_line(mut self) -> Self { self.single_line = true; self } /// unaligned table output mode + #[must_use] pub fn no_align(mut self) -> Self { self.no_align = true; self } /// CSV (Comma-Separated Values) table output mode + #[must_use] pub fn csv(mut self) -> Self { self.csv = true; self } /// field separator for unaligned output (default: "|") + #[must_use] pub fn field_separator>(mut self, field_separator: S) -> Self { self.field_separator = Some(field_separator.as_ref().to_os_string()); self } /// HTML table output mode + #[must_use] pub fn html(mut self) -> Self { self.html = true; self } /// set printing option VAR to ARG (see \pset command) + #[must_use] pub fn pset>(mut self, pset: (S, S)) -> Self { let (var, arg) = pset; self.pset = Some((var.as_ref().into(), arg.as_ref().into())); @@ -215,72 +244,84 @@ impl PsqlBuilder { } /// record separator for unaligned output (default: newline) + #[must_use] pub fn record_separator>(mut self, record_separator: S) -> Self { self.record_separator = Some(record_separator.as_ref().to_os_string()); self } /// print rows only + #[must_use] pub fn tuples_only(mut self) -> Self { self.tuples_only = true; self } /// set HTML table tag attributes (e.g., width, border) + #[must_use] pub fn table_attr>(mut self, table_attr: S) -> Self { self.table_attr = Some(table_attr.as_ref().to_os_string()); self } /// turn on expanded table output + #[must_use] pub fn expanded(mut self) -> Self { self.expanded = true; self } /// set field separator for unaligned output to zero byte + #[must_use] pub fn field_separator_zero(mut self) -> Self { self.field_separator_zero = true; self } /// set record separator for unaligned output to zero byte + #[must_use] pub fn record_separator_zero(mut self) -> Self { self.record_separator_zero = true; self } /// database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// database server port + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// database user name + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// force password prompt (should happen automatically) + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self @@ -299,6 +340,7 @@ impl CommandBuilder for PsqlBuilder { } /// Get the arguments for the command + #[allow(clippy::too_many_lines)] fn get_args(&self) -> Vec { let mut args: Vec = Vec::new(); @@ -461,7 +503,7 @@ impl CommandBuilder for PsqlBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -469,6 +511,13 @@ impl CommandBuilder for PsqlBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -499,6 +548,7 @@ mod tests { #[test] fn test_builder() { let command = PsqlBuilder::new() + .env("PGDATABASE", "database") .command("SELECT * FROM test") .dbname("dbname") .file("test.sql") @@ -538,7 +588,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "psql" "--command" "SELECT * FROM test" "--dbname" "dbname" "--file" "test.sql" "--list" "--variable" "ON_ERROR_STOP=1" "--version" "--no-psqlrc" "--single-transaction" "--help" "options" "--echo-all" "--echo-errors" "--echo-queries" "--echo-hidden" "--log-file" "psql.log" "--no-readline" "--output" "output.txt" "--quiet" "--single-step" "--single-line" "--no-align" "--csv" "--field-separator" "|" "--html" "--pset" "border=1" "--record-separator" "\n" "--tuples-only" "--table-attr" "width=100" "--expanded" "--field-separator-zero" "--record-separator-zero" "--host" "localhost" "--port" "5432" "--username" "postgres" "--no-password" "--password""#, + r#"PGDATABASE="database" PGPASSWORD="password" "psql" "--command" "SELECT * FROM test" "--dbname" "dbname" "--file" "test.sql" "--list" "--variable" "ON_ERROR_STOP=1" "--version" "--no-psqlrc" "--single-transaction" "--help" "options" "--echo-all" "--echo-errors" "--echo-queries" "--echo-hidden" "--log-file" "psql.log" "--no-readline" "--output" "output.txt" "--quiet" "--single-step" "--single-line" "--no-align" "--csv" "--field-separator" "|" "--html" "--pset" "border=1" "--record-separator" "\n" "--tuples-only" "--table-attr" "width=100" "--expanded" "--field-separator-zero" "--record-separator-zero" "--host" "localhost" "--port" "5432" "--username" "postgres" "--no-password" "--password""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/reindexdb.rs b/postgresql_commands/src/reindexdb.rs index b763db4..0c97d2c 100644 --- a/postgresql_commands/src/reindexdb.rs +++ b/postgresql_commands/src/reindexdb.rs @@ -3,10 +3,12 @@ use crate::Settings; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `reindexdb` reindexes a PostgreSQL database. +/// `reindexdb` reindexes a `PostgreSQL` database. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct ReindexDbBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, all: bool, concurrently: bool, dbname: Option, @@ -31,12 +33,13 @@ pub struct ReindexDbBuilder { } impl ReindexDbBuilder { - /// Create a new [ReindexDbBuilder] + /// Create a new [`ReindexDbBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [ReindexDbBuilder] from [Settings] + /// Create a new [`ReindexDbBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -47,132 +50,154 @@ impl ReindexDbBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// reindex all databases + #[must_use] pub fn all(mut self) -> Self { self.all = true; self } /// reindex concurrently + #[must_use] pub fn concurrently(mut self) -> Self { self.concurrently = true; self } /// database to reindex + #[must_use] pub fn dbname>(mut self, dbname: S) -> Self { self.dbname = Some(dbname.as_ref().to_os_string()); self } /// show the commands being sent to the server + #[must_use] pub fn echo(mut self) -> Self { self.echo = true; self } /// recreate specific index(es) only + #[must_use] pub fn index>(mut self, index: S) -> Self { self.index = Some(index.as_ref().to_os_string()); self } /// use this many concurrent connections to reindex + #[must_use] pub fn jobs(mut self, jobs: u32) -> Self { self.jobs = Some(jobs); self } /// don't write any messages + #[must_use] pub fn quiet(mut self) -> Self { self.quiet = true; self } /// reindex system catalogs only + #[must_use] pub fn system(mut self) -> Self { self.system = true; self } /// reindex specific schema(s) only + #[must_use] pub fn schema>(mut self, schema: S) -> Self { self.schema = Some(schema.as_ref().to_os_string()); self } /// reindex specific table(s) only + #[must_use] pub fn table>(mut self, table: S) -> Self { self.table = Some(table.as_ref().to_os_string()); self } /// tablespace where indexes are rebuilt + #[must_use] pub fn tablespace>(mut self, tablespace: S) -> Self { self.tablespace = Some(tablespace.as_ref().to_os_string()); self } /// write a lot of output + #[must_use] pub fn verbose(mut self) -> Self { self.verbose = true; self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// database server port + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// user name to connect as + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// force password prompt + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self } /// alternate maintenance database + #[must_use] pub fn maintenance_db>(mut self, maintenance_db: S) -> Self { self.maintenance_db = Some(maintenance_db.as_ref().to_os_string()); self @@ -289,7 +314,7 @@ impl CommandBuilder for ReindexDbBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -297,6 +322,13 @@ impl CommandBuilder for ReindexDbBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -327,6 +359,7 @@ mod tests { #[test] fn test_builder() { let command = ReindexDbBuilder::new() + .env("PGDATABASE", "database") .all() .concurrently() .dbname("dbname") @@ -351,7 +384,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "reindexdb" "--all" "--concurrently" "--dbname" "dbname" "--echo" "--index" "index" "--jobs" "1" "--quiet" "--system" "--schema" "schema" "--table" "table" "--tablespace" "tablespace" "--verbose" "--version" "--help" "--host" "localhost" "--port" "5432" "--username" "username" "--no-password" "--password" "--maintenance-db" "maintenance-db""#, + r#"PGDATABASE="database" PGPASSWORD="password" "reindexdb" "--all" "--concurrently" "--dbname" "dbname" "--echo" "--index" "index" "--jobs" "1" "--quiet" "--system" "--schema" "schema" "--table" "table" "--tablespace" "tablespace" "--verbose" "--version" "--help" "--host" "localhost" "--port" "5432" "--username" "username" "--no-password" "--password" "--maintenance-db" "maintenance-db""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/traits.rs b/postgresql_commands/src/traits.rs index a69b1dc..c4ddd69 100644 --- a/postgresql_commands/src/traits.rs +++ b/postgresql_commands/src/traits.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use std::time::Duration; use tracing::debug; -/// Interface for PostgreSQL settings +/// Interface for `PostgreSQL` settings pub trait Settings { fn get_binary_dir(&self) -> PathBuf; fn get_host(&self) -> OsString; @@ -63,9 +63,11 @@ pub trait CommandBuilder: Debug { } /// Get the environment variables for the command - fn get_envs(&self) -> Vec<(OsString, OsString)> { - vec![] - } + fn get_envs(&self) -> Vec<(OsString, OsString)>; + + /// Set an environment variable for the command + #[must_use] + fn env>(self, key: S, value: S) -> Self; /// Build a standard Command fn build(self) -> std::process::Command @@ -120,6 +122,10 @@ impl CommandToString for tokio::process::Command { /// Interface for executing a command pub trait CommandExecutor { /// Execute the command and return the stdout and stderr + /// + /// # Errors + /// + /// Returns an error if the command fails fn execute(&mut self) -> Result<(String, String)>; } @@ -157,6 +163,7 @@ impl CommandExecutor for std::process::Command { #[cfg(feature = "tokio")] /// Implement the [`CommandExecutor`] trait for [`Command`](tokio::process::Command) +#[allow(clippy::items_after_statements)] impl AsyncCommandExecutor for tokio::process::Command { /// Execute the command and return the stdout and stderr async fn execute(&mut self, timeout: Option) -> Result<(String, String)> { @@ -216,7 +223,7 @@ impl AsyncCommandExecutor for tokio::process::Command { let _ = exit_anyway_broadcast_sender.send(()); } let (stdout, stderr) = tokio::join!(stdout, stderr); - std::mem::drop(exit_anyway_broadcast_sender); + drop(exit_anyway_broadcast_sender); let exit_status = exit_status?; fn debug_render( @@ -225,8 +232,8 @@ impl AsyncCommandExecutor for tokio::process::Command { ) -> String { match res { Ok(Ok(s)) => s.into(), - Ok(Err(io_err)) => format!("", which, io_err), - Err(join_err) => format!("", which, join_err), + Ok(Err(io_err)) => format!(""), + Err(join_err) => format!(""), } } debug!( @@ -268,9 +275,10 @@ mod test { #[test] fn test_command_builder_defaults() { - #[derive(Debug)] + #[derive(Debug, Default)] struct DefaultCommandBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, } impl CommandBuilder for DefaultCommandBuilder { @@ -281,9 +289,19 @@ mod test { fn get_program_dir(&self) -> &Option { &self.program_dir } + + fn get_envs(&self) -> Vec<(OsString, OsString)> { + self.envs.clone() + } + + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } - let builder = DefaultCommandBuilder { program_dir: None }; + let builder = DefaultCommandBuilder::default(); let command = builder.build(); assert_eq!(r#""test""#, command.to_command_string()); @@ -312,6 +330,12 @@ mod test { fn get_envs(&self) -> Vec<(OsString, OsString)> { self.envs.clone() } + + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[test] diff --git a/postgresql_commands/src/vacuumdb.rs b/postgresql_commands/src/vacuumdb.rs index 60b8e25..5b35e09 100644 --- a/postgresql_commands/src/vacuumdb.rs +++ b/postgresql_commands/src/vacuumdb.rs @@ -4,10 +4,12 @@ use std::convert::AsRef; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; -/// `vacuumdb` cleans and analyzes a PostgreSQL database. +/// `vacuumdb` cleans and analyzes a `PostgreSQL` database. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct VacuumDbBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, all: bool, buffer_usage_limit: Option, dbname: Option, @@ -44,14 +46,15 @@ pub struct VacuumDbBuilder { maintenance_db: Option, } -/// vacuumdb cleans and analyzes a PostgreSQL database. +/// vacuumdb cleans and analyzes a `PostgreSQL` database. impl VacuumDbBuilder { - /// Create a new [VacuumDbBuilder] + /// Create a new [`VacuumDbBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [VacuumDbBuilder] from [Settings] + /// Create a new [`VacuumDbBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -62,210 +65,245 @@ impl VacuumDbBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// vacuum all databases + #[must_use] pub fn all(mut self) -> Self { self.all = true; self } /// size of ring buffer used for vacuum + #[must_use] pub fn buffer_usage_limit>(mut self, buffer_usage_limit: S) -> Self { self.buffer_usage_limit = Some(buffer_usage_limit.as_ref().to_os_string()); self } /// database to vacuum + #[must_use] pub fn dbname>(mut self, dbname: S) -> Self { self.dbname = Some(dbname.as_ref().to_os_string()); self } /// disable all page-skipping behavior + #[must_use] pub fn disable_page_skipping(mut self) -> Self { self.disable_page_skipping = true; self } /// show the commands being sent to the server + #[must_use] pub fn echo(mut self) -> Self { self.echo = true; self } /// do full vacuuming + #[must_use] pub fn full(mut self) -> Self { self.full = true; self } /// freeze row transaction information + #[must_use] pub fn freeze(mut self) -> Self { self.freeze = true; self } /// always remove index entries that point to dead tuples + #[must_use] pub fn force_index_cleanup(mut self) -> Self { self.force_index_cleanup = true; self } /// use this many concurrent connections to vacuum + #[must_use] pub fn jobs(mut self, jobs: u32) -> Self { self.jobs = Some(jobs); self } /// minimum multixact ID age of tables to vacuum + #[must_use] pub fn min_mxid_age>(mut self, min_mxid_age: S) -> Self { self.min_mxid_age = Some(min_mxid_age.as_ref().to_os_string()); self } /// minimum transaction ID age of tables to vacuum + #[must_use] pub fn min_xid_age>(mut self, min_xid_age: S) -> Self { self.min_xid_age = Some(min_xid_age.as_ref().to_os_string()); self } /// don't remove index entries that point to dead tuples + #[must_use] pub fn no_index_cleanup(mut self) -> Self { self.no_index_cleanup = true; self } /// skip the main relation + #[must_use] pub fn no_process_main(mut self) -> Self { self.no_process_main = true; self } /// skip the TOAST table associated with the table to vacuum + #[must_use] pub fn no_process_toast(mut self) -> Self { self.no_process_toast = true; self } /// don't truncate empty pages at the end of the table + #[must_use] pub fn no_truncate(mut self) -> Self { self.no_truncate = true; self } /// vacuum tables in the specified schema(s) only + #[must_use] pub fn schema>(mut self, schema: S) -> Self { self.schema = Some(schema.as_ref().to_os_string()); self } /// do not vacuum tables in the specified schema(s) + #[must_use] pub fn exclude_schema>(mut self, exclude_schema: S) -> Self { self.exclude_schema = Some(exclude_schema.as_ref().to_os_string()); self } /// use this many background workers for vacuum, if available + #[must_use] pub fn parallel(mut self, parallel: u32) -> Self { self.parallel = Some(parallel); self } /// don't write any messages + #[must_use] pub fn quiet(mut self) -> Self { self.quiet = true; self } /// skip relations that cannot be immediately locked + #[must_use] pub fn skip_locked(mut self) -> Self { self.skip_locked = true; self } /// vacuum specific table(s) only + #[must_use] pub fn table>(mut self, table: S) -> Self { self.table = Some(table.as_ref().to_os_string()); self } /// write a lot of output + #[must_use] pub fn verbose(mut self) -> Self { self.verbose = true; self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// update optimizer statistics + #[must_use] pub fn analyze(mut self) -> Self { self.analyze = true; self } /// only update optimizer statistics; no vacuum + #[must_use] pub fn analyze_only(mut self) -> Self { self.analyze_only = true; self } /// only update optimizer statistics, in multiple stages for faster results; no vacuum + #[must_use] pub fn analyze_in_stages(mut self) -> Self { self.analyze_in_stages = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// database server port + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// user name to connect as + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// force password prompt + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self } /// alternate maintenance database + #[must_use] pub fn maintenance_db>(mut self, maintenance_db: S) -> Self { self.maintenance_db = Some(maintenance_db.as_ref().to_os_string()); self @@ -284,6 +322,7 @@ impl CommandBuilder for VacuumDbBuilder { } /// Get the arguments for the command + #[allow(clippy::too_many_lines)] fn get_args(&self) -> Vec { let mut args: Vec = Vec::new(); @@ -437,7 +476,7 @@ impl CommandBuilder for VacuumDbBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -445,6 +484,13 @@ impl CommandBuilder for VacuumDbBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -475,6 +521,7 @@ mod tests { #[test] fn test_builder() { let command = VacuumDbBuilder::new() + .env("PGDATABASE", "database") .all() .buffer_usage_limit("buffer_usage_limit") .dbname("dbname") @@ -512,7 +559,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "vacuumdb" "--all" "--buffer-usage-limit" "buffer_usage_limit" "--dbname" "dbname" "--disable-page-skipping" "--echo" "--full" "--freeze" "--force-index-cleanup" "--jobs" "1" "--min-mxid-age" "min_mxid_age" "--min-xid-age" "min_xid_age" "--no-index-cleanup" "--no-process-main" "--no-process-toast" "--no-truncate" "--schema" "schema" "--exclude-schema" "exclude_schema" "--parallel" "1" "--quiet" "--skip-locked" "--table" "table" "--verbose" "--version" "--analyze" "--analyze-only" "--analyze-in-stages" "--help" "--host" "localhost" "--port" "5432" "--username" "username" "--no-password" "--password" "--maintenance-db" "maintenance_db""#, + r#"PGDATABASE="database" PGPASSWORD="password" "vacuumdb" "--all" "--buffer-usage-limit" "buffer_usage_limit" "--dbname" "dbname" "--disable-page-skipping" "--echo" "--full" "--freeze" "--force-index-cleanup" "--jobs" "1" "--min-mxid-age" "min_mxid_age" "--min-xid-age" "min_xid_age" "--no-index-cleanup" "--no-process-main" "--no-process-toast" "--no-truncate" "--schema" "schema" "--exclude-schema" "exclude_schema" "--parallel" "1" "--quiet" "--skip-locked" "--table" "table" "--verbose" "--version" "--analyze" "--analyze-only" "--analyze-in-stages" "--help" "--host" "localhost" "--port" "5432" "--username" "username" "--no-password" "--password" "--maintenance-db" "maintenance_db""#, command.to_command_string() ); } diff --git a/postgresql_commands/src/vacuumlo.rs b/postgresql_commands/src/vacuumlo.rs index f2a039c..527e705 100644 --- a/postgresql_commands/src/vacuumlo.rs +++ b/postgresql_commands/src/vacuumlo.rs @@ -6,8 +6,10 @@ use std::path::PathBuf; /// `vacuumlo` removes unreferenced large objects from databases. #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct VacuumLoBuilder { program_dir: Option, + envs: Vec<(OsString, OsString)>, limit: Option, dry_run: bool, verbose: bool, @@ -22,12 +24,13 @@ pub struct VacuumLoBuilder { } impl VacuumLoBuilder { - /// Create a new [VacuumLoBuilder] + /// Create a new [`VacuumLoBuilder`] + #[must_use] pub fn new() -> Self { Self::default() } - /// Create a new [VacuumLoBuilder] from [Settings] + /// Create a new [`VacuumLoBuilder`] from [Settings] pub fn from(settings: &dyn Settings) -> Self { Self::new() .program_dir(settings.get_binary_dir()) @@ -38,72 +41,84 @@ impl VacuumLoBuilder { } /// Location of the program binary + #[must_use] pub fn program_dir>(mut self, path: P) -> Self { self.program_dir = Some(path.into()); self } /// commit after removing each LIMIT large objects + #[must_use] pub fn limit(mut self, limit: usize) -> Self { self.limit = Some(limit); self } /// don't remove large objects, just show what would be done + #[must_use] pub fn dry_run(mut self) -> Self { self.dry_run = true; self } /// write a lot of progress messages + #[must_use] pub fn verbose(mut self) -> Self { self.verbose = true; self } /// output version information, then exit + #[must_use] pub fn version(mut self) -> Self { self.version = true; self } /// show help, then exit + #[must_use] pub fn help(mut self) -> Self { self.help = true; self } /// database server host or socket directory + #[must_use] pub fn host>(mut self, host: S) -> Self { self.host = Some(host.as_ref().to_os_string()); self } /// database server port + #[must_use] pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } /// user name to connect as + #[must_use] pub fn username>(mut self, username: S) -> Self { self.username = Some(username.as_ref().to_os_string()); self } /// never prompt for password + #[must_use] pub fn no_password(mut self) -> Self { self.no_password = true; self } /// force password prompt + #[must_use] pub fn password(mut self) -> Self { self.password = true; self } /// user password + #[must_use] pub fn pg_password>(mut self, pg_password: S) -> Self { self.pg_password = Some(pg_password.as_ref().to_os_string()); self @@ -174,7 +189,7 @@ impl CommandBuilder for VacuumLoBuilder { /// Get the environment variables for the command fn get_envs(&self) -> Vec<(OsString, OsString)> { - let mut envs: Vec<(OsString, OsString)> = Vec::new(); + let mut envs: Vec<(OsString, OsString)> = self.envs.clone(); if let Some(password) = &self.pg_password { envs.push(("PGPASSWORD".into(), password.into())); @@ -182,6 +197,13 @@ impl CommandBuilder for VacuumLoBuilder { envs } + + /// Set an environment variable for the command + fn env>(mut self, key: S, value: S) -> Self { + self.envs + .push((key.as_ref().to_os_string(), value.as_ref().to_os_string())); + self + } } #[cfg(test)] @@ -212,6 +234,7 @@ mod tests { #[test] fn test_builder() { let command = VacuumLoBuilder::new() + .env("PGDATABASE", "database") .limit(100) .dry_run() .verbose() @@ -226,7 +249,7 @@ mod tests { .build(); assert_eq!( - r#"PGPASSWORD="password" "vacuumlo" "--limit" "100" "--dry-run" "--verbose" "--version" "--help" "--host" "localhost" "--port" "5432" "--username" "postgres" "--no-password" "--password""#, + r#"PGDATABASE="database" PGPASSWORD="password" "vacuumlo" "--limit" "100" "--dry-run" "--verbose" "--version" "--help" "--host" "localhost" "--port" "5432" "--username" "postgres" "--no-password" "--password""#, command.to_command_string() ); } diff --git a/postgresql_embedded/src/postgresql.rs b/postgresql_embedded/src/postgresql.rs index b73c946..9e0ce41 100644 --- a/postgresql_embedded/src/postgresql.rs +++ b/postgresql_embedded/src/postgresql.rs @@ -37,6 +37,8 @@ lazy_static::lazy_static! { #[cfg(feature = "bundled")] pub(crate) const ARCHIVE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/postgresql.tar.gz")); +const PGDATABASE: &str = "PGDATABASE"; + /// PostgreSQL status #[derive(Debug, Clone, Copy, PartialEq)] pub enum Status { @@ -260,10 +262,11 @@ impl PostgreSQL { options.push(format!("-c {key}={value}")); } let pg_ctl = PgCtlBuilder::from(&self.settings) + .env(PGDATABASE, "") .mode(Start) .pgdata(&self.settings.data_dir) .log(start_log) - .options(options) + .options(options.as_slice()) .wait(); match self.execute_command(pg_ctl).await { @@ -317,6 +320,7 @@ impl PostgreSQL { self.settings.port ); let psql = PsqlBuilder::from(&self.settings) + .env(PGDATABASE, "") .command(format!("CREATE DATABASE \"{}\"", database_name.as_ref())) .username(BOOTSTRAP_SUPERUSER) .no_psqlrc(); @@ -348,6 +352,7 @@ impl PostgreSQL { self.settings.port ); let psql = PsqlBuilder::from(&self.settings) + .env(PGDATABASE, "") .command(format!( "SELECT 1 FROM pg_database WHERE datname='{}'", database_name.as_ref() @@ -378,6 +383,7 @@ impl PostgreSQL { self.settings.port ); let psql = PsqlBuilder::from(&self.settings) + .env(PGDATABASE, "") .command(format!( "DROP DATABASE IF EXISTS \"{}\"", database_name.as_ref() diff --git a/postgresql_embedded/tests/environment_variables.rs b/postgresql_embedded/tests/environment_variables.rs new file mode 100644 index 0000000..dd5404b --- /dev/null +++ b/postgresql_embedded/tests/environment_variables.rs @@ -0,0 +1,25 @@ +use postgresql_embedded::{PostgreSQL, Status}; +use std::env; +use test_log::test; + +#[test(tokio::test)] +async fn lifecycle() -> anyhow::Result<()> { + // Explicitly set PGDATABASE environment variables to verify that the library behavior + // is not affected by the environment + env::set_var("PGDATABASE", "foodb"); + + let mut postgresql = PostgreSQL::default(); + + postgresql.setup().await?; + postgresql.start().await?; + + let database_name = "test"; + assert!(!postgresql.database_exists(database_name).await?); + postgresql.create_database(database_name).await?; + assert!(postgresql.database_exists(database_name).await?); + postgresql.drop_database(database_name).await?; + + postgresql.stop().await?; + assert_eq!(Status::Stopped, postgresql.status()); + Ok(()) +}