2 This file is part of GNUnet
3 Copyright (C) 2017, 2019, 2020 GNUnet e.V.
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
21 * @file pq/pq_connect.c
22 * @brief functions to connect to libpq (PostGres)
23 * @author Christian Grothoff
30 * Function called by libpq whenever it wants to log something.
31 * We already log whenever we care, so this function does nothing
32 * and merely exists to silence the libpq logging.
34 * @param arg the SQL connection that was used
35 * @param res information about some libpq event
38 pq_notice_receiver_cb (void *arg,
41 /* do nothing, intentionally */
48 * Function called by libpq whenever it wants to log something.
49 * We log those using the GNUnet logger.
51 * @param arg the SQL connection that was used
52 * @param message information about some libpq event
55 pq_notice_processor_cb (void *arg,
59 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
67 * Create a connection to the Postgres database using @a config_str for the
68 * configuration. Initialize logging via GNUnet's log routines and disable
69 * Postgres's logger. Also ensures that the statements in @a load_path and @a
70 * es are executed whenever we (re)connect to the database, and that the
71 * prepared statements in @a ps are "ready". If statements in @es fail that
72 * were created with #GNUNET_PQ_make_execute(), then the entire operation
75 * In @a load_path, a list of "$XXXX.sql" files is expected where $XXXX
76 * must be a sequence of contiguous integer values starting at 0000.
77 * These files are then loaded in sequence using "psql $config_str" before
78 * running statements from @e es. The directory is inspected again on
81 * @param config_str configuration to use
82 * @param load_path path to directory with SQL transactions to run, can be NULL
83 * @param es #GNUNET_PQ_PREPARED_STATEMENT_END-terminated
84 * array of statements to execute upon EACH connection, can be NULL
85 * @param ps array of prepared statements to prepare, can be NULL
86 * @return NULL on error
88 struct GNUNET_PQ_Context *
89 GNUNET_PQ_connect (const char *config_str,
90 const char *load_path,
91 const struct GNUNET_PQ_ExecuteStatement *es,
92 const struct GNUNET_PQ_PreparedStatement *ps)
94 struct GNUNET_PQ_Context *db;
95 unsigned int elen = 0;
96 unsigned int plen = 0;
99 while (NULL != es[elen].sql)
102 while (NULL != ps[plen].name)
105 db = GNUNET_new (struct GNUNET_PQ_Context);
106 db->config_str = GNUNET_strdup (config_str);
107 if (NULL != load_path)
108 db->load_path = GNUNET_strdup (load_path);
111 db->es = GNUNET_new_array (elen + 1,
112 struct GNUNET_PQ_ExecuteStatement);
115 elen * sizeof (struct GNUNET_PQ_ExecuteStatement));
119 db->ps = GNUNET_new_array (plen + 1,
120 struct GNUNET_PQ_PreparedStatement);
123 plen * sizeof (struct GNUNET_PQ_PreparedStatement));
125 GNUNET_PQ_reconnect (db);
126 if (NULL == db->conn)
128 GNUNET_free_non_null (db->load_path);
129 GNUNET_free (db->config_str);
138 * Apply patch number @a from path @a load_path.
140 * @param db database context to use
141 * @param load_path where to find the SQL code to run
142 * @param i patch number to append to the @a load_path
143 * @return #GNUNET_OK on success, #GNUNET_NO if patch @a i does not exist, #GNUNET_SYSERR on error
146 apply_patch (struct GNUNET_PQ_Context *db,
147 const char *load_path,
150 struct GNUNET_OS_Process *psql;
151 enum GNUNET_OS_ProcessStatusType type;
153 size_t slen = strlen (load_path) + 10;
156 GNUNET_snprintf (buf,
161 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
162 "Applying SQL file `%s' on database %s\n",
165 psql = GNUNET_OS_start_process (GNUNET_NO,
166 GNUNET_OS_INHERIT_STD_NONE,
179 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
182 return GNUNET_SYSERR;
184 GNUNET_assert (GNUNET_OK ==
185 GNUNET_OS_process_wait_status (psql,
188 GNUNET_OS_process_destroy (psql);
189 if ( (GNUNET_OS_PROCESS_EXITED != type) ||
192 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
193 "Could not run PSQL on file %s: %d",
196 return GNUNET_SYSERR;
203 * Within the @a db context, run all the SQL files
204 * from the @a load_path from 0000-9999.sql (as long
205 * as the files exist contiguously).
207 * @param db database context to use
208 * @param load_path where to find the XXXX.sql files
209 * @return #GNUNET_OK on success
212 GNUNET_PQ_run_sql (struct GNUNET_PQ_Context *db,
213 const char *load_path)
215 const char *load_path_suffix;
216 size_t slen = strlen (load_path) + 10;
218 load_path_suffix = strrchr (load_path, '/');
219 if (NULL == load_path_suffix)
222 return GNUNET_SYSERR;
224 load_path_suffix++; /* skip '/' */
225 for (unsigned int i = 1; i<10000; i++)
227 enum GNUNET_DB_QueryStatus qs;
231 /* First, check patch actually exists */
232 GNUNET_snprintf (buf,
238 GNUNET_DISK_file_test (buf))
239 return GNUNET_OK; /* We are done */
242 /* Second, check with DB versioning schema if this patch was already applied,
245 char patch_name[slen];
247 GNUNET_snprintf (patch_name,
254 struct GNUNET_PQ_QueryParam params[] = {
255 GNUNET_PQ_query_param_string (patch_name),
256 GNUNET_PQ_query_param_end
258 struct GNUNET_PQ_ResultSpec rs[] = {
259 GNUNET_PQ_result_spec_string ("applied_by",
261 GNUNET_PQ_result_spec_end
264 qs = GNUNET_PQ_eval_prepared_singleton_select (db,
265 "gnunet_pq_check_patch",
268 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
270 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
271 "Database version %s already applied by %s, skipping\n",
274 GNUNET_PQ_cleanup_result (rs);
276 if (GNUNET_DB_STATUS_HARD_ERROR == qs)
279 return GNUNET_SYSERR;
283 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
284 continue; /* patch already applied, skip it */
286 /* patch not yet applied, run it! */
290 ret = apply_patch (db,
293 if (GNUNET_NO == ret)
295 if (GNUNET_SYSERR == ret)
296 return GNUNET_SYSERR;
304 * Reinitialize the database @a db if the connection is down.
306 * @param db database connection to reinitialize
309 GNUNET_PQ_reconnect_if_down (struct GNUNET_PQ_Context *db)
311 if (CONNECTION_BAD != PQstatus (db->conn))
313 GNUNET_PQ_reconnect (db);
318 * Reinitialize the database @a db.
320 * @param db database connection to reinitialize
323 GNUNET_PQ_reconnect (struct GNUNET_PQ_Context *db)
325 if (NULL != db->conn)
327 db->conn = PQconnectdb (db->config_str);
328 if ( (NULL == db->conn) ||
329 (CONNECTION_OK != PQstatus (db->conn)) )
331 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
333 "Database connection to '%s' failed: %s\n",
336 PQerrorMessage (db->conn)
337 : "PQconnectdb returned NULL");
338 if (NULL != db->conn)
345 PQsetNoticeReceiver (db->conn,
346 &pq_notice_receiver_cb,
348 PQsetNoticeProcessor (db->conn,
349 &pq_notice_processor_cb,
351 if (NULL != db->load_path)
355 res = PQprepare (db->conn,
356 "gnunet_pq_check_patch",
360 " WHERE patch_name = $1"
364 if (PGRES_COMMAND_OK != PQresultStatus (res))
369 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
370 "Failed to prepare statement to check patch level. Likely versioning schema does not exist yet, loading patch level 0000!\n");
371 ret = apply_patch (db,
374 if (GNUNET_NO == ret)
376 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
377 "Failed to find SQL file to load database versioning logic\n");
382 if (GNUNET_SYSERR == ret)
384 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
385 "Failed to run SQL logic to setup database versioning logic\n");
390 /* try again to prepare our statement! */
391 res = PQprepare (db->conn,
392 "gnunet_pq_check_patch",
396 " WHERE patch_name = $1"
400 if (PGRES_COMMAND_OK != PQresultStatus (res))
402 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
403 "Failed to run SQL logic to setup database versioning logic: %s/%s\n",
404 PQresultErrorMessage (res),
405 PQerrorMessage (db->conn));
415 GNUNET_PQ_run_sql (db,
418 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
419 "Failed to load SQL statements from `%s'\n",
426 if ( (NULL != db->es) &&
428 GNUNET_PQ_exec_statements (db,
435 if ( (NULL != db->ps) &&
437 GNUNET_PQ_prepare_statements (db,
448 * Connect to a postgres database using the configuration
449 * option "CONFIG" in @a section. Also ensures that the
450 * statements in @a es are executed whenever we (re)connect to the
451 * database, and that the prepared statements in @a ps are "ready".
453 * The caller does not have to ensure that @a es and @a ps remain allocated
454 * and initialized in memory until #GNUNET_PQ_disconnect() is called, as a copy will be made.
456 * @param cfg configuration
457 * @param section configuration section to use to get Postgres configuration options
458 * @param load_path_suffix suffix to append to the SQL_DIR in the configuration
459 * @param es #GNUNET_PQ_PREPARED_STATEMENT_END-terminated
460 * array of statements to execute upon EACH connection, can be NULL
461 * @param ps array of prepared statements to prepare, can be NULL
462 * @return the postgres handle, NULL on error
464 struct GNUNET_PQ_Context *
465 GNUNET_PQ_connect_with_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
467 const char *load_path_suffix,
468 const struct GNUNET_PQ_ExecuteStatement *es,
469 const struct GNUNET_PQ_PreparedStatement *ps)
471 struct GNUNET_PQ_Context *db;
477 GNUNET_CONFIGURATION_get_value_string (cfg,
484 if ( (NULL != load_path_suffix) &&
486 GNUNET_CONFIGURATION_get_value_filename (cfg,
490 GNUNET_asprintf (&load_path,
494 db = GNUNET_PQ_connect (conninfo == NULL ? "" : conninfo,
498 GNUNET_free_non_null (load_path);
499 GNUNET_free_non_null (sp);
500 GNUNET_free_non_null (conninfo);
506 * Disconnect from the database, destroying the prepared statements
507 * and releasing other associated resources.
509 * @param db database handle to disconnect (will be free'd)
512 GNUNET_PQ_disconnect (struct GNUNET_PQ_Context *db)
514 GNUNET_free_non_null (db->es);
515 GNUNET_free_non_null (db->ps);
516 GNUNET_free_non_null (db->load_path);
517 GNUNET_free_non_null (db->config_str);
523 /* end of pq/pq_connect.c */