Skip to content

refactor!: move version from PostgreSQL::new() to Settings #82

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions postgresql_embedded/src/blocking/postgresql.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::{Result, Settings, Status};
use lazy_static::lazy_static;
use postgresql_archive::Version;
use tokio::runtime::Runtime;

lazy_static! {
Expand All @@ -17,9 +16,9 @@ pub struct PostgreSQL {
impl PostgreSQL {
/// Create a new [`crate::postgresql::PostgreSQL`] instance
#[must_use]
pub fn new(version: Version, settings: Settings) -> Self {
pub fn new(settings: Settings) -> Self {
Self {
inner: crate::postgresql::PostgreSQL::new(version, settings),
inner: crate::postgresql::PostgreSQL::new(settings),
}
}

Expand All @@ -29,12 +28,6 @@ impl PostgreSQL {
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 {
Expand Down Expand Up @@ -123,13 +116,17 @@ impl PostgreSQL {
#[cfg(test)]
mod test {
use super::*;
use postgresql_archive::Version;

#[test]
fn test_postgresql() {
let version = Version::new(16, Some(2), Some(0));
let postgresql = PostgreSQL::new(version, Settings::default());
let version = Version::new(16, Some(3), Some(0));
let settings = Settings {
version,
..Settings::default()
};
let postgresql = PostgreSQL::new(settings);
let initial_statuses = [Status::NotInstalled, Status::Installed, Status::Stopped];
assert!(initial_statuses.contains(&postgresql.status()));
assert_eq!(postgresql.version(), &version);
}
}
91 changes: 26 additions & 65 deletions postgresql_embedded/src/postgresql.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::error::Error::{DatabaseInitializationError, DatabaseStartError, DatabaseStopError};
use crate::error::Result;
use crate::settings::{Settings, BOOTSTRAP_SUPERUSER};
use postgresql_archive::get_version;
use postgresql_archive::{extract, get_archive};
use postgresql_archive::{get_version, Version};
use postgresql_commands::initdb::InitDbBuilder;
use postgresql_commands::pg_ctl::Mode::{Start, Stop};
use postgresql_commands::pg_ctl::PgCtlBuilder;
Expand All @@ -16,26 +16,10 @@ use postgresql_commands::CommandExecutor;
use std::fs::{remove_dir_all, remove_file};
use std::io::prelude::*;
use std::net::TcpListener;
#[cfg(feature = "bundled")]
use std::str::FromStr;
use tracing::{debug, instrument};

use crate::Error::{CreateDatabaseError, DatabaseExistsError, DropDatabaseError};

#[cfg(feature = "bundled")]
lazy_static::lazy_static! {
#[allow(clippy::unwrap_used)]
pub(crate) static ref ARCHIVE_VERSION: Version = {
let version_string = include_str!(concat!(std::env!("OUT_DIR"), "/postgresql.version"));
let version = Version::from_str(version_string).unwrap();
debug!("Bundled installation archive version {version}");
version
};
}

#[cfg(feature = "bundled")]
pub(crate) const ARCHIVE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/postgresql.tar.gz"));

const PGDATABASE: &str = "PGDATABASE";

/// `PostgreSQL` status
Expand All @@ -54,21 +38,21 @@ pub enum Status {
/// `PostgreSQL` server
#[derive(Clone, Debug)]
pub struct PostgreSQL {
version: Version,
settings: Settings,
}

/// `PostgreSQL` server methods
impl PostgreSQL {
/// Create a new [`PostgreSQL`] instance
#[must_use]
pub fn new(version: Version, settings: Settings) -> Self {
let mut postgresql = PostgreSQL { version, settings };
pub fn new(settings: Settings) -> Self {
let mut postgresql = PostgreSQL { settings };

// If the minor and release version are set, append the version to the installation directory
// to avoid conflicts with other versions. This will also facilitate setting the status
// of the server to the correct initial value. If the minor and release version are not set,
// the installation directory will be determined dynamically during the installation process.
let version = postgresql.settings.version;
if version.minor.is_some() && version.release.is_some() {
let path = &postgresql.settings.installation_dir;
let version_string = version.to_string();
Expand All @@ -82,20 +66,6 @@ impl PostgreSQL {
postgresql
}

/// Get the default version used if not otherwise specified
#[must_use]
pub fn default_version() -> Version {
#[cfg(feature = "bundled")]
{
*ARCHIVE_VERSION
}

#[cfg(not(feature = "bundled"))]
{
postgresql_archive::LATEST
}
}

/// Get the [status](Status) of the PostgreSQL server
#[instrument(level = "debug", skip(self))]
pub fn status(&self) -> Status {
Expand All @@ -110,12 +80,6 @@ impl PostgreSQL {
}
}

/// Get the [version](Version) of the `PostgreSQL` server
#[must_use]
pub fn version(&self) -> &Version {
&self.version
}

/// Get the [settings](Settings) of the `PostgreSQL` server
#[must_use]
pub fn settings(&self) -> &Settings {
Expand All @@ -124,12 +88,13 @@ impl PostgreSQL {

/// Check if the `PostgreSQL` server is installed
fn is_installed(&self) -> bool {
if self.version.minor.is_none() || self.version.release.is_none() {
let version = self.settings.version;
if version.minor.is_none() || version.release.is_none() {
return false;
}

let path = &self.settings.installation_dir;
path.ends_with(self.version.to_string()) && path.exists()
path.ends_with(version.to_string()) && path.exists()
}

/// Check if the `PostgreSQL` server is initialized
Expand Down Expand Up @@ -166,18 +131,21 @@ impl PostgreSQL {
/// returned.
#[instrument(skip(self))]
async fn install(&mut self) -> Result<()> {
debug!("Starting installation process for version {}", self.version);
debug!(
"Starting installation process for version {}",
self.settings.version
);

// If the minor and release version are not set, determine the latest version and update the
// 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.settings.releases_url, &self.version).await?;
self.version = version;
if self.settings.version.minor.is_none() || self.settings.version.release.is_none() {
self.settings.version =
get_version(&self.settings.releases_url, &self.settings.version).await?;
self.settings.installation_dir = self
.settings
.installation_dir
.join(self.version.to_string());
.join(self.settings.version.to_string());
}

if self.settings.installation_dir.exists() {
Expand All @@ -189,22 +157,26 @@ impl PostgreSQL {
// If the requested version is the same as the version of the bundled archive, use the bundled
// archive. This avoids downloading the archive in environments where internet access is
// restricted or undesirable.
let (version, bytes) = if *ARCHIVE_VERSION == self.version {
let (version, bytes) = if *crate::settings::ARCHIVE_VERSION == self.settings.version {
debug!("Using bundled installation archive");
(self.version, bytes::Bytes::copy_from_slice(ARCHIVE))
(
self.settings.version,
bytes::Bytes::copy_from_slice(crate::settings::ARCHIVE),
)
} else {
get_archive(&self.settings.releases_url, &self.version).await?
get_archive(&self.settings.releases_url, &self.settings.version).await?
};

#[cfg(not(feature = "bundled"))]
let (version, bytes) = { get_archive(&self.settings.releases_url, &self.version).await? };
let (version, bytes) =
{ get_archive(&self.settings.releases_url, &self.settings.version).await? };

self.version = version;
self.settings.version = version;
extract(&bytes, &self.settings.installation_dir).await?;

debug!(
"Installed PostgreSQL version {} to {}",
self.version,
self.settings.version,
self.settings.installation_dir.to_string_lossy()
);

Expand Down Expand Up @@ -433,9 +405,7 @@ impl PostgreSQL {
/// Default `PostgreSQL` server
impl Default for PostgreSQL {
fn default() -> Self {
let version = PostgreSQL::default_version();
let settings = Settings::default();
Self::new(version, settings)
Self::new(Settings::default())
}
}

Expand All @@ -459,12 +429,3 @@ impl Drop for PostgreSQL {
}
}
}

#[cfg(test)]
mod tests {
#[test]
#[cfg(feature = "bundled")]
fn test_archive_version() {
assert!(!super::ARCHIVE_VERSION.to_string().is_empty());
}
}
51 changes: 49 additions & 2 deletions postgresql_embedded/src/settings.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::error::{Error, Result};
use home::home_dir;
use postgresql_archive::DEFAULT_RELEASES_URL;
use postgresql_archive::{Version, DEFAULT_RELEASES_URL};
use rand::distributions::Alphanumeric;
use rand::Rng;
use std::collections::HashMap;
Expand All @@ -12,6 +12,20 @@ use std::str::FromStr;
use std::time::Duration;
use url::Url;

#[cfg(feature = "bundled")]
lazy_static::lazy_static! {
#[allow(clippy::unwrap_used)]
pub(crate) static ref ARCHIVE_VERSION: Version = {
let version_string = include_str!(concat!(std::env!("OUT_DIR"), "/postgresql.version"));
let version = Version::from_str(version_string).unwrap();
tracing::debug!("Bundled installation archive version {version}");
version
};
}

#[cfg(feature = "bundled")]
pub(crate) const ARCHIVE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/postgresql.tar.gz"));

/// `PostgreSQL` superuser
pub const BOOTSTRAP_SUPERUSER: &str = "postgres";

Expand All @@ -20,6 +34,8 @@ pub const BOOTSTRAP_SUPERUSER: &str = "postgres";
pub struct Settings {
/// URL for the releases location of the `PostgreSQL` installation archives
pub releases_url: String,
/// Version of `PostgreSQL` to install
pub version: Version,
/// `PostgreSQL` installation directory
pub installation_dir: PathBuf,
/// `PostgreSQL` password file
Expand Down Expand Up @@ -75,6 +91,7 @@ impl Settings {

Self {
releases_url: DEFAULT_RELEASES_URL.to_string(),
version: default_version(),
installation_dir: home_dir.join(".theseus").join("postgresql"),
password_file,
data_dir,
Expand Down Expand Up @@ -128,6 +145,9 @@ impl Settings {
if let Some(releases_url) = query_parameters.get("releases_url") {
settings.releases_url = releases_url.to_string();
}
if let Some(version) = query_parameters.get("version") {
settings.version = Version::from_str(version)?;
}
if let Some(installation_dir) = query_parameters.get("installation_dir") {
if let Ok(path) = PathBuf::from_str(installation_dir) {
settings.installation_dir = path;
Expand Down Expand Up @@ -214,11 +234,31 @@ impl Default for Settings {
}
}

/// Get the default version used if not otherwise specified
#[must_use]
fn default_version() -> Version {
#[cfg(feature = "bundled")]
{
*ARCHIVE_VERSION
}

#[cfg(not(feature = "bundled"))]
{
postgresql_archive::LATEST
}
}

#[cfg(test)]
mod tests {
use super::*;
use test_log::test;

#[test]
#[cfg(feature = "bundled")]
fn test_archive_version() {
assert!(!super::ARCHIVE_VERSION.to_string().is_empty());
}

#[test]
fn test_settings_new() {
let settings = Settings::new();
Expand Down Expand Up @@ -248,17 +288,19 @@ mod tests {
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 version = "version=16.3.0";
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}?{releases_url}&{installation_dir}&{password_file}&{data_dir}&{temporary}&{temporary}&{timeout}&{configuration}");
let url = format!("{base_url}?{releases_url}&{version}&{installation_dir}&{password_file}&{data_dir}&{temporary}&{temporary}&{timeout}&{configuration}");

let settings = Settings::from_url(url)?;

assert_eq!("https://github.com", settings.releases_url);
assert_eq!(Version::new(16, Some(3), Some(0)), settings.version);
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);
Expand All @@ -280,6 +322,11 @@ mod tests {
assert!(Settings::from_url("^`~").is_err());
}

#[test]
fn test_settings_from_url_invalid_version() {
assert!(Settings::from_url("postgresql://?version=foo").is_err());
}

#[test]
fn test_settings_from_url_invalid_timeout() {
assert!(Settings::from_url("postgresql://?timeout=foo").is_err());
Expand Down
Loading