@@ -13,10 +13,12 @@ use postgresql_commands::initdb::InitDbBuilder;
13
13
use postgresql_commands:: pg_ctl:: Mode :: { Start , Stop } ;
14
14
use postgresql_commands:: pg_ctl:: PgCtlBuilder ;
15
15
use postgresql_commands:: pg_ctl:: ShutdownMode :: Fast ;
16
+ use semver:: Version ;
16
17
use sqlx:: { PgPool , Row } ;
17
- use std:: fs:: { remove_dir_all, remove_file} ;
18
+ use std:: fs:: { read_dir , remove_dir_all, remove_file} ;
18
19
use std:: io:: prelude:: * ;
19
20
use std:: net:: TcpListener ;
21
+ use std:: path:: PathBuf ;
20
22
use tracing:: { debug, instrument} ;
21
23
22
24
use crate :: Error :: { CreateDatabaseError , DatabaseExistsError , DropDatabaseError } ;
@@ -73,7 +75,7 @@ impl PostgreSQL {
73
75
Status :: Started
74
76
} else if self . is_initialized ( ) {
75
77
Status :: Stopped
76
- } else if self . is_installed ( ) {
78
+ } else if self . installed_dir ( ) . is_some ( ) {
77
79
Status :: Installed
78
80
} else {
79
81
Status :: NotInstalled
@@ -86,13 +88,48 @@ impl PostgreSQL {
86
88
& self . settings
87
89
}
88
90
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 > {
94
96
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
96
133
}
97
134
98
135
/// Check if the `PostgreSQL` server is initialized
@@ -111,10 +148,14 @@ impl PostgreSQL {
111
148
/// If the data directory already exists, the database will not be initialized.
112
149
#[ instrument( skip( self ) ) ]
113
150
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
+ }
116
158
}
117
-
118
159
if !self . is_initialized ( ) {
119
160
self . initialize ( ) . await ?;
120
161
}
0 commit comments