Line data Source code
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 229 : 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 229 : struct GNUNET_PQ_ExecuteStatement es[] = {
77 229 : GNUNET_PQ_make_try_execute (
78 : "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
79 229 : GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
80 229 : GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
81 : /* Mergejoin causes issues, see Postgres #18380 */
82 229 : GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"),
83 229 : GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
84 : GNUNET_PQ_EXECUTE_STATEMENT_END
85 : };
86 : #endif
87 :
88 229 : if (GNUNET_OK !=
89 229 : GNUNET_PQ_exec_statements (pq,
90 : es))
91 : {
92 0 : GNUNET_break (0);
93 0 : return;
94 : }
95 229 : 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 229 : internal_setup (struct TALER_EXCHANGEDB_PostgresContext *pg)
107 : {
108 : struct GNUNET_PQ_Context *db_conn;
109 :
110 229 : if (NULL != pg->conn)
111 0 : return GNUNET_OK;
112 229 : db_conn = GNUNET_PQ_init (pg->cfg,
113 : "exchangedb-postgres",
114 : &reconnect_cb,
115 : pg);
116 229 : if (NULL == db_conn)
117 0 : return GNUNET_SYSERR;
118 229 : if (0 == pg->prep_gen)
119 : {
120 0 : GNUNET_PQ_disconnect (db_conn);
121 0 : return GNUNET_SYSERR;
122 : }
123 229 : if (0 == pg->prep_gen)
124 : {
125 0 : GNUNET_PQ_disconnect (db_conn);
126 0 : return GNUNET_SYSERR;
127 : }
128 229 : pg->conn = db_conn;
129 229 : 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 229 : 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 229 : pg = GNUNET_new (struct TALER_EXCHANGEDB_PostgresContext);
148 229 : pg->cfg = cfg;
149 229 : if (GNUNET_OK !=
150 229 : GNUNET_CONFIGURATION_get_value_filename (cfg,
151 : "exchangedb-postgres",
152 : "SQL_DIR",
153 : &pg->sql_dir))
154 : {
155 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
156 : "exchangedb-postgres",
157 : "SQL_DIR");
158 0 : goto fail;
159 : }
160 229 : if (GNUNET_OK !=
161 229 : GNUNET_CONFIGURATION_get_value_string (cfg,
162 : "exchange",
163 : "BASE_URL",
164 : &pg->exchange_url))
165 : {
166 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
167 : "exchange",
168 : "BASE_URL");
169 0 : goto fail;
170 : }
171 229 : if (GNUNET_OK !=
172 229 : GNUNET_CONFIGURATION_get_value_time (cfg,
173 : "exchangedb",
174 : "IDLE_RESERVE_EXPIRATION_TIME",
175 : &pg->idle_reserve_expiration_time))
176 : {
177 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
178 : "exchangedb",
179 : "IDLE_RESERVE_EXPIRATION_TIME");
180 0 : goto fail;
181 : }
182 229 : if (GNUNET_OK !=
183 229 : GNUNET_CONFIGURATION_get_value_time (cfg,
184 : "exchangedb",
185 : "MAX_AML_PROGRAM_RUNTIME",
186 : &pg->max_aml_program_runtime))
187 : {
188 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
189 : "exchangedb",
190 : "MAX_AML_PROGRAM_RUNTIME");
191 0 : goto fail;
192 : }
193 229 : if (GNUNET_OK !=
194 229 : GNUNET_CONFIGURATION_get_value_time (cfg,
195 : "exchangedb",
196 : "LEGAL_RESERVE_EXPIRATION_TIME",
197 : &pg->legal_reserve_expiration_time))
198 : {
199 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
200 : "exchangedb",
201 : "LEGAL_RESERVE_EXPIRATION_TIME");
202 0 : goto fail;
203 : }
204 229 : if (GNUNET_OK !=
205 229 : GNUNET_CONFIGURATION_get_value_time (cfg,
206 : "exchangedb",
207 : "AGGREGATOR_SHIFT",
208 : &pg->aggregator_shift))
209 : {
210 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
211 : "exchangedb",
212 : "AGGREGATOR_SHIFT");
213 : }
214 229 : if (GNUNET_OK !=
215 229 : GNUNET_CONFIGURATION_get_value_number (cfg,
216 : "exchangedb",
217 : "DEFAULT_PURSE_LIMIT",
218 : &dpl))
219 : {
220 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
221 : "exchangedb",
222 : "DEFAULT_PURSE_LIMIT");
223 0 : pg->def_purse_limit = 1;
224 : }
225 : else
226 : {
227 229 : pg->def_purse_limit = (uint32_t) dpl;
228 : }
229 :
230 229 : if (GNUNET_OK !=
231 229 : TALER_config_get_currency (cfg,
232 : "exchange",
233 : &pg->currency))
234 : {
235 0 : goto fail;
236 : }
237 229 : if (GNUNET_OK !=
238 229 : internal_setup (pg))
239 : {
240 0 : goto fail;
241 : }
242 437 : if (check_current &&
243 : (GNUNET_OK !=
244 208 : GNUNET_PQ_check_current (pg->conn,
245 : "exchange-")) )
246 : {
247 0 : 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 0 : goto fail;
250 : }
251 229 : return pg;
252 :
253 0 : fail:
254 0 : TALER_EXCHANGEDB_disconnect (pg);
255 0 : GNUNET_free (pg);
256 0 : return NULL;
257 : }
258 :
259 :
260 : struct TALER_EXCHANGEDB_PostgresContext *
261 208 : TALER_EXCHANGEDB_connect (
262 : const struct GNUNET_CONFIGURATION_Handle *cfg)
263 : {
264 208 : return do_connect (cfg,
265 : true);
266 : }
267 :
268 :
269 : struct TALER_EXCHANGEDB_PostgresContext *
270 21 : TALER_EXCHANGEDB_connect_admin (
271 : const struct GNUNET_CONFIGURATION_Handle *cfg)
272 : {
273 21 : return do_connect (cfg,
274 : false);
275 : }
276 :
277 :
278 : void
279 229 : TALER_EXCHANGEDB_disconnect (struct TALER_EXCHANGEDB_PostgresContext *pg)
280 : {
281 229 : if (NULL == pg)
282 0 : return;
283 229 : if (NULL != pg->conn)
284 : {
285 229 : GNUNET_PQ_disconnect (pg->conn);
286 229 : pg->conn = NULL;
287 : }
288 229 : GNUNET_free (pg->exchange_url);
289 229 : GNUNET_free (pg->sql_dir);
290 229 : GNUNET_free (pg->currency);
291 229 : GNUNET_free (pg);
292 : }
293 :
294 :
295 : /* end of plugin_exchangedb_postgres.c */
|