pg.c (9300B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014--2025 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 /** 18 * @file pg.c 19 * @brief Low-level (statement-level) Postgres database access for the exchange 20 * @author Florian Dold 21 * @author Christian Grothoff 22 * @author Sree Harsha Totakura 23 * @author Marcello Stanisci 24 * @author Özgür Kesim 25 */ 26 #include <poll.h> 27 #include <pthread.h> 28 #include <libpq-fe.h> 29 struct TALER_EXCHANGEDB_PostgresContext; 30 #define GNUNET_PQ_RECONNECT_CALLBACK_CLOSURE \ 31 struct TALER_EXCHANGEDB_PostgresContext 32 #include "helper.h" 33 #include "exchangedb_lib.h" 34 #include "exchange-database/preflight.h" 35 36 /** 37 * Set to 1 to enable Postgres auto_explain module. This will 38 * slow down things a _lot_, but also provide extensive logging 39 * in the Postgres database logger for performance analysis. 40 */ 41 #define AUTO_EXPLAIN 0 42 43 44 /** 45 * Function called each time we connect or reconnect to the 46 * database. Gives the application a chance to run some 47 * per-connection initialization logic. 48 * 49 * @param pg database conntext in the exchange 50 * @param pq database connection handle 51 */ 52 static void 53 reconnect_cb (struct TALER_EXCHANGEDB_PostgresContext *pg, 54 struct GNUNET_PQ_Context *pq) 55 { 56 #if AUTO_EXPLAIN 57 /* Enable verbose logging to see where queries do not 58 properly use indices */ 59 struct GNUNET_PQ_ExecuteStatement es[] = { 60 GNUNET_PQ_make_try_execute ("LOAD 'auto_explain';"), 61 GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"), 62 GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"), 63 GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"), 64 /* https://wiki.postgresql.org/wiki/Serializable suggests to really 65 force the default to 'serializable' if SSI is to be used. */ 66 GNUNET_PQ_make_try_execute ( 67 "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"), 68 GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"), 69 GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"), 70 GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"), 71 /* Mergejoin causes issues, see Postgres #18380 */ 72 GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"), 73 GNUNET_PQ_EXECUTE_STATEMENT_END 74 }; 75 #else 76 struct GNUNET_PQ_ExecuteStatement es[] = { 77 GNUNET_PQ_make_try_execute ( 78 "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"), 79 GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"), 80 GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"), 81 /* Mergejoin causes issues, see Postgres #18380 */ 82 GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"), 83 GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"), 84 GNUNET_PQ_EXECUTE_STATEMENT_END 85 }; 86 #endif 87 88 if (GNUNET_OK != 89 GNUNET_PQ_exec_statements (pq, 90 es)) 91 { 92 GNUNET_break (0); 93 return; 94 } 95 pg->prep_gen++; 96 } 97 98 99 /** 100 * Connect to the db if the connection does not exist yet. 101 * 102 * @param[in,out] pg the database state 103 * @return #GNUNET_OK on success 104 */ 105 static enum GNUNET_GenericReturnValue 106 internal_setup (struct TALER_EXCHANGEDB_PostgresContext *pg) 107 { 108 struct GNUNET_PQ_Context *db_conn; 109 110 if (NULL != pg->conn) 111 return GNUNET_OK; 112 db_conn = GNUNET_PQ_init (pg->cfg, 113 "exchangedb-postgres", 114 &reconnect_cb, 115 pg); 116 if (NULL == db_conn) 117 return GNUNET_SYSERR; 118 if (0 == pg->prep_gen) 119 { 120 GNUNET_PQ_disconnect (db_conn); 121 return GNUNET_SYSERR; 122 } 123 if (0 == pg->prep_gen) 124 { 125 GNUNET_PQ_disconnect (db_conn); 126 return GNUNET_SYSERR; 127 } 128 pg->conn = db_conn; 129 return GNUNET_OK; 130 } 131 132 133 /** 134 * Initialize the database connection. 135 * 136 * @param cfg configuration to use 137 * @param check_current true to check if the database schema is current 138 * @return NULL on failure 139 */ 140 static struct TALER_EXCHANGEDB_PostgresContext * 141 do_connect (const struct GNUNET_CONFIGURATION_Handle *cfg, 142 bool check_current) 143 { 144 struct TALER_EXCHANGEDB_PostgresContext *pg; 145 unsigned long long dpl; 146 147 pg = GNUNET_new (struct TALER_EXCHANGEDB_PostgresContext); 148 pg->cfg = cfg; 149 if (GNUNET_OK != 150 GNUNET_CONFIGURATION_get_value_filename (cfg, 151 "exchangedb-postgres", 152 "SQL_DIR", 153 &pg->sql_dir)) 154 { 155 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 156 "exchangedb-postgres", 157 "SQL_DIR"); 158 goto fail; 159 } 160 if (GNUNET_OK != 161 GNUNET_CONFIGURATION_get_value_string (cfg, 162 "exchange", 163 "BASE_URL", 164 &pg->exchange_url)) 165 { 166 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 167 "exchange", 168 "BASE_URL"); 169 goto fail; 170 } 171 if (GNUNET_OK != 172 GNUNET_CONFIGURATION_get_value_time (cfg, 173 "exchangedb", 174 "IDLE_RESERVE_EXPIRATION_TIME", 175 &pg->idle_reserve_expiration_time)) 176 { 177 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 178 "exchangedb", 179 "IDLE_RESERVE_EXPIRATION_TIME"); 180 goto fail; 181 } 182 if (GNUNET_OK != 183 GNUNET_CONFIGURATION_get_value_time (cfg, 184 "exchangedb", 185 "MAX_AML_PROGRAM_RUNTIME", 186 &pg->max_aml_program_runtime)) 187 { 188 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 189 "exchangedb", 190 "MAX_AML_PROGRAM_RUNTIME"); 191 goto fail; 192 } 193 if (GNUNET_OK != 194 GNUNET_CONFIGURATION_get_value_time (cfg, 195 "exchangedb", 196 "LEGAL_RESERVE_EXPIRATION_TIME", 197 &pg->legal_reserve_expiration_time)) 198 { 199 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 200 "exchangedb", 201 "LEGAL_RESERVE_EXPIRATION_TIME"); 202 goto fail; 203 } 204 if (GNUNET_OK != 205 GNUNET_CONFIGURATION_get_value_time (cfg, 206 "exchangedb", 207 "AGGREGATOR_SHIFT", 208 &pg->aggregator_shift)) 209 { 210 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, 211 "exchangedb", 212 "AGGREGATOR_SHIFT"); 213 } 214 if (GNUNET_OK != 215 GNUNET_CONFIGURATION_get_value_number (cfg, 216 "exchangedb", 217 "DEFAULT_PURSE_LIMIT", 218 &dpl)) 219 { 220 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, 221 "exchangedb", 222 "DEFAULT_PURSE_LIMIT"); 223 pg->def_purse_limit = 1; 224 } 225 else 226 { 227 pg->def_purse_limit = (uint32_t) dpl; 228 } 229 230 if (GNUNET_OK != 231 TALER_config_get_currency (cfg, 232 "exchange", 233 &pg->currency)) 234 { 235 goto fail; 236 } 237 if (GNUNET_OK != 238 internal_setup (pg)) 239 { 240 goto fail; 241 } 242 if (check_current && 243 (GNUNET_OK != 244 GNUNET_PQ_check_current (pg->conn, 245 "exchange-")) ) 246 { 247 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 248 "Database schema is not up-to-date. Try running taler-exchange-dbinit or taler-exchange-dbconfig!\n"); 249 goto fail; 250 } 251 return pg; 252 253 fail: 254 TALER_EXCHANGEDB_disconnect (pg); 255 GNUNET_free (pg); 256 return NULL; 257 } 258 259 260 struct TALER_EXCHANGEDB_PostgresContext * 261 TALER_EXCHANGEDB_connect ( 262 const struct GNUNET_CONFIGURATION_Handle *cfg) 263 { 264 return do_connect (cfg, 265 true); 266 } 267 268 269 struct TALER_EXCHANGEDB_PostgresContext * 270 TALER_EXCHANGEDB_connect_admin ( 271 const struct GNUNET_CONFIGURATION_Handle *cfg) 272 { 273 return do_connect (cfg, 274 false); 275 } 276 277 278 void 279 TALER_EXCHANGEDB_disconnect (struct TALER_EXCHANGEDB_PostgresContext *pg) 280 { 281 if (NULL == pg) 282 return; 283 if (NULL != pg->conn) 284 { 285 GNUNET_PQ_disconnect (pg->conn); 286 pg->conn = NULL; 287 } 288 GNUNET_free (pg->exchange_url); 289 GNUNET_free (pg->sql_dir); 290 GNUNET_free (pg->currency); 291 GNUNET_free (pg); 292 } 293 294 295 /* end of plugin_exchangedb_postgres.c */