@@ -13,11 +13,15 @@ use postgresql_commands::AsyncCommandExecutor;
13
13
use postgresql_commands:: CommandBuilder ;
14
14
#[ cfg( not( feature = "tokio" ) ) ]
15
15
use postgresql_commands:: CommandExecutor ;
16
+ use semver:: Version ;
16
17
use sqlx:: { PgPool , Row } ;
18
+ use std:: ffi:: OsStr ;
17
19
use std:: fs:: { remove_dir_all, remove_file} ;
18
20
use std:: io:: prelude:: * ;
19
21
use std:: net:: TcpListener ;
22
+ use std:: path:: PathBuf ;
20
23
use tracing:: { debug, instrument} ;
24
+ use walkdir:: WalkDir ;
21
25
22
26
use crate :: Error :: { CreateDatabaseError , DatabaseExistsError , DropDatabaseError } ;
23
27
@@ -73,7 +77,7 @@ impl PostgreSQL {
73
77
Status :: Started
74
78
} else if self . is_initialized ( ) {
75
79
Status :: Stopped
76
- } else if self . is_installed ( ) {
80
+ } else if self . installed_dir ( ) . is_some ( ) {
77
81
Status :: Installed
78
82
} else {
79
83
Status :: NotInstalled
@@ -86,13 +90,53 @@ impl PostgreSQL {
86
90
& self . settings
87
91
}
88
92
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
+ }
94
101
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
96
140
}
97
141
98
142
/// Check if the `PostgreSQL` server is initialized
@@ -111,10 +155,14 @@ impl PostgreSQL {
111
155
/// If the data directory already exists, the database will not be initialized.
112
156
#[ instrument( skip( self ) ) ]
113
157
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
+ }
116
165
}
117
-
118
166
if !self . is_initialized ( ) {
119
167
self . initialize ( ) . await ?;
120
168
}
0 commit comments