From 58900ee4f8276b8885913d327d9e7e805172dc35 Mon Sep 17 00:00:00 2001 From: brianheineman Date: Wed, 19 Jun 2024 19:15:57 -0600 Subject: [PATCH 1/6] feat!: allow releases URL to be configured --- README.md | 3 +- postgresql_archive/benches/archive.rs | 4 +- postgresql_archive/src/archive.rs | 39 ++--- postgresql_archive/src/blocking/archive.rs | 67 +++++++++ postgresql_archive/src/blocking/mod.rs | 67 +-------- postgresql_archive/src/lib.rs | 2 + postgresql_archive/tests/archive.rs | 24 ++-- postgresql_archive/tests/blocking.rs | 8 +- postgresql_embedded/README.md | 3 +- postgresql_embedded/build/bundle.rs | 4 +- postgresql_embedded/src/blocking/mod.rs | 136 +----------------- .../src/blocking/postgresql.rs | 135 +++++++++++++++++ postgresql_embedded/src/postgresql.rs | 4 +- postgresql_embedded/src/settings.rs | 43 +++--- 14 files changed, 287 insertions(+), 252 deletions(-) create mode 100644 postgresql_archive/src/blocking/archive.rs create mode 100644 postgresql_embedded/src/blocking/postgresql.rs diff --git a/README.md b/README.md index c68ba99..bf17a11 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,8 @@ PostgreSQL is covered under [The PostgreSQL License](https://opensource.org/lice ## Notes -Uses PostgreSQL binaries from [theseus-rs/postgresql-binaries](https://github.com/theseus-rs/postgresql_binaries). +Uses PostgreSQL binaries from [theseus-rs/postgresql-binaries](https://github.com/theseus-rs/postgresql_binaries) by +default. ## Contribution diff --git a/postgresql_archive/benches/archive.rs b/postgresql_archive/benches/archive.rs index 71a384f..9234591 100644 --- a/postgresql_archive/benches/archive.rs +++ b/postgresql_archive/benches/archive.rs @@ -1,7 +1,7 @@ use bytes::Bytes; use criterion::{criterion_group, criterion_main, Criterion}; use postgresql_archive::blocking::{extract, get_archive}; -use postgresql_archive::{Result, LATEST}; +use postgresql_archive::{Result, DEFAULT_RELEASES_URL, LATEST}; use std::fs::{create_dir_all, remove_dir_all}; use std::time::Duration; @@ -11,7 +11,7 @@ fn benchmarks(criterion: &mut Criterion) { fn bench_extract(criterion: &mut Criterion) -> Result<()> { let version = &LATEST; - let (_archive_version, archive) = get_archive(version)?; + let (_archive_version, archive) = get_archive(DEFAULT_RELEASES_URL, version)?; criterion.bench_function("extract", |bencher| { bencher.iter(|| { diff --git a/postgresql_archive/src/archive.rs b/postgresql_archive/src/archive.rs index f2f0c71..fc54cee 100644 --- a/postgresql_archive/src/archive.rs +++ b/postgresql_archive/src/archive.rs @@ -29,6 +29,8 @@ use tracing::{debug, instrument, warn}; const GITHUB_API_VERSION_HEADER: &str = "X-GitHub-Api-Version"; const GITHUB_API_VERSION: &str = "2022-11-28"; +pub const DEFAULT_RELEASES_URL: &str = + "https://api.github.com/repos/theseus-rs/postgresql-binaries/releases"; lazy_static! { static ref GITHUB_TOKEN: Option = match std::env::var("GITHUB_TOKEN") { @@ -103,14 +105,13 @@ fn reqwest_client() -> ClientWithMiddleware { /// Gets a release from GitHub for a given [version](Version) of PostgreSQL. If a release for the /// [version](Version) is not found, then a [ReleaseNotFound] error is returned. #[instrument(level = "debug")] -async fn get_release(version: &Version) -> Result { - let url = "https://api.github.com/repos/theseus-rs/postgresql-binaries/releases"; +async fn get_release(releases_url: &str, version: &Version) -> Result { let client = reqwest_client(); debug!("Attempting to locate release for version {version}"); if version.minor.is_some() && version.release.is_some() { - let request = client.get(format!("{url}/tags/{version}")); + let request = client.get(format!("{releases_url}/tags/{version}")); let response = request.send().await?.error_for_status()?; let release = response.json::().await?; @@ -123,7 +124,7 @@ async fn get_release(version: &Version) -> Result { loop { let request = client - .get(url) + .get(releases_url) .query(&[("page", page.to_string().as_str()), ("per_page", "100")]); let response = request.send().await?.error_for_status()?; let response_releases = response.json::>().await?; @@ -169,8 +170,8 @@ async fn get_release(version: &Version) -> Result { /// specified, then the latest version is returned. If a release for the [version](Version) is not found, then a /// [ReleaseNotFound] error is returned. #[instrument(level = "debug")] -pub async fn get_version(version: &Version) -> Result { - let release = get_release(version).await?; +pub async fn get_version(releases_url: &str, version: &Version) -> Result { + let release = get_release(releases_url, version).await?; Version::from_str(&release.tag_name) } @@ -181,8 +182,12 @@ pub async fn get_version(version: &Version) -> Result { /// /// Two assets are returned. The first [asset](Asset) is the archive, and the second [asset](Asset) is the archive hash. #[instrument(level = "debug", skip(target))] -async fn get_asset>(version: &Version, target: S) -> Result<(Version, Asset, Asset)> { - let release = get_release(version).await?; +async fn get_asset>( + releases_url: &str, + version: &Version, + target: S, +) -> Result<(Version, Asset, Asset)> { + let release = get_release(releases_url, version).await?; let asset_version = Version::from_str(&release.tag_name)?; let mut asset: Option = None; let mut asset_hash: Option = None; @@ -213,8 +218,8 @@ async fn get_asset>(version: &Version, target: S) -> Result<(Versi /// /// Returns the archive version and bytes. #[instrument] -pub async fn get_archive(version: &Version) -> Result<(Version, Bytes)> { - get_archive_for_target(version, target_triple::TARGET).await +pub async fn get_archive(releases_url: &str, version: &Version) -> Result<(Version, Bytes)> { + get_archive_for_target(releases_url, version, target_triple::TARGET).await } /// Gets the archive for a given [version](Version) of PostgreSQL and @@ -226,10 +231,11 @@ pub async fn get_archive(version: &Version) -> Result<(Version, Bytes)> { #[allow(clippy::cast_precision_loss)] #[instrument(level = "debug", skip(target))] pub async fn get_archive_for_target>( + releases_url: &str, version: &Version, target: S, ) -> Result<(Version, Bytes)> { - let (asset_version, asset, asset_hash) = get_asset(version, target).await?; + let (asset_version, asset, asset_hash) = get_asset(releases_url, version, target).await?; debug!( "Downloading archive hash {}", @@ -436,13 +442,13 @@ mod tests { #[test(tokio::test)] async fn test_get_release() -> Result<()> { - let _ = get_release(&VERSION).await?; + let _ = get_release(DEFAULT_RELEASES_URL, &VERSION).await?; Ok(()) } #[test(tokio::test)] async fn test_get_release_version_not_found() -> Result<()> { - let release = get_release(&INVALID_VERSION).await; + let release = get_release(DEFAULT_RELEASES_URL, &INVALID_VERSION).await; assert!(release.is_err()); Ok(()) } @@ -450,7 +456,8 @@ mod tests { #[test(tokio::test)] async fn test_get_asset() -> Result<()> { let target_triple = "x86_64-unknown-linux-musl".to_string(); - let (asset_version, asset, asset_hash) = get_asset(&VERSION, &target_triple).await?; + let (asset_version, asset, asset_hash) = + get_asset(DEFAULT_RELEASES_URL, &VERSION, &target_triple).await?; assert!(asset_version.matches(&VERSION)); assert!(asset.name.contains(&target_triple)); assert!(asset_hash.name.contains(&target_triple)); @@ -462,7 +469,7 @@ mod tests { #[test(tokio::test)] async fn test_get_asset_version_not_found() -> Result<()> { let target_triple = "x86_64-unknown-linux-musl".to_string(); - let result = get_asset(&INVALID_VERSION, &target_triple).await; + let result = get_asset(DEFAULT_RELEASES_URL, &INVALID_VERSION, &target_triple).await; assert!(result.is_err()); Ok(()) } @@ -470,7 +477,7 @@ mod tests { #[test(tokio::test)] async fn test_get_asset_target_not_found() -> Result<()> { let target_triple = "wasm64-unknown-unknown".to_string(); - let result = get_asset(&VERSION, &target_triple).await; + let result = get_asset(DEFAULT_RELEASES_URL, &VERSION, &target_triple).await; assert!(result.is_err()); Ok(()) } diff --git a/postgresql_archive/src/blocking/archive.rs b/postgresql_archive/src/blocking/archive.rs new file mode 100644 index 0000000..c46c49c --- /dev/null +++ b/postgresql_archive/src/blocking/archive.rs @@ -0,0 +1,67 @@ +use crate::Version; +use bytes::Bytes; +use std::path::Path; +use tokio::runtime::Runtime; + +lazy_static! { + static ref RUNTIME: Runtime = Runtime::new().unwrap(); +} + +/// Gets the version of PostgreSQL for the specified [version](Version). If the version minor or release is not +/// specified, then the latest version is returned. If a release for the [version](Version) is not found, then a +/// [ReleaseNotFound](crate::Error::ReleaseNotFound) error is returned. +/// +/// # Errors +/// +/// Returns an error if the version is not found. +pub fn get_version(releases_url: &str, version: &Version) -> crate::Result { + RUNTIME + .handle() + .block_on(async move { crate::get_version(releases_url, version).await }) +} + +/// Gets the archive for a given [version](Version) of PostgreSQL for the current target. +/// If the [version](Version) is not found for this target, then an +/// [error](crate::Error) is returned. +/// +/// Returns the archive version and bytes. +/// +/// # Errors +/// +/// Returns an error if the version is not found. +pub fn get_archive(releases_url: &str, version: &Version) -> crate::Result<(Version, Bytes)> { + RUNTIME + .handle() + .block_on(async move { crate::get_archive(releases_url, version).await }) +} + +/// Gets the archive for a given [version](Version) of PostgreSQL and +/// [target](https://doc.rust-lang.org/nightly/rustc/platform-support.html). +/// If the [version](Version) or [target](https://doc.rust-lang.org/nightly/rustc/platform-support.html) +/// is not found, then an [error](crate::error::Error) is returned. +/// +/// Returns the archive version and bytes. +/// +/// # Errors +/// +/// Returns an error if the version or target is not found. +pub fn get_archive_for_target>( + releases_url: &str, + version: &Version, + target: S, +) -> crate::Result<(Version, Bytes)> { + RUNTIME + .handle() + .block_on(async move { crate::get_archive_for_target(releases_url, version, target).await }) +} + +/// Extracts the compressed tar [bytes](Bytes) to the [out_dir](Path). +/// +/// # Errors +/// +/// Returns an error if the extraction fails. +pub fn extract(bytes: &Bytes, out_dir: &Path) -> crate::Result<()> { + RUNTIME + .handle() + .block_on(async move { crate::extract(bytes, out_dir).await }) +} diff --git a/postgresql_archive/src/blocking/mod.rs b/postgresql_archive/src/blocking/mod.rs index b303b5a..21664a4 100644 --- a/postgresql_archive/src/blocking/mod.rs +++ b/postgresql_archive/src/blocking/mod.rs @@ -1,66 +1,3 @@ -use crate::Version; -use bytes::Bytes; -use std::path::Path; -use tokio::runtime::Runtime; +mod archive; -lazy_static! { - static ref RUNTIME: Runtime = Runtime::new().unwrap(); -} - -/// Gets the version of PostgreSQL for the specified [version](Version). If the version minor or release is not -/// specified, then the latest version is returned. If a release for the [version](Version) is not found, then a -/// [ReleaseNotFound](crate::Error::ReleaseNotFound) error is returned. -/// -/// # Errors -/// -/// Returns an error if the version is not found. -pub fn get_version(version: &Version) -> crate::Result { - RUNTIME - .handle() - .block_on(async move { crate::get_version(version).await }) -} - -/// Gets the archive for a given [version](Version) of PostgreSQL for the current target. -/// If the [version](Version) is not found for this target, then an -/// [error](crate::Error) is returned. -/// -/// Returns the archive version and bytes. -/// -/// # Errors -/// -/// Returns an error if the version is not found. -pub fn get_archive(version: &Version) -> crate::Result<(Version, Bytes)> { - RUNTIME - .handle() - .block_on(async move { crate::get_archive(version).await }) -} - -/// Gets the archive for a given [version](Version) of PostgreSQL and -/// [target](https://doc.rust-lang.org/nightly/rustc/platform-support.html). -/// If the [version](Version) or [target](https://doc.rust-lang.org/nightly/rustc/platform-support.html) -/// is not found, then an [error](crate::error::Error) is returned. -/// -/// Returns the archive version and bytes. -/// -/// # Errors -/// -/// Returns an error if the version or target is not found. -pub fn get_archive_for_target>( - version: &Version, - target: S, -) -> crate::Result<(Version, Bytes)> { - RUNTIME - .handle() - .block_on(async move { crate::get_archive_for_target(version, target).await }) -} - -/// Extracts the compressed tar [bytes](Bytes) to the [out_dir](Path). -/// -/// # Errors -/// -/// Returns an error if the extraction fails. -pub fn extract(bytes: &Bytes, out_dir: &Path) -> crate::Result<()> { - RUNTIME - .handle() - .block_on(async move { crate::extract(bytes, out_dir).await }) -} +pub use archive::{extract, get_archive, get_archive_for_target, get_version}; diff --git a/postgresql_archive/src/lib.rs b/postgresql_archive/src/lib.rs index 86f8777..b1a35c7 100644 --- a/postgresql_archive/src/lib.rs +++ b/postgresql_archive/src/lib.rs @@ -106,6 +106,7 @@ #![forbid(unsafe_code)] #![deny(clippy::pedantic)] #![allow(clippy::doc_markdown)] +#![allow(clippy::module_name_repetitions)] #[macro_use] extern crate lazy_static; @@ -117,6 +118,7 @@ mod error; mod github; mod version; +pub use archive::DEFAULT_RELEASES_URL; pub use archive::{extract, get_archive, get_archive_for_target, get_version}; pub use error::{Error, Result}; #[allow(deprecated)] diff --git a/postgresql_archive/tests/archive.rs b/postgresql_archive/tests/archive.rs index 992025f..50bac3b 100644 --- a/postgresql_archive/tests/archive.rs +++ b/postgresql_archive/tests/archive.rs @@ -1,11 +1,11 @@ #[allow(deprecated)] use postgresql_archive::{extract, Version, LATEST, V12, V13, V14, V15, V16}; -use postgresql_archive::{get_archive, get_archive_for_target, get_version}; +use postgresql_archive::{get_archive, get_archive_for_target, get_version, DEFAULT_RELEASES_URL}; use std::fs::{create_dir_all, remove_dir_all}; use test_log::test; async fn test_get_archive_for_version_constant(version: Version) -> anyhow::Result<()> { - let (_archive_version, _archive) = get_archive(&version).await?; + let (_archive_version, _archive) = get_archive(DEFAULT_RELEASES_URL, &version).await?; Ok(()) } @@ -38,7 +38,7 @@ async fn test_get_archive_for_version_constant_v12() -> anyhow::Result<()> { #[test(tokio::test)] async fn test_get_version_not_found() -> postgresql_archive::Result<()> { let invalid_version = Version::new(1, Some(0), Some(0)); - let result = get_version(&invalid_version).await; + let result = get_version(DEFAULT_RELEASES_URL, &invalid_version).await; assert!(result.is_err()); Ok(()) } @@ -50,7 +50,7 @@ async fn test_get_version() -> anyhow::Result<()> { assert!(version.minor.is_none()); assert!(version.release.is_none()); - let latest_version = get_version(version).await?; + let latest_version = get_version(DEFAULT_RELEASES_URL, version).await?; assert_eq!(version.major, latest_version.major); assert!(latest_version.minor.is_some()); @@ -62,7 +62,7 @@ async fn test_get_version() -> anyhow::Result<()> { #[test(tokio::test)] async fn test_get_archive_and_extract() -> anyhow::Result<()> { let version = &LATEST; - let (archive_version, archive) = get_archive(version).await?; + let (archive_version, archive) = get_archive(DEFAULT_RELEASES_URL, version).await?; assert!(archive_version.matches(version)); @@ -77,7 +77,7 @@ async fn test_get_archive_and_extract() -> anyhow::Result<()> { #[test(tokio::test)] async fn test_get_archive_version_not_found() -> postgresql_archive::Result<()> { let invalid_version = Version::new(1, Some(0), Some(0)); - let result = get_archive(&invalid_version).await; + let result = get_archive(DEFAULT_RELEASES_URL, &invalid_version).await; assert!(result.is_err()); Ok(()) } @@ -85,14 +85,20 @@ async fn test_get_archive_version_not_found() -> postgresql_archive::Result<()> #[test(tokio::test)] async fn test_get_archive_for_target_version_not_found() -> postgresql_archive::Result<()> { let invalid_version = Version::new(1, Some(0), Some(0)); - let result = get_archive_for_target(&invalid_version, target_triple::TARGET).await; + let result = get_archive_for_target( + DEFAULT_RELEASES_URL, + &invalid_version, + target_triple::TARGET, + ) + .await; assert!(result.is_err()); Ok(()) } #[test(tokio::test)] async fn test_get_archive_for_target_target_not_found() -> postgresql_archive::Result<()> { - let result = get_archive_for_target(&LATEST, "wasm64-unknown-unknown").await; + let result = + get_archive_for_target(DEFAULT_RELEASES_URL, &LATEST, "wasm64-unknown-unknown").await; assert!(result.is_err()); Ok(()) } @@ -101,7 +107,7 @@ async fn test_get_archive_for_target_target_not_found() -> postgresql_archive::R async fn test_get_archive_for_target() -> anyhow::Result<()> { let version = &LATEST; let (archive_version, _archive) = - get_archive_for_target(version, target_triple::TARGET).await?; + get_archive_for_target(DEFAULT_RELEASES_URL, version, target_triple::TARGET).await?; assert!(archive_version.matches(version)); diff --git a/postgresql_archive/tests/blocking.rs b/postgresql_archive/tests/blocking.rs index db759a4..f2ddeda 100644 --- a/postgresql_archive/tests/blocking.rs +++ b/postgresql_archive/tests/blocking.rs @@ -1,5 +1,6 @@ #[cfg(feature = "blocking")] use postgresql_archive::blocking::{extract, get_archive, get_archive_for_target, get_version}; +use postgresql_archive::DEFAULT_RELEASES_URL; #[cfg(feature = "blocking")] use postgresql_archive::LATEST; #[cfg(feature = "blocking")] @@ -15,7 +16,7 @@ fn test_get_version() -> anyhow::Result<()> { assert!(version.minor.is_none()); assert!(version.release.is_none()); - let latest_version = get_version(version)?; + let latest_version = get_version(DEFAULT_RELEASES_URL, version)?; assert_eq!(version.major, latest_version.major); assert!(latest_version.minor.is_some()); @@ -29,7 +30,7 @@ fn test_get_version() -> anyhow::Result<()> { #[allow(deprecated)] fn test_get_archive_and_extract() -> anyhow::Result<()> { let version = &LATEST; - let (archive_version, archive) = get_archive(version)?; + let (archive_version, archive) = get_archive(DEFAULT_RELEASES_URL, version)?; assert!(archive_version.matches(version)); @@ -46,7 +47,8 @@ fn test_get_archive_and_extract() -> anyhow::Result<()> { #[allow(deprecated)] fn test_get_archive_for_target() -> anyhow::Result<()> { let version = &LATEST; - let (archive_version, _archive) = get_archive_for_target(version, target_triple::TARGET)?; + let (archive_version, _archive) = + get_archive_for_target(DEFAULT_RELEASES_URL, version, target_triple::TARGET)?; assert!(archive_version.matches(version)); diff --git a/postgresql_embedded/README.md b/postgresql_embedded/README.md index 6b19a0d..a693043 100644 --- a/postgresql_embedded/README.md +++ b/postgresql_embedded/README.md @@ -117,7 +117,8 @@ at your option. ## Notes -Uses PostgreSQL binaries from [theseus-rs/postgresql-binaries](https://github.com/theseus-rs/postgresql_binaries). +Uses PostgreSQL binaries from [theseus-rs/postgresql-binaries](https://github.com/theseus-rs/postgresql_binaries) by +default. ## Contribution diff --git a/postgresql_embedded/build/bundle.rs b/postgresql_embedded/build/bundle.rs index 9860f5d..dff39da 100644 --- a/postgresql_embedded/build/bundle.rs +++ b/postgresql_embedded/build/bundle.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use anyhow::Result; -use postgresql_archive::get_archive; +use postgresql_archive::{get_archive, DEFAULT_RELEASES_URL}; use postgresql_archive::{Version, LATEST}; use std::fs::File; use std::io::Write; @@ -31,7 +31,7 @@ pub(crate) async fn stage_postgresql_archive() -> Result<()> { return Ok(()); } - let (asset_version, archive) = get_archive(&version).await?; + let (asset_version, archive) = get_archive(DEFAULT_RELEASES_URL, &version).await?; fs::write(archive_version_file.clone(), asset_version.to_string())?; let mut file = File::create(archive_file.clone())?; diff --git a/postgresql_embedded/src/blocking/mod.rs b/postgresql_embedded/src/blocking/mod.rs index acaab8e..1849f2a 100644 --- a/postgresql_embedded/src/blocking/mod.rs +++ b/postgresql_embedded/src/blocking/mod.rs @@ -1,135 +1,3 @@ -use crate::{Result, Settings, Status}; -use lazy_static::lazy_static; -use postgresql_archive::Version; -use tokio::runtime::Runtime; +mod postgresql; -lazy_static! { - static ref RUNTIME: Runtime = Runtime::new().unwrap(); -} - -/// `PostgreSQL` server -#[derive(Clone, Debug, Default)] -pub struct PostgreSQL { - inner: crate::postgresql::PostgreSQL, -} - -/// `PostgreSQL` server methods -impl PostgreSQL { - /// Create a new [`crate::postgresql::PostgreSQL`] instance - #[must_use] - pub fn new(version: Version, settings: Settings) -> Self { - Self { - inner: crate::postgresql::PostgreSQL::new(version, settings), - } - } - - /// Get the [status](Status) of the `PostgreSQL` server - #[must_use] - pub fn status(&self) -> Status { - self.inner.status() - } - - /// Get the [version](Version) of the `PostgreSQL` server - #[must_use] - pub fn version(&self) -> &Version { - self.inner.version() - } - - /// Get the [settings](Settings) of the `PostgreSQL` server - #[must_use] - pub fn settings(&self) -> &Settings { - self.inner.settings() - } - - /// Set up the database by extracting the archive and initializing the database. - /// If the installation directory already exists, the archive will not be extracted. - /// If the data directory already exists, the database will not be initialized. - /// - /// # Errors - /// - /// Returns an error if the setup fails. - pub fn setup(&mut self) -> Result<()> { - RUNTIME - .handle() - .block_on(async move { self.inner.setup().await }) - } - - /// Start the database and wait for the startup to complete. - /// If the port is set to `0`, the database will be started on a random port. - /// - /// # Errors - /// - /// Returns an error if the startup fails. - pub fn start(&mut self) -> Result<()> { - RUNTIME - .handle() - .block_on(async move { self.inner.start().await }) - } - - /// Stop the database gracefully (smart mode) and wait for the shutdown to complete. - /// - /// # Errors - /// - /// Returns an error if the shutdown fails. - pub fn stop(&self) -> Result<()> { - RUNTIME - .handle() - .block_on(async move { self.inner.stop().await }) - } - - /// Create a new database with the given name. - /// - /// # Errors - /// - /// Returns an error if the database creation fails. - pub fn create_database(&self, database_name: S) -> Result<()> - where - S: AsRef + std::fmt::Debug, - { - RUNTIME - .handle() - .block_on(async move { self.inner.create_database(database_name).await }) - } - - /// Check if a database with the given name exists. - /// - /// # Errors - /// - /// Returns an error if the database existence check fails. - pub fn database_exists(&self, database_name: S) -> Result - where - S: AsRef + std::fmt::Debug, - { - RUNTIME - .handle() - .block_on(async move { self.inner.database_exists(database_name).await }) - } - - /// Drop a database with the given name. - /// - /// # Errors - /// - /// Returns an error if the database drop fails. - pub fn drop_database(&self, database_name: S) -> Result<()> - where - S: AsRef + std::fmt::Debug, - { - RUNTIME - .handle() - .block_on(async move { self.inner.drop_database(database_name).await }) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_postgresql() { - let version = Version::new(16, Some(2), Some(0)); - let postgresql = PostgreSQL::new(version, Settings::default()); - let initial_statuses = [Status::NotInstalled, Status::Installed, Status::Stopped]; - assert!(initial_statuses.contains(&postgresql.status())); - assert_eq!(postgresql.version(), &version); - } -} +pub use postgresql::PostgreSQL; diff --git a/postgresql_embedded/src/blocking/postgresql.rs b/postgresql_embedded/src/blocking/postgresql.rs new file mode 100644 index 0000000..acaab8e --- /dev/null +++ b/postgresql_embedded/src/blocking/postgresql.rs @@ -0,0 +1,135 @@ +use crate::{Result, Settings, Status}; +use lazy_static::lazy_static; +use postgresql_archive::Version; +use tokio::runtime::Runtime; + +lazy_static! { + static ref RUNTIME: Runtime = Runtime::new().unwrap(); +} + +/// `PostgreSQL` server +#[derive(Clone, Debug, Default)] +pub struct PostgreSQL { + inner: crate::postgresql::PostgreSQL, +} + +/// `PostgreSQL` server methods +impl PostgreSQL { + /// Create a new [`crate::postgresql::PostgreSQL`] instance + #[must_use] + pub fn new(version: Version, settings: Settings) -> Self { + Self { + inner: crate::postgresql::PostgreSQL::new(version, settings), + } + } + + /// Get the [status](Status) of the `PostgreSQL` server + #[must_use] + pub fn status(&self) -> Status { + self.inner.status() + } + + /// Get the [version](Version) of the `PostgreSQL` server + #[must_use] + pub fn version(&self) -> &Version { + self.inner.version() + } + + /// Get the [settings](Settings) of the `PostgreSQL` server + #[must_use] + pub fn settings(&self) -> &Settings { + self.inner.settings() + } + + /// Set up the database by extracting the archive and initializing the database. + /// If the installation directory already exists, the archive will not be extracted. + /// If the data directory already exists, the database will not be initialized. + /// + /// # Errors + /// + /// Returns an error if the setup fails. + pub fn setup(&mut self) -> Result<()> { + RUNTIME + .handle() + .block_on(async move { self.inner.setup().await }) + } + + /// Start the database and wait for the startup to complete. + /// If the port is set to `0`, the database will be started on a random port. + /// + /// # Errors + /// + /// Returns an error if the startup fails. + pub fn start(&mut self) -> Result<()> { + RUNTIME + .handle() + .block_on(async move { self.inner.start().await }) + } + + /// Stop the database gracefully (smart mode) and wait for the shutdown to complete. + /// + /// # Errors + /// + /// Returns an error if the shutdown fails. + pub fn stop(&self) -> Result<()> { + RUNTIME + .handle() + .block_on(async move { self.inner.stop().await }) + } + + /// Create a new database with the given name. + /// + /// # Errors + /// + /// Returns an error if the database creation fails. + pub fn create_database(&self, database_name: S) -> Result<()> + where + S: AsRef + std::fmt::Debug, + { + RUNTIME + .handle() + .block_on(async move { self.inner.create_database(database_name).await }) + } + + /// Check if a database with the given name exists. + /// + /// # Errors + /// + /// Returns an error if the database existence check fails. + pub fn database_exists(&self, database_name: S) -> Result + where + S: AsRef + std::fmt::Debug, + { + RUNTIME + .handle() + .block_on(async move { self.inner.database_exists(database_name).await }) + } + + /// Drop a database with the given name. + /// + /// # Errors + /// + /// Returns an error if the database drop fails. + pub fn drop_database(&self, database_name: S) -> Result<()> + where + S: AsRef + std::fmt::Debug, + { + RUNTIME + .handle() + .block_on(async move { self.inner.drop_database(database_name).await }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_postgresql() { + let version = Version::new(16, Some(2), Some(0)); + let postgresql = PostgreSQL::new(version, Settings::default()); + let initial_statuses = [Status::NotInstalled, Status::Installed, Status::Stopped]; + assert!(initial_statuses.contains(&postgresql.status())); + assert_eq!(postgresql.version(), &version); + } +} diff --git a/postgresql_embedded/src/postgresql.rs b/postgresql_embedded/src/postgresql.rs index 780db4d..5e7d6ee 100644 --- a/postgresql_embedded/src/postgresql.rs +++ b/postgresql_embedded/src/postgresql.rs @@ -172,7 +172,7 @@ impl PostgreSQL { // version and installation directory accordingly. This is an optimization to avoid downloading // the archive if the latest version is already installed. if self.version.minor.is_none() || self.version.release.is_none() { - let version = get_version(&self.version).await?; + let version = get_version(&self.settings.releases_url, &self.version).await?; self.version = version; self.settings.installation_dir = self .settings @@ -193,7 +193,7 @@ impl PostgreSQL { debug!("Using bundled installation archive"); (self.version, bytes::Bytes::copy_from_slice(ARCHIVE)) } else { - get_archive(&self.version).await? + get_archive(&self.settings().releases_url, &self.version).await? }; #[cfg(not(feature = "bundled"))] diff --git a/postgresql_embedded/src/settings.rs b/postgresql_embedded/src/settings.rs index 9792fd1..b2c6fcb 100644 --- a/postgresql_embedded/src/settings.rs +++ b/postgresql_embedded/src/settings.rs @@ -1,5 +1,6 @@ use crate::error::{Error, Result}; use home::home_dir; +use postgresql_archive::DEFAULT_RELEASES_URL; use rand::distributions::Alphanumeric; use rand::Rng; use std::collections::HashMap; @@ -17,6 +18,8 @@ pub const BOOTSTRAP_SUPERUSER: &str = "postgres"; /// Database settings #[derive(Clone, Debug, PartialEq)] pub struct Settings { + /// URL for the releases location of the `PostgreSQL` installation archives + pub releases_url: String, /// `PostgreSQL` installation directory pub installation_dir: PathBuf, /// `PostgreSQL` password file @@ -71,6 +74,7 @@ impl Settings { .collect(); Self { + releases_url: DEFAULT_RELEASES_URL.to_string(), installation_dir: home_dir.join(".theseus").join("postgresql"), password_file, data_dir, @@ -121,17 +125,8 @@ impl Settings { parsed_url.query_pairs().into_owned().collect(); let mut settings = Self::default(); - if !parsed_url.username().is_empty() { - settings.username = parsed_url.username().to_string(); - } - if let Some(password) = parsed_url.password() { - settings.password = password.to_string(); - } - if let Some(host) = parsed_url.host() { - settings.host = host.to_string(); - } - if let Some(port) = parsed_url.port() { - settings.port = port; + if let Some(releases_url) = query_parameters.get("releases_url") { + settings.releases_url = releases_url.to_string(); } if let Some(installation_dir) = query_parameters.get("installation_dir") { if let Ok(path) = PathBuf::from_str(installation_dir) { @@ -148,6 +143,18 @@ impl Settings { settings.data_dir = path; } } + if let Some(host) = parsed_url.host() { + settings.host = host.to_string(); + } + if let Some(port) = parsed_url.port() { + settings.port = port; + } + if !parsed_url.username().is_empty() { + settings.username = parsed_url.username().to_string(); + } + if let Some(password) = parsed_url.password() { + settings.password = password.to_string(); + } if let Some(temporary) = query_parameters.get("temporary") { settings.temporary = temporary == "true"; } @@ -240,28 +247,30 @@ mod tests { #[test] fn test_settings_from_url() -> Result<()> { let base_url = "postgresql://postgres:password@localhost:5432/test"; + let releases_url = "releases_url=https%3A%2F%2Fgithub.com"; let installation_dir = "installation_dir=/tmp/postgresql"; let password_file = "password_file=/tmp/.pgpass"; let data_dir = "data_dir=/tmp/data"; let temporary = "temporary=false"; let timeout = "timeout=10"; let configuration = "configuration.max_connections=42"; - let url = format!("{base_url}?{installation_dir}&{password_file}&{data_dir}&{temporary}&{temporary}&{timeout}&{configuration}"); + let url = format!("{base_url}?{releases_url}&{installation_dir}&{password_file}&{data_dir}&{temporary}&{temporary}&{timeout}&{configuration}"); let settings = Settings::from_url(url)?; - assert_eq!(BOOTSTRAP_SUPERUSER, settings.username); - assert_eq!("password", settings.password); - assert_eq!("localhost", settings.host); - assert_eq!(5432, settings.port); - assert_eq!(base_url, settings.url("test")); + assert_eq!("https://github.com", settings.releases_url); assert_eq!(PathBuf::from("/tmp/postgresql"), settings.installation_dir); assert_eq!(PathBuf::from("/tmp/.pgpass"), settings.password_file); assert_eq!(PathBuf::from("/tmp/data"), settings.data_dir); + assert_eq!("localhost", settings.host); + assert_eq!(5432, settings.port); + assert_eq!(BOOTSTRAP_SUPERUSER, settings.username); + assert_eq!("password", settings.password); assert!(!settings.temporary); assert_eq!(Some(Duration::from_secs(10)), settings.timeout); let configuration = HashMap::from([("max_connections".to_string(), "42".to_string())]); assert_eq!(configuration, settings.configuration); + assert_eq!(base_url, settings.url("test")); Ok(()) } From 9866a89fd8b96e79cf61016c0abaf5ca34c165ec Mon Sep 17 00:00:00 2001 From: brianheineman Date: Wed, 19 Jun 2024 19:18:13 -0600 Subject: [PATCH 2/6] fix: reference settings directly instead of via function call --- postgresql_embedded/src/postgresql.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postgresql_embedded/src/postgresql.rs b/postgresql_embedded/src/postgresql.rs index 5e7d6ee..b1c5ddb 100644 --- a/postgresql_embedded/src/postgresql.rs +++ b/postgresql_embedded/src/postgresql.rs @@ -193,7 +193,7 @@ impl PostgreSQL { debug!("Using bundled installation archive"); (self.version, bytes::Bytes::copy_from_slice(ARCHIVE)) } else { - get_archive(&self.settings().releases_url, &self.version).await? + get_archive(&self.settings.releases_url, &self.version).await? }; #[cfg(not(feature = "bundled"))] From cf347cd87e1ba01980f0ff4b4dd84c6b51dc03c9 Mon Sep 17 00:00:00 2001 From: brianheineman Date: Wed, 19 Jun 2024 19:34:20 -0600 Subject: [PATCH 3/6] fix: update examples --- examples/archive_async/src/main.rs | 4 ++-- examples/archive_sync/src/main.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/archive_async/src/main.rs b/examples/archive_async/src/main.rs index dd68cf6..278b1a2 100644 --- a/examples/archive_async/src/main.rs +++ b/examples/archive_async/src/main.rs @@ -1,11 +1,11 @@ #![forbid(unsafe_code)] #![deny(clippy::pedantic)] -use postgresql_archive::{extract, get_archive, Result, LATEST}; +use postgresql_archive::{extract, get_archive, Result, DEFAULT_RELEASES_URL, LATEST}; #[tokio::main] async fn main() -> Result<()> { - let (archive_version, archive) = get_archive(&LATEST).await?; + let (archive_version, archive) = get_archive(DEFAULT_RELEASES_URL, &LATEST).await?; let out_dir = tempfile::tempdir()?.into_path(); extract(&archive, &out_dir).await?; println!( diff --git a/examples/archive_sync/src/main.rs b/examples/archive_sync/src/main.rs index 95795a0..db3d1d9 100644 --- a/examples/archive_sync/src/main.rs +++ b/examples/archive_sync/src/main.rs @@ -2,10 +2,10 @@ #![deny(clippy::pedantic)] use postgresql_archive::blocking::{extract, get_archive}; -use postgresql_archive::{Result, LATEST}; +use postgresql_archive::{Result, DEFAULT_RELEASES_URL, LATEST}; fn main() -> Result<()> { - let (archive_version, archive) = get_archive(&LATEST)?; + let (archive_version, archive) = get_archive(DEFAULT_RELEASES_URL, &LATEST)?; let out_dir = tempfile::tempdir()?.into_path(); extract(&archive, &out_dir)?; println!( From 0cb16e06b7127ec42d0edf6539d3048a2f43eab5 Mon Sep 17 00:00:00 2001 From: brianheineman Date: Wed, 19 Jun 2024 19:41:37 -0600 Subject: [PATCH 4/6] feat: allow releases url to be specified at build time when the bundles flag is set with the POSTGRESQL_RELEASES_URL environment variable --- postgresql_embedded/build/bundle.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/postgresql_embedded/build/bundle.rs b/postgresql_embedded/build/bundle.rs index dff39da..001c005 100644 --- a/postgresql_embedded/build/bundle.rs +++ b/postgresql_embedded/build/bundle.rs @@ -14,6 +14,8 @@ use std::{env, fs}; /// self-contained binary that does not require the PostgreSQL archive to be /// downloaded at runtime. pub(crate) async fn stage_postgresql_archive() -> Result<()> { + let releases_url = env::var("POSTGRESQL_RELEASES_URL").unwrap_or(DEFAULT_RELEASES_URL.to_string()); + println!("PostgreSQL releases URL: {releases_url}"); let postgres_version = env::var("POSTGRESQL_VERSION").unwrap_or(LATEST.to_string()); let version = Version::from_str(postgres_version.as_str())?; println!("PostgreSQL version: {postgres_version}"); @@ -31,7 +33,7 @@ pub(crate) async fn stage_postgresql_archive() -> Result<()> { return Ok(()); } - let (asset_version, archive) = get_archive(DEFAULT_RELEASES_URL, &version).await?; + let (asset_version, archive) = get_archive(&releases_url, &version).await?; fs::write(archive_version_file.clone(), asset_version.to_string())?; let mut file = File::create(archive_file.clone())?; From 03d5a2b0373e47587df77198fcc6598d45d58829 Mon Sep 17 00:00:00 2001 From: brianheineman Date: Wed, 19 Jun 2024 19:55:42 -0600 Subject: [PATCH 5/6] fix: pass settings release_url when bundled flag is set --- postgresql_embedded/build/bundle.rs | 3 ++- postgresql_embedded/src/postgresql.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/postgresql_embedded/build/bundle.rs b/postgresql_embedded/build/bundle.rs index 001c005..37572e3 100644 --- a/postgresql_embedded/build/bundle.rs +++ b/postgresql_embedded/build/bundle.rs @@ -14,7 +14,8 @@ use std::{env, fs}; /// self-contained binary that does not require the PostgreSQL archive to be /// downloaded at runtime. pub(crate) async fn stage_postgresql_archive() -> Result<()> { - let releases_url = env::var("POSTGRESQL_RELEASES_URL").unwrap_or(DEFAULT_RELEASES_URL.to_string()); + let releases_url = + env::var("POSTGRESQL_RELEASES_URL").unwrap_or(DEFAULT_RELEASES_URL.to_string()); println!("PostgreSQL releases URL: {releases_url}"); let postgres_version = env::var("POSTGRESQL_VERSION").unwrap_or(LATEST.to_string()); let version = Version::from_str(postgres_version.as_str())?; diff --git a/postgresql_embedded/src/postgresql.rs b/postgresql_embedded/src/postgresql.rs index b1c5ddb..9210d06 100644 --- a/postgresql_embedded/src/postgresql.rs +++ b/postgresql_embedded/src/postgresql.rs @@ -197,7 +197,7 @@ impl PostgreSQL { }; #[cfg(not(feature = "bundled"))] - let (version, bytes) = { get_archive(&self.version).await? }; + let (version, bytes) = { get_archive(&self.settings.releases_url, &self.version).await? }; self.version = version; extract(&bytes, &self.settings.installation_dir).await?; From 0300aeb6bf8bb5d621fafa9bcee37401805b7f92 Mon Sep 17 00:00:00 2001 From: brianheineman Date: Wed, 19 Jun 2024 20:03:43 -0600 Subject: [PATCH 6/6] docs: updated archive documentation examples --- postgresql_archive/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/postgresql_archive/src/lib.rs b/postgresql_archive/src/lib.rs index b1a35c7..8d8170d 100644 --- a/postgresql_archive/src/lib.rs +++ b/postgresql_archive/src/lib.rs @@ -21,11 +21,11 @@ //! ### Asynchronous API //! //! ```no_run -//! use postgresql_archive::{extract, get_archive, Result, LATEST}; +//! use postgresql_archive::{extract, get_archive, Result, DEFAULT_RELEASES_URL, LATEST}; //! //! #[tokio::main] //! async fn main() -> Result<()> { -//! let (archive_version, archive) = get_archive(&LATEST).await?; +//! let (archive_version, archive) = get_archive(DEFAULT_RELEASES_URL, &LATEST).await?; //! let out_dir = std::env::temp_dir(); //! extract(&archive, &out_dir).await //! } @@ -34,10 +34,10 @@ //! ### Synchronous API //! ```no_run //! #[cfg(feature = "blocking")] { -//! use postgresql_archive::LATEST; +//! use postgresql_archive::{DEFAULT_RELEASES_URL, LATEST}; //! use postgresql_archive::blocking::{extract, get_archive}; //! -//! let (archive_version, archive) = get_archive(&LATEST).unwrap(); +//! let (archive_version, archive) = get_archive(DEFAULT_RELEASES_URL, &LATEST).unwrap(); //! let out_dir = std::env::temp_dir(); //! let result = extract(&archive, &out_dir).unwrap(); //! }