pg.c (9881B)
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 * Counts how often we have established a fresh @e conn 45 * to the database. Used to re-prepare statements. 46 */ 47 unsigned long long TEH_PG_prep_gen_; 48 49 /** 50 * Function called each time we connect or reconnect to the 51 * database. Gives the application a chance to run some 52 * per-connection initialization logic. 53 * 54 * @param pg database context of the exchange 55 * @param pq database connection handle 56 */ 57 static void 58 reconnect_cb (struct TALER_EXCHANGEDB_PostgresContext *pg, 59 struct GNUNET_PQ_Context *pq) 60 { 61 #if AUTO_EXPLAIN 62 /* Enable verbose logging to see where queries do not 63 properly use indices */ 64 struct GNUNET_PQ_ExecuteStatement es[] = { 65 GNUNET_PQ_make_try_execute ("LOAD 'auto_explain';"), 66 GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"), 67 GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"), 68 GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"), 69 /* https://wiki.postgresql.org/wiki/Serializable suggests to really 70 force the default to 'serializable' if SSI is to be used. */ 71 GNUNET_PQ_make_try_execute ( 72 "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"), 73 GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"), 74 GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"), 75 GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"), 76 /* Mergejoin causes issues, see Postgres #18380 */ 77 GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"), 78 GNUNET_PQ_EXECUTE_STATEMENT_END 79 }; 80 #else 81 struct GNUNET_PQ_ExecuteStatement es[] = { 82 GNUNET_PQ_make_try_execute ( 83 "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"), 84 GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"), 85 GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"), 86 /* Mergejoin causes issues, see Postgres #18380 */ 87 GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"), 88 GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"), 89 GNUNET_PQ_EXECUTE_STATEMENT_END 90 }; 91 #endif 92 93 if (GNUNET_OK != 94 GNUNET_PQ_exec_statements (pq, 95 es)) 96 { 97 GNUNET_break (0); 98 return; 99 } 100 { 101 char *set_time; 102 enum GNUNET_GenericReturnValue ret; 103 104 GNUNET_asprintf (&set_time, 105 "SET taler.timetravel_us = '%lld'", 106 GNUNET_TIME_get_offset ()); 107 { 108 struct GNUNET_PQ_ExecuteStatement time_es[] = { 109 GNUNET_PQ_make_execute (set_time), 110 GNUNET_PQ_EXECUTE_STATEMENT_END 111 }; 112 113 ret = GNUNET_PQ_exec_statements (pq, 114 time_es); 115 } 116 GNUNET_free (set_time); 117 if (GNUNET_OK != ret) 118 { 119 GNUNET_break (0); 120 return; 121 } 122 } 123 TEH_PG_prep_gen_++; 124 } 125 126 127 /** 128 * Connect to the db if the connection does not exist yet. 129 * 130 * @param[in,out] pg the database state 131 * @return #GNUNET_OK on success 132 */ 133 static enum GNUNET_GenericReturnValue 134 internal_setup (struct TALER_EXCHANGEDB_PostgresContext *pg) 135 { 136 struct GNUNET_PQ_Context *db_conn; 137 138 if (NULL != pg->conn) 139 return GNUNET_OK; 140 db_conn = GNUNET_PQ_init (pg->cfg, 141 "exchangedb-postgres", 142 &reconnect_cb, 143 pg); 144 if (NULL == db_conn) 145 return GNUNET_SYSERR; 146 if (0 == TEH_PG_prep_gen_) 147 { 148 GNUNET_PQ_disconnect (db_conn); 149 return GNUNET_SYSERR; 150 } 151 pg->conn = db_conn; 152 return GNUNET_OK; 153 } 154 155 156 /** 157 * Initialize the database connection. 158 * 159 * @param cfg configuration to use 160 * @param check_current true to check if the database schema is current 161 * @return NULL on failure 162 */ 163 static struct TALER_EXCHANGEDB_PostgresContext * 164 do_connect (const struct GNUNET_CONFIGURATION_Handle *cfg, 165 bool check_current) 166 { 167 struct TALER_EXCHANGEDB_PostgresContext *pg; 168 unsigned long long dpl; 169 170 pg = GNUNET_new (struct TALER_EXCHANGEDB_PostgresContext); 171 pg->cfg = cfg; 172 if (GNUNET_OK != 173 GNUNET_CONFIGURATION_get_value_filename (cfg, 174 "exchangedb-postgres", 175 "SQL_DIR", 176 &pg->sql_dir)) 177 { 178 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 179 "exchangedb-postgres", 180 "SQL_DIR"); 181 goto fail; 182 } 183 if (GNUNET_OK != 184 GNUNET_CONFIGURATION_get_value_string (cfg, 185 "exchange", 186 "BASE_URL", 187 &pg->exchange_url)) 188 { 189 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 190 "exchange", 191 "BASE_URL"); 192 goto fail; 193 } 194 if (GNUNET_OK != 195 GNUNET_CONFIGURATION_get_value_time (cfg, 196 "exchangedb", 197 "IDLE_RESERVE_EXPIRATION_TIME", 198 &pg->idle_reserve_expiration_time)) 199 { 200 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 201 "exchangedb", 202 "IDLE_RESERVE_EXPIRATION_TIME"); 203 goto fail; 204 } 205 if (GNUNET_OK != 206 GNUNET_CONFIGURATION_get_value_time (cfg, 207 "exchangedb", 208 "MAX_AML_PROGRAM_RUNTIME", 209 &pg->max_aml_program_runtime)) 210 { 211 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 212 "exchangedb", 213 "MAX_AML_PROGRAM_RUNTIME"); 214 goto fail; 215 } 216 if (GNUNET_OK != 217 GNUNET_CONFIGURATION_get_value_time (cfg, 218 "exchangedb", 219 "LEGAL_RESERVE_EXPIRATION_TIME", 220 &pg->legal_reserve_expiration_time)) 221 { 222 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 223 "exchangedb", 224 "LEGAL_RESERVE_EXPIRATION_TIME"); 225 goto fail; 226 } 227 if (GNUNET_OK != 228 GNUNET_CONFIGURATION_get_value_time (cfg, 229 "exchangedb", 230 "AGGREGATOR_SHIFT", 231 &pg->aggregator_shift)) 232 { 233 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, 234 "exchangedb", 235 "AGGREGATOR_SHIFT"); 236 } 237 if (GNUNET_OK != 238 GNUNET_CONFIGURATION_get_value_number (cfg, 239 "exchangedb", 240 "DEFAULT_PURSE_LIMIT", 241 &dpl)) 242 { 243 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, 244 "exchangedb", 245 "DEFAULT_PURSE_LIMIT"); 246 pg->def_purse_limit = 1; 247 } 248 else 249 { 250 pg->def_purse_limit = (uint32_t) dpl; 251 } 252 253 if (GNUNET_OK != 254 TALER_config_get_currency (cfg, 255 "exchange", 256 &pg->currency)) 257 { 258 goto fail; 259 } 260 if (GNUNET_OK != 261 internal_setup (pg)) 262 { 263 goto fail; 264 } 265 if (check_current && 266 (GNUNET_OK != 267 GNUNET_PQ_check_current (pg->conn, 268 "exchange-")) ) 269 { 270 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 271 "Database schema is not up-to-date. Try running taler-exchange-dbinit or taler-exchange-dbconfig!\n"); 272 goto fail; 273 } 274 return pg; 275 276 fail: 277 TALER_EXCHANGEDB_disconnect (pg); 278 return NULL; 279 } 280 281 282 struct TALER_EXCHANGEDB_PostgresContext * 283 TALER_EXCHANGEDB_connect ( 284 const struct GNUNET_CONFIGURATION_Handle *cfg) 285 { 286 return do_connect (cfg, 287 true); 288 } 289 290 291 struct TALER_EXCHANGEDB_PostgresContext * 292 TALER_EXCHANGEDB_connect_admin ( 293 const struct GNUNET_CONFIGURATION_Handle *cfg) 294 { 295 return do_connect (cfg, 296 false); 297 } 298 299 300 void 301 TALER_EXCHANGEDB_disconnect (struct TALER_EXCHANGEDB_PostgresContext *pg) 302 { 303 if (NULL == pg) 304 return; 305 if (NULL != pg->conn) 306 { 307 GNUNET_PQ_disconnect (pg->conn); 308 pg->conn = NULL; 309 } 310 GNUNET_free (pg->exchange_url); 311 GNUNET_free (pg->sql_dir); 312 GNUNET_free (pg->currency); 313 GNUNET_free (pg); 314 } 315 316 317 /* end of pg.c */