From 3c4bff0a5f8107a267826ccccffb17e656e68583 Mon Sep 17 00:00:00 2001 From: Jeffrey Dallatezza Date: Wed, 12 Feb 2025 11:54:13 -0800 Subject: [PATCH 1/2] Check for existing installations in children before installing. --- Cargo.lock | 1 + postgresql_embedded/Cargo.toml | 1 + postgresql_embedded/src/postgresql.rs | 68 +++++++++++++++++++++++---- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9103e24..beb5758 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2066,6 +2066,7 @@ dependencies = [ "tokio", "tracing", "url", + "walkdir", ] [[package]] diff --git a/postgresql_embedded/Cargo.toml b/postgresql_embedded/Cargo.toml index 205011d..784d19f 100644 --- a/postgresql_embedded/Cargo.toml +++ b/postgresql_embedded/Cargo.toml @@ -30,6 +30,7 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["full"], optional = true } tracing = { workspace = true, features = ["log"] } url = { workspace = true } +walkdir = "2.5.0" [dev-dependencies] anyhow = { workspace = true } diff --git a/postgresql_embedded/src/postgresql.rs b/postgresql_embedded/src/postgresql.rs index 1c5db94..9977503 100644 --- a/postgresql_embedded/src/postgresql.rs +++ b/postgresql_embedded/src/postgresql.rs @@ -13,11 +13,15 @@ use postgresql_commands::AsyncCommandExecutor; use postgresql_commands::CommandBuilder; #[cfg(not(feature = "tokio"))] use postgresql_commands::CommandExecutor; +use semver::Version; use sqlx::{PgPool, Row}; +use std::ffi::OsStr; use std::fs::{remove_dir_all, remove_file}; use std::io::prelude::*; use std::net::TcpListener; +use std::path::PathBuf; use tracing::{debug, instrument}; +use walkdir::WalkDir; use crate::Error::{CreateDatabaseError, DatabaseExistsError, DropDatabaseError}; @@ -73,7 +77,7 @@ impl PostgreSQL { Status::Started } else if self.is_initialized() { Status::Stopped - } else if self.is_installed() { + } else if self.installed_dir().is_some() { Status::Installed } else { Status::NotInstalled @@ -86,13 +90,53 @@ impl PostgreSQL { &self.settings } - /// Check if the `PostgreSQL` server is installed - fn is_installed(&self) -> bool { - let Some(version) = self.settings.version.exact_version() else { - return false; - }; + /// Find a directory where `PostgreSQL` server is installed. + /// This first checks if the installation directory exists and matches the version requirement. + /// If it doesn't, it will search all the child directories for the latest version that matches the requirement. + /// If it returns None, we couldn't find a matching installation. + fn installed_dir(&self) -> Option { + fn file_name_to_version(name: &OsStr) -> Option { + Version::parse(name.to_string_lossy().as_ref()).ok() + } let path = &self.settings.installation_dir; - path.ends_with(version.to_string()) && path.exists() + let maybe_path_version = path + .file_name() + .map(|name| file_name_to_version(name)) + .flatten(); + // If this directory matches the version requirement, we're done. + if let Some(path_version) = maybe_path_version { + if self.settings.version.matches(&path_version) && path.exists() { + return Some(path.to_path_buf()); + } + } + // Otherwise we check the child directories. + let mut max_version: Option = None; + let mut max_path: Option = None; + for entry in WalkDir::new(path).min_depth(1).max_depth(1) { + let Some(entry) = entry.ok() else { + // We ignore filesystem errors. + continue; + }; + // Skip non-directories + if !entry.file_type().is_dir() { + continue; + } + // If it doesn't look like a version, we ignore it. + let Some(version) = file_name_to_version(entry.file_name()) else { + continue; + }; + // If it doesn't match the version requirement, we ignore it. + if !self.settings.version.matches(&version) { + continue; + } + // If we already found a version that's greater or equal, we ignore it. + if max_version.iter().any(|prev_max| *prev_max >= version) { + continue; + } + max_version = Some(version.clone()); + max_path = Some(entry.path().to_path_buf()); + } + max_path } /// Check if the `PostgreSQL` server is initialized @@ -111,10 +155,14 @@ impl PostgreSQL { /// If the data directory already exists, the database will not be initialized. #[instrument(skip(self))] pub async fn setup(&mut self) -> Result<()> { - if !self.is_installed() { - self.install().await?; + match self.installed_dir() { + Some(installed_dir) => { + self.settings.installation_dir = installed_dir; + } + None => { + self.install().await?; + } } - if !self.is_initialized() { self.initialize().await?; } From 7015c02018a873fb44aa8052769468e3388899ff Mon Sep 17 00:00:00 2001 From: brianheineman Date: Wed, 26 Feb 2025 12:31:39 -0700 Subject: [PATCH 2/2] refactor: remove use of walkdir crate and address clippy lints --- Cargo.lock | 9 ++-- postgresql_embedded/Cargo.toml | 1 - postgresql_embedded/src/postgresql.rs | 67 ++++++++++++--------------- 3 files changed, 34 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index beb5758..0b364fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1737,9 +1737,9 @@ checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -1769,9 +1769,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" dependencies = [ "cc", "libc", @@ -2066,7 +2066,6 @@ dependencies = [ "tokio", "tracing", "url", - "walkdir", ] [[package]] diff --git a/postgresql_embedded/Cargo.toml b/postgresql_embedded/Cargo.toml index 784d19f..205011d 100644 --- a/postgresql_embedded/Cargo.toml +++ b/postgresql_embedded/Cargo.toml @@ -30,7 +30,6 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["full"], optional = true } tracing = { workspace = true, features = ["log"] } url = { workspace = true } -walkdir = "2.5.0" [dev-dependencies] anyhow = { workspace = true } diff --git a/postgresql_embedded/src/postgresql.rs b/postgresql_embedded/src/postgresql.rs index 9977503..dc0ede2 100644 --- a/postgresql_embedded/src/postgresql.rs +++ b/postgresql_embedded/src/postgresql.rs @@ -15,13 +15,11 @@ use postgresql_commands::CommandBuilder; use postgresql_commands::CommandExecutor; use semver::Version; use sqlx::{PgPool, Row}; -use std::ffi::OsStr; -use std::fs::{remove_dir_all, remove_file}; +use std::fs::{read_dir, remove_dir_all, remove_file}; use std::io::prelude::*; use std::net::TcpListener; use std::path::PathBuf; use tracing::{debug, instrument}; -use walkdir::WalkDir; use crate::Error::{CreateDatabaseError, DatabaseExistsError, DropDatabaseError}; @@ -95,48 +93,43 @@ impl PostgreSQL { /// If it doesn't, it will search all the child directories for the latest version that matches the requirement. /// If it returns None, we couldn't find a matching installation. fn installed_dir(&self) -> Option { - fn file_name_to_version(name: &OsStr) -> Option { - Version::parse(name.to_string_lossy().as_ref()).ok() - } let path = &self.settings.installation_dir; let maybe_path_version = path .file_name() - .map(|name| file_name_to_version(name)) - .flatten(); + .and_then(|file_name| Version::parse(&file_name.to_string_lossy()).ok()); // If this directory matches the version requirement, we're done. if let Some(path_version) = maybe_path_version { if self.settings.version.matches(&path_version) && path.exists() { - return Some(path.to_path_buf()); - } - } - // Otherwise we check the child directories. - let mut max_version: Option = None; - let mut max_path: Option = None; - for entry in WalkDir::new(path).min_depth(1).max_depth(1) { - let Some(entry) = entry.ok() else { - // We ignore filesystem errors. - continue; - }; - // Skip non-directories - if !entry.file_type().is_dir() { - continue; + return Some(path.clone()); } - // If it doesn't look like a version, we ignore it. - let Some(version) = file_name_to_version(entry.file_name()) else { - continue; - }; - // If it doesn't match the version requirement, we ignore it. - if !self.settings.version.matches(&version) { - continue; - } - // If we already found a version that's greater or equal, we ignore it. - if max_version.iter().any(|prev_max| *prev_max >= version) { - continue; - } - max_version = Some(version.clone()); - max_path = Some(entry.path().to_path_buf()); } - max_path + + // Get all directories in the path as versions. + let mut versions = read_dir(path) + .ok()? + .filter_map(|entry| { + let Some(entry) = entry.ok() else { + // We ignore filesystem errors. + return None; + }; + // Skip non-directories + if !entry.file_type().ok()?.is_dir() { + return None; + } + let file_name = entry.file_name(); + let version = Version::parse(&file_name.to_string_lossy()).ok()?; + if self.settings.version.matches(&version) { + Some((version, entry.path())) + } else { + None + } + }) + .collect::>(); + // Sort the versions in descending order i.e. latest version first + versions.sort_by(|(a, _), (b, _)| b.cmp(a)); + // Get the first matching version as the best match + let version_path = versions.first().map(|(_, path)| path.clone()); + version_path } /// Check if the `PostgreSQL` server is initialized