anastasis-db_pg.c (7016B)
1 /* 2 This file is part of Anastasis 3 Copyright (C) 2020, 2021, 2022 Anastasis SARL 4 5 Anastasis is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 Anastasis 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 Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file stasis/anastasis-db_pg.c 18 * @brief shared database state and helpers for the Anastasis postgres backend 19 * @author Christian Grothoff 20 * @author Marcello Stanisci 21 */ 22 #include "platform.h" 23 #include "anastasis-db_pg.h" 24 #include "anastasis/anastasis-database/preflight.h" 25 #include "anastasis/anastasis-database/transaction.h" 26 #include "anastasis/anastasis-database/event.h" 27 #include <taler/taler_pq_lib.h> 28 29 30 /** 31 * Global database state. 32 */ 33 struct PostgresClosure *pg; 34 35 36 enum GNUNET_GenericReturnValue 37 ANASTASIS_DB_start (const char *name) 38 { 39 struct GNUNET_PQ_ExecuteStatement es[] = { 40 GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"), 41 GNUNET_PQ_EXECUTE_STATEMENT_END 42 }; 43 44 GNUNET_break (GNUNET_OK == 45 ANASTASIS_DB_preflight ()); 46 pg->transaction_name = name; 47 if (GNUNET_OK != 48 GNUNET_PQ_exec_statements (pg->conn, 49 es)) 50 { 51 TALER_LOG_ERROR ("Failed to start transaction\n"); 52 GNUNET_break (0); 53 return GNUNET_SYSERR; 54 } 55 return GNUNET_OK; 56 } 57 58 59 void 60 ANASTASIS_DB_rollback (void) 61 { 62 struct GNUNET_PQ_ExecuteStatement es[] = { 63 GNUNET_PQ_make_execute ("ROLLBACK"), 64 GNUNET_PQ_EXECUTE_STATEMENT_END 65 }; 66 67 if (GNUNET_OK != 68 GNUNET_PQ_exec_statements (pg->conn, 69 es)) 70 { 71 TALER_LOG_ERROR ("Failed to rollback transaction\n"); 72 GNUNET_break (0); 73 } 74 pg->transaction_name = NULL; 75 } 76 77 78 enum GNUNET_DB_QueryStatus 79 ANASTASIS_DB_commit (void) 80 { 81 enum GNUNET_DB_QueryStatus qs; 82 struct GNUNET_PQ_QueryParam no_params[] = { 83 GNUNET_PQ_query_param_end 84 }; 85 86 PREPARE ("do_commit", 87 "COMMIT"); 88 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 89 "do_commit", 90 no_params); 91 pg->transaction_name = NULL; 92 return qs; 93 } 94 95 96 struct GNUNET_DB_EventHandler * 97 ANASTASIS_DB_event_listen (const struct GNUNET_DB_EventHeaderP *es, 98 struct GNUNET_TIME_Relative timeout, 99 GNUNET_DB_EventCallback cb, 100 void *cb_cls) 101 { 102 return GNUNET_PQ_event_listen (pg->conn, 103 es, 104 timeout, 105 cb, 106 cb_cls); 107 } 108 109 110 void 111 ANASTASIS_DB_event_listen_cancel (struct GNUNET_DB_EventHandler *eh) 112 { 113 GNUNET_PQ_event_listen_cancel (eh); 114 } 115 116 117 void 118 ANASTASIS_DB_event_notify (const struct GNUNET_DB_EventHeaderP *es, 119 const void *extra, 120 size_t extra_size) 121 { 122 GNUNET_PQ_event_notify (pg->conn, 123 es, 124 extra, 125 extra_size); 126 } 127 128 129 /** 130 * Function called each time we connect or reconnect to the 131 * database. Gives the application a chance to run some 132 * per-connection initialization logic. 133 * 134 * @param cls unused, NULL 135 * @param pq database connection handle 136 */ 137 static void 138 reconnect_cb (void *cls, 139 struct GNUNET_PQ_Context *pq) 140 { 141 #if AUTO_EXPLAIN 142 /* Enable verbose logging to see where queries do not 143 properly use indices */ 144 struct GNUNET_PQ_ExecuteStatement es[] = { 145 GNUNET_PQ_make_try_execute ("LOAD 'auto_explain';"), 146 GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"), 147 GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"), 148 GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"), 149 /* https://wiki.postgresql.org/wiki/Serializable suggests to really 150 force the default to 'serializable' if SSI is to be used. */ 151 GNUNET_PQ_make_try_execute ( 152 "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"), 153 GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"), 154 GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"), 155 GNUNET_PQ_make_execute ("SET search_path TO anastasis;"), 156 GNUNET_PQ_EXECUTE_STATEMENT_END 157 }; 158 #else 159 struct GNUNET_PQ_ExecuteStatement es[] = { 160 GNUNET_PQ_make_execute ("SET search_path TO anastasis;"), 161 GNUNET_PQ_EXECUTE_STATEMENT_END 162 }; 163 #endif 164 165 (void) cls; 166 if (GNUNET_OK != 167 GNUNET_PQ_exec_statements (pq, 168 es)) 169 { 170 GNUNET_break (0); 171 return; 172 } 173 pg->prep_gen++; 174 } 175 176 177 /** 178 * Initialize the database connection. 179 * 180 * @param cfg configuration to use 181 * @param check_current true to check if the database schema is current 182 * @return NULL on failure 183 */ 184 static enum GNUNET_GenericReturnValue 185 do_init (const struct GNUNET_CONFIGURATION_Handle *cfg, 186 bool check_current) 187 { 188 pg = GNUNET_new (struct PostgresClosure); 189 pg->cfg = cfg; 190 if (GNUNET_OK != 191 GNUNET_CONFIGURATION_get_value_string (cfg, 192 "anastasis", 193 "CURRENCY", 194 &pg->currency)) 195 { 196 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 197 "anastasis", 198 "CURRENCY"); 199 GNUNET_free (pg); 200 return GNUNET_SYSERR; 201 } 202 pg->conn = GNUNET_PQ_init (pg->cfg, 203 "stasis-postgres", 204 &reconnect_cb, 205 pg); 206 if (NULL == pg->conn) 207 { 208 GNUNET_break (0); 209 ANASTASIS_DB_fini (); 210 return GNUNET_SYSERR; 211 } 212 if (check_current && 213 (GNUNET_OK != 214 GNUNET_PQ_check_current (pg->conn, 215 "stasis-")) ) 216 { 217 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 218 "Database schema is not up-to-date. Try running anastasis-dbinit or anastasis-dbconfig!\n"); 219 ANASTASIS_DB_fini (); 220 return GNUNET_NO; 221 } 222 return GNUNET_OK; 223 } 224 225 226 enum GNUNET_GenericReturnValue 227 ANASTASIS_DB_init (const struct GNUNET_CONFIGURATION_Handle *cfg) 228 { 229 return do_init (cfg, 230 true); 231 } 232 233 234 enum GNUNET_GenericReturnValue 235 ANASTASIS_DB_init_admin (const struct GNUNET_CONFIGURATION_Handle *cfg) 236 { 237 return do_init (cfg, 238 false); 239 } 240 241 242 void 243 ANASTASIS_DB_fini (void) 244 { 245 if (NULL == pg) 246 return; 247 if (NULL != pg->conn) 248 GNUNET_PQ_disconnect (pg->conn); 249 GNUNET_free (pg->currency); 250 GNUNET_free (pg); 251 } 252 253 254 /* end of anastasis-db_pg.c */