1
- use crate :: cardtest:: { Benchmark , CardtestRunnerDBHelper } ;
1
+ use crate :: {
2
+ benchmark:: Benchmark ,
3
+ cardtest:: CardtestRunnerDBHelper ,
4
+ shell,
5
+ tpch:: { TpchConfig , TpchKit } ,
6
+ } ;
2
7
use anyhow:: Result ;
3
8
use async_trait:: async_trait;
9
+ use std:: {
10
+ env:: { self , consts:: OS } ,
11
+ fs:: { self , File } ,
12
+ path:: { Path , PathBuf } ,
13
+ process:: Command ,
14
+ } ;
4
15
5
- pub struct PostgresDb { }
16
+ const OPTD_DB_NAME : & str = "optd" ;
6
17
18
+ pub struct PostgresDb {
19
+ verbose : bool ,
20
+
21
+ // cache these paths so we don't have to build them multiple times
22
+ _postgres_db_dpath : PathBuf ,
23
+ pgdata_dpath : PathBuf ,
24
+ log_fpath : PathBuf ,
25
+ }
26
+
27
+ /// Conventions I keep for methods of this class:
28
+ /// - Functions should be idempotent. For instance, start_postgres() should not fail if Postgres is already running
29
+ /// - Stop and start functions should be separate
30
+ /// - Setup should be done in build() unless it requires more information (like benchmark)
7
31
impl PostgresDb {
8
- pub async fn new ( ) -> Result < Self > {
9
- Ok ( PostgresDb { } )
32
+ pub async fn build ( verbose : bool ) -> Result < Self > {
33
+ // build paths, sometimes creating them if they don't exist
34
+ let curr_dpath = env:: current_dir ( ) ?;
35
+ let postgres_db_dpath = Path :: new ( file ! ( ) )
36
+ . parent ( )
37
+ . unwrap ( )
38
+ . join ( "postgres_db" )
39
+ . to_path_buf ( ) ;
40
+ let postgres_db_dpath = curr_dpath. join ( postgres_db_dpath) ; // make it absolute
41
+ if !postgres_db_dpath. exists ( ) {
42
+ fs:: create_dir ( & postgres_db_dpath) ?;
43
+ }
44
+ let pgdata_dpath = postgres_db_dpath. join ( "pgdata" ) ;
45
+ let log_fpath = postgres_db_dpath. join ( "postgres_log" ) ;
46
+
47
+ // create Self
48
+ let db = PostgresDb {
49
+ verbose,
50
+ _postgres_db_dpath : postgres_db_dpath,
51
+ pgdata_dpath,
52
+ log_fpath,
53
+ } ;
54
+
55
+ // (re)start postgres
56
+ db. install_postgres ( ) . await ?;
57
+ db. init_pgdata ( ) . await ?;
58
+ db. start_postgres ( ) . await ?;
59
+
60
+ Ok ( db)
61
+ }
62
+
63
+ /// Installs an up-to-date version of Postgres using the OS's package manager
64
+ async fn install_postgres ( & self ) -> Result < ( ) > {
65
+ match OS {
66
+ "macos" => {
67
+ if self . verbose {
68
+ println ! ( "updating and upgrading brew..." ) ;
69
+ }
70
+ shell:: run_command_with_status_check ( "brew update" ) ?;
71
+ shell:: run_command_with_status_check ( "brew upgrade" ) ?;
72
+
73
+ if self . verbose {
74
+ println ! ( "installing postgresql..." ) ;
75
+ }
76
+ shell:: run_command_with_status_check ( "brew install postgresql" ) ?;
77
+ }
78
+ _ => unimplemented ! ( ) ,
79
+ } ;
80
+ Ok ( ( ) )
81
+ }
82
+
83
+ /// Remove the pgdata dir, making sure to stop a running Postgres process if there is one
84
+ /// If there is a Postgres process running on pgdata, it's important to stop it to avoid
85
+ /// corrupting it (not stopping it leads to lots of weird behavior)
86
+ async fn remove_pgdata ( & self ) -> Result < ( ) > {
87
+ if PostgresDb :: get_is_postgres_running ( ) ? {
88
+ self . stop_postgres ( ) . await ?;
89
+ }
90
+ shell:: make_into_empty_dir ( & self . pgdata_dpath ) ?;
91
+ Ok ( ( ) )
92
+ }
93
+
94
+ /// Initializes pgdata_dpath directory if it wasn't already initialized
95
+ async fn init_pgdata ( & self ) -> Result < ( ) > {
96
+ let done_fpath = self . pgdata_dpath . join ( "initdb_done" ) ;
97
+ if !done_fpath. exists ( ) {
98
+ if self . verbose {
99
+ println ! ( "running initdb..." ) ;
100
+ }
101
+ shell:: make_into_empty_dir ( & self . pgdata_dpath ) ?;
102
+ shell:: run_command_with_status_check ( & format ! (
103
+ "initdb {}" ,
104
+ self . pgdata_dpath. to_str( ) . unwrap( )
105
+ ) ) ?;
106
+ File :: create ( done_fpath) ?;
107
+ } else {
108
+ #[ allow( clippy:: collapsible_else_if) ]
109
+ if self . verbose {
110
+ println ! ( "skipped running initdb" ) ;
111
+ }
112
+ }
113
+ Ok ( ( ) )
114
+ }
115
+
116
+ /// Start the Postgres process if it's not already started
117
+ /// It will always be started using the pg_ctl binary installed with the package manager
118
+ /// It will always be started on port 5432
119
+ async fn start_postgres ( & self ) -> Result < ( ) > {
120
+ if !PostgresDb :: get_is_postgres_running ( ) ? {
121
+ if self . verbose {
122
+ println ! ( "starting postgres..." ) ;
123
+ }
124
+ shell:: run_command_with_status_check ( & format ! (
125
+ "pg_ctl -D{} -l{} start" ,
126
+ self . pgdata_dpath. to_str( ) . unwrap( ) ,
127
+ self . log_fpath. to_str( ) . unwrap( )
128
+ ) ) ?;
129
+ } else {
130
+ #[ allow( clippy:: collapsible_else_if) ]
131
+ if self . verbose {
132
+ println ! ( "skipped starting postgres" ) ;
133
+ }
134
+ }
135
+
136
+ Ok ( ( ) )
137
+ }
138
+
139
+ /// Stop the Postgres process started by start_postgres()
140
+ async fn stop_postgres ( & self ) -> Result < ( ) > {
141
+ if PostgresDb :: get_is_postgres_running ( ) ? {
142
+ if self . verbose {
143
+ println ! ( "stopping postgres..." ) ;
144
+ }
145
+ shell:: run_command_with_status_check ( & format ! (
146
+ "pg_ctl -D{} stop" ,
147
+ self . pgdata_dpath. to_str( ) . unwrap( )
148
+ ) ) ?;
149
+ } else {
150
+ #[ allow( clippy:: collapsible_else_if) ]
151
+ if self . verbose {
152
+ println ! ( "skipped stopping postgres" ) ;
153
+ }
154
+ }
155
+
156
+ Ok ( ( ) )
157
+ }
158
+
159
+ /// Check whether postgres is running
160
+ fn get_is_postgres_running ( ) -> Result < bool > {
161
+ Ok ( Command :: new ( "pg_isready" ) . output ( ) ?. status . success ( ) )
162
+ }
163
+
164
+ /// Load the benchmark data without worrying about caching
165
+ async fn load_benchmark_data_raw ( & self , benchmark : & Benchmark ) -> Result < ( ) > {
166
+ match benchmark {
167
+ Benchmark :: Tpch ( tpch_cfg) => self . load_tpch_data ( tpch_cfg) . await ?,
168
+ _ => unimplemented ! ( ) ,
169
+ } ;
170
+ Ok ( ( ) )
171
+ }
172
+
173
+ async fn load_tpch_data ( & self , tpch_cfg : & TpchConfig ) -> Result < ( ) > {
174
+ // start from a clean slate
175
+ self . remove_pgdata ( ) . await ?;
176
+ // since we deleted pgdata we'll need to re-init it
177
+ self . init_pgdata ( ) . await ?;
178
+ // postgres must be started again since remove_pgdata() stops it
179
+ self . start_postgres ( ) . await ?;
180
+ // load the schema. createdb should not fail since we just make a fresh pgdata
181
+ shell:: run_command_with_status_check ( & format ! ( "createdb {}" , OPTD_DB_NAME ) ) ?;
182
+ let tpch_kit = TpchKit :: build ( self . verbose ) ?;
183
+ tpch_kit. gen_tables ( tpch_cfg) ?;
184
+ shell:: run_command_with_status_check ( & format ! (
185
+ "psql {} -f {}" ,
186
+ OPTD_DB_NAME ,
187
+ tpch_kit. schema_fpath. to_str( ) . unwrap( )
188
+ ) ) ?;
189
+ let tbl_fpath_iter = tpch_kit. get_tbl_fpath_iter ( tpch_cfg) . unwrap ( ) ;
190
+ for tbl_fpath in tbl_fpath_iter {
191
+ let tbl_name = tbl_fpath. file_stem ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) ;
192
+ let copy_table_cmd = format ! (
193
+ "\\ copy {} from {} csv delimiter '|'" ,
194
+ tbl_name,
195
+ tbl_fpath. to_str( ) . unwrap( )
196
+ ) ;
197
+ shell:: run_command_with_status_check ( & format ! (
198
+ "psql {} -c \" {}\" " ,
199
+ OPTD_DB_NAME , copy_table_cmd
200
+ ) ) ?;
201
+ }
202
+ Ok ( ( ) )
10
203
}
11
204
}
12
205
@@ -16,7 +209,30 @@ impl CardtestRunnerDBHelper for PostgresDb {
16
209
"Postgres"
17
210
}
18
211
19
- async fn load_database ( & self , _benchmark : & Benchmark ) -> anyhow:: Result < ( ) > {
212
+ /// Load the data of a benchmark with parameters
213
+ /// As an optimization, if this benchmark only has read-only queries and the
214
+ /// data currently loaded was with the same benchmark and parameters, we don't
215
+ /// need to load it again
216
+ async fn load_database ( & self , benchmark : & Benchmark ) -> anyhow:: Result < ( ) > {
217
+ if benchmark. is_readonly ( ) {
218
+ let benchmark_strid = benchmark. get_strid ( ) ;
219
+ let done_fname = format ! ( "{}_done" , benchmark_strid) ;
220
+ let done_fpath = self . pgdata_dpath . join ( done_fname) ;
221
+ if !done_fpath. exists ( ) {
222
+ if self . verbose {
223
+ println ! ( "loading data for {}..." , benchmark_strid) ;
224
+ }
225
+ self . load_benchmark_data_raw ( benchmark) . await ?;
226
+ File :: create ( done_fpath) ?;
227
+ } else {
228
+ #[ allow( clippy:: collapsible_else_if) ]
229
+ if self . verbose {
230
+ println ! ( "skipped loading data for {}" , benchmark_strid) ;
231
+ }
232
+ }
233
+ } else {
234
+ self . load_benchmark_data_raw ( benchmark) . await ?
235
+ }
20
236
Ok ( ( ) )
21
237
}
22
238
0 commit comments