@@ -13,11 +13,15 @@ use postgresql_commands::AsyncCommandExecutor;
1313use postgresql_commands:: CommandBuilder ;
1414#[ cfg( not( feature = "tokio" ) ) ]
1515use postgresql_commands:: CommandExecutor ;
16+ use semver:: Version ;
1617use sqlx:: { PgPool , Row } ;
18+ use std:: ffi:: OsStr ;
1719use std:: fs:: { remove_dir_all, remove_file} ;
1820use std:: io:: prelude:: * ;
1921use std:: net:: TcpListener ;
22+ use std:: path:: PathBuf ;
2023use tracing:: { debug, instrument} ;
24+ use walkdir:: WalkDir ;
2125
2226use crate :: Error :: { CreateDatabaseError , DatabaseExistsError , DropDatabaseError } ;
2327
@@ -73,7 +77,7 @@ impl PostgreSQL {
7377 Status :: Started
7478 } else if self . is_initialized ( ) {
7579 Status :: Stopped
76- } else if self . is_installed ( ) {
80+ } else if self . installed_dir ( ) . is_some ( ) {
7781 Status :: Installed
7882 } else {
7983 Status :: NotInstalled
@@ -86,13 +90,53 @@ impl PostgreSQL {
8690 & self . settings
8791 }
8892
89- /// Check if the `PostgreSQL` server is installed
90- fn is_installed ( & self ) -> bool {
91- let Some ( version) = self . settings . version . exact_version ( ) else {
92- return false ;
93- } ;
93+ /// Find a directory where `PostgreSQL` server is installed.
94+ /// This first checks if the installation directory exists and matches the version requirement.
95+ /// If it doesn't, it will search all the child directories for the latest version that matches the requirement.
96+ /// If it returns None, we couldn't find a matching installation.
97+ fn installed_dir ( & self ) -> Option < PathBuf > {
98+ fn file_name_to_version ( name : & OsStr ) -> Option < Version > {
99+ Version :: parse ( name. to_string_lossy ( ) . as_ref ( ) ) . ok ( )
100+ }
94101 let path = & self . settings . installation_dir ;
95- path. ends_with ( version. to_string ( ) ) && path. exists ( )
102+ let maybe_path_version = path
103+ . file_name ( )
104+ . map ( |name| file_name_to_version ( name) )
105+ . flatten ( ) ;
106+ // If this directory matches the version requirement, we're done.
107+ if let Some ( path_version) = maybe_path_version {
108+ if self . settings . version . matches ( & path_version) && path. exists ( ) {
109+ return Some ( path. to_path_buf ( ) ) ;
110+ }
111+ }
112+ // Otherwise we check the child directories.
113+ let mut max_version: Option < Version > = None ;
114+ let mut max_path: Option < PathBuf > = None ;
115+ for entry in WalkDir :: new ( path) . min_depth ( 1 ) . max_depth ( 1 ) {
116+ let Some ( entry) = entry. ok ( ) else {
117+ // We ignore filesystem errors.
118+ continue ;
119+ } ;
120+ // Skip non-directories
121+ if !entry. file_type ( ) . is_dir ( ) {
122+ continue ;
123+ }
124+ // If it doesn't look like a version, we ignore it.
125+ let Some ( version) = file_name_to_version ( entry. file_name ( ) ) else {
126+ continue ;
127+ } ;
128+ // If it doesn't match the version requirement, we ignore it.
129+ if !self . settings . version . matches ( & version) {
130+ continue ;
131+ }
132+ // If we already found a version that's greater or equal, we ignore it.
133+ if max_version. iter ( ) . any ( |prev_max| * prev_max >= version) {
134+ continue ;
135+ }
136+ max_version = Some ( version. clone ( ) ) ;
137+ max_path = Some ( entry. path ( ) . to_path_buf ( ) ) ;
138+ }
139+ max_path
96140 }
97141
98142 /// Check if the `PostgreSQL` server is initialized
@@ -111,10 +155,14 @@ impl PostgreSQL {
111155 /// If the data directory already exists, the database will not be initialized.
112156 #[ instrument( skip( self ) ) ]
113157 pub async fn setup ( & mut self ) -> Result < ( ) > {
114- if !self . is_installed ( ) {
115- self . install ( ) . await ?;
158+ match self . installed_dir ( ) {
159+ Some ( installed_dir) => {
160+ self . settings . installation_dir = installed_dir;
161+ }
162+ None => {
163+ self . install ( ) . await ?;
164+ }
116165 }
117-
118166 if !self . is_initialized ( ) {
119167 self . initialize ( ) . await ?;
120168 }
0 commit comments