@@ -13,10 +13,12 @@ use postgresql_commands::initdb::InitDbBuilder;
1313use postgresql_commands:: pg_ctl:: Mode :: { Start , Stop } ;
1414use postgresql_commands:: pg_ctl:: PgCtlBuilder ;
1515use postgresql_commands:: pg_ctl:: ShutdownMode :: Fast ;
16+ use semver:: Version ;
1617use sqlx:: { PgPool , Row } ;
17- use std:: fs:: { remove_dir_all, remove_file} ;
18+ use std:: fs:: { read_dir , remove_dir_all, remove_file} ;
1819use std:: io:: prelude:: * ;
1920use std:: net:: TcpListener ;
21+ use std:: path:: PathBuf ;
2022use tracing:: { debug, instrument} ;
2123
2224use crate :: Error :: { CreateDatabaseError , DatabaseExistsError , DropDatabaseError } ;
@@ -73,7 +75,7 @@ impl PostgreSQL {
7375 Status :: Started
7476 } else if self . is_initialized ( ) {
7577 Status :: Stopped
76- } else if self . is_installed ( ) {
78+ } else if self . installed_dir ( ) . is_some ( ) {
7779 Status :: Installed
7880 } else {
7981 Status :: NotInstalled
@@ -86,13 +88,48 @@ impl PostgreSQL {
8688 & self . settings
8789 }
8890
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- } ;
91+ /// Find a directory where `PostgreSQL` server is installed.
92+ /// This first checks if the installation directory exists and matches the version requirement.
93+ /// If it doesn't, it will search all the child directories for the latest version that matches the requirement.
94+ /// If it returns None, we couldn't find a matching installation.
95+ fn installed_dir ( & self ) -> Option < PathBuf > {
9496 let path = & self . settings . installation_dir ;
95- path. ends_with ( version. to_string ( ) ) && path. exists ( )
97+ let maybe_path_version = path
98+ . file_name ( )
99+ . and_then ( |file_name| Version :: parse ( & file_name. to_string_lossy ( ) ) . ok ( ) ) ;
100+ // If this directory matches the version requirement, we're done.
101+ if let Some ( path_version) = maybe_path_version {
102+ if self . settings . version . matches ( & path_version) && path. exists ( ) {
103+ return Some ( path. clone ( ) ) ;
104+ }
105+ }
106+
107+ // Get all directories in the path as versions.
108+ let mut versions = read_dir ( path)
109+ . ok ( ) ?
110+ . filter_map ( |entry| {
111+ let Some ( entry) = entry. ok ( ) else {
112+ // We ignore filesystem errors.
113+ return None ;
114+ } ;
115+ // Skip non-directories
116+ if !entry. file_type ( ) . ok ( ) ?. is_dir ( ) {
117+ return None ;
118+ }
119+ let file_name = entry. file_name ( ) ;
120+ let version = Version :: parse ( & file_name. to_string_lossy ( ) ) . ok ( ) ?;
121+ if self . settings . version . matches ( & version) {
122+ Some ( ( version, entry. path ( ) ) )
123+ } else {
124+ None
125+ }
126+ } )
127+ . collect :: < Vec < _ > > ( ) ;
128+ // Sort the versions in descending order i.e. latest version first
129+ versions. sort_by ( |( a, _) , ( b, _) | b. cmp ( a) ) ;
130+ // Get the first matching version as the best match
131+ let version_path = versions. first ( ) . map ( |( _, path) | path. clone ( ) ) ;
132+ version_path
96133 }
97134
98135 /// Check if the `PostgreSQL` server is initialized
@@ -111,10 +148,14 @@ impl PostgreSQL {
111148 /// If the data directory already exists, the database will not be initialized.
112149 #[ instrument( skip( self ) ) ]
113150 pub async fn setup ( & mut self ) -> Result < ( ) > {
114- if !self . is_installed ( ) {
115- self . install ( ) . await ?;
151+ match self . installed_dir ( ) {
152+ Some ( installed_dir) => {
153+ self . settings . installation_dir = installed_dir;
154+ }
155+ None => {
156+ self . install ( ) . await ?;
157+ }
116158 }
117-
118159 if !self . is_initialized ( ) {
119160 self . initialize ( ) . await ?;
120161 }
0 commit comments