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 : * 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 286 : 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 286 : struct GNUNET_PQ_ExecuteStatement es[] = {
82 286 : GNUNET_PQ_make_try_execute (
83 : "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
84 286 : GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
85 286 : GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
86 : /* Mergejoin causes issues, see Postgres #18380 */
87 286 : GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"),
88 286 : GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
89 : GNUNET_PQ_EXECUTE_STATEMENT_END
90 : };
91 : #endif
92 :
93 286 : if (GNUNET_OK !=
94 286 : GNUNET_PQ_exec_statements (pq,
95 : es))
96 : {
97 0 : GNUNET_break (0);
98 0 : return;
99 : }
100 : {
101 : char *set_time;
102 : enum GNUNET_GenericReturnValue ret;
103 :
104 286 : GNUNET_asprintf (&set_time,
105 : "SET taler.timetravel_us = '%lld'",
106 : GNUNET_TIME_get_offset ());
107 : {
108 286 : struct GNUNET_PQ_ExecuteStatement time_es[] = {
109 286 : GNUNET_PQ_make_execute (set_time),
110 : GNUNET_PQ_EXECUTE_STATEMENT_END
111 : };
112 :
113 286 : ret = GNUNET_PQ_exec_statements (pq,
114 : time_es);
115 : }
116 286 : GNUNET_free (set_time);
117 286 : if (GNUNET_OK != ret)
118 : {
119 0 : GNUNET_break (0);
120 0 : return;
121 : }
122 : }
123 286 : 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 285 : internal_setup (struct TALER_EXCHANGEDB_PostgresContext *pg)
135 : {
136 : struct GNUNET_PQ_Context *db_conn;
137 :
138 285 : if (NULL != pg->conn)
139 0 : return GNUNET_OK;
140 285 : db_conn = GNUNET_PQ_init (pg->cfg,
141 : "exchangedb-postgres",
142 : &reconnect_cb,
143 : pg);
144 285 : if (NULL == db_conn)
145 0 : return GNUNET_SYSERR;
146 285 : if (0 == TEH_PG_prep_gen_)
147 : {
148 0 : GNUNET_PQ_disconnect (db_conn);
149 0 : return GNUNET_SYSERR;
150 : }
151 285 : pg->conn = db_conn;
152 285 : 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 285 : 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 285 : pg = GNUNET_new (struct TALER_EXCHANGEDB_PostgresContext);
171 285 : pg->cfg = cfg;
172 285 : if (GNUNET_OK !=
173 285 : GNUNET_CONFIGURATION_get_value_filename (cfg,
174 : "exchangedb-postgres",
175 : "SQL_DIR",
176 : &pg->sql_dir))
177 : {
178 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
179 : "exchangedb-postgres",
180 : "SQL_DIR");
181 0 : goto fail;
182 : }
183 285 : if (GNUNET_OK !=
184 285 : GNUNET_CONFIGURATION_get_value_string (cfg,
185 : "exchange",
186 : "BASE_URL",
187 : &pg->exchange_url))
188 : {
189 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
190 : "exchange",
191 : "BASE_URL");
192 0 : goto fail;
193 : }
194 285 : if (GNUNET_OK !=
195 285 : GNUNET_CONFIGURATION_get_value_time (cfg,
196 : "exchangedb",
197 : "IDLE_RESERVE_EXPIRATION_TIME",
198 : &pg->idle_reserve_expiration_time))
199 : {
200 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
201 : "exchangedb",
202 : "IDLE_RESERVE_EXPIRATION_TIME");
203 0 : goto fail;
204 : }
205 285 : if (GNUNET_OK !=
206 285 : GNUNET_CONFIGURATION_get_value_time (cfg,
207 : "exchangedb",
208 : "MAX_AML_PROGRAM_RUNTIME",
209 : &pg->max_aml_program_runtime))
210 : {
211 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
212 : "exchangedb",
213 : "MAX_AML_PROGRAM_RUNTIME");
214 0 : goto fail;
215 : }
216 285 : if (GNUNET_OK !=
217 285 : GNUNET_CONFIGURATION_get_value_time (cfg,
218 : "exchangedb",
219 : "LEGAL_RESERVE_EXPIRATION_TIME",
220 : &pg->legal_reserve_expiration_time))
221 : {
222 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
223 : "exchangedb",
224 : "LEGAL_RESERVE_EXPIRATION_TIME");
225 0 : goto fail;
226 : }
227 285 : if (GNUNET_OK !=
228 285 : GNUNET_CONFIGURATION_get_value_time (cfg,
229 : "exchangedb",
230 : "AGGREGATOR_SHIFT",
231 : &pg->aggregator_shift))
232 : {
233 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
234 : "exchangedb",
235 : "AGGREGATOR_SHIFT");
236 : }
237 285 : if (GNUNET_OK !=
238 285 : GNUNET_CONFIGURATION_get_value_number (cfg,
239 : "exchangedb",
240 : "DEFAULT_PURSE_LIMIT",
241 : &dpl))
242 : {
243 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
244 : "exchangedb",
245 : "DEFAULT_PURSE_LIMIT");
246 0 : pg->def_purse_limit = 1;
247 : }
248 : else
249 : {
250 285 : pg->def_purse_limit = (uint32_t) dpl;
251 : }
252 :
253 285 : if (GNUNET_OK !=
254 285 : TALER_config_get_currency (cfg,
255 : "exchange",
256 : &pg->currency))
257 : {
258 0 : goto fail;
259 : }
260 285 : if (GNUNET_OK !=
261 285 : internal_setup (pg))
262 : {
263 0 : goto fail;
264 : }
265 493 : if (check_current &&
266 : (GNUNET_OK !=
267 208 : GNUNET_PQ_check_current (pg->conn,
268 : "exchange-")) )
269 : {
270 0 : 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 0 : goto fail;
273 : }
274 285 : return pg;
275 :
276 0 : fail:
277 0 : TALER_EXCHANGEDB_disconnect (pg);
278 0 : return NULL;
279 : }
280 :
281 :
282 : struct TALER_EXCHANGEDB_PostgresContext *
283 208 : TALER_EXCHANGEDB_connect (
284 : const struct GNUNET_CONFIGURATION_Handle *cfg)
285 : {
286 208 : return do_connect (cfg,
287 : true);
288 : }
289 :
290 :
291 : struct TALER_EXCHANGEDB_PostgresContext *
292 77 : TALER_EXCHANGEDB_connect_admin (
293 : const struct GNUNET_CONFIGURATION_Handle *cfg)
294 : {
295 77 : return do_connect (cfg,
296 : false);
297 : }
298 :
299 :
300 : void
301 285 : TALER_EXCHANGEDB_disconnect (struct TALER_EXCHANGEDB_PostgresContext *pg)
302 : {
303 285 : if (NULL == pg)
304 0 : return;
305 285 : if (NULL != pg->conn)
306 : {
307 285 : GNUNET_PQ_disconnect (pg->conn);
308 285 : pg->conn = NULL;
309 : }
310 285 : GNUNET_free (pg->exchange_url);
311 285 : GNUNET_free (pg->sql_dir);
312 285 : GNUNET_free (pg->currency);
313 285 : GNUNET_free (pg);
314 : }
315 :
316 :
317 : /* end of pg.c */
|