modify GNUNET_PQ_connect_with_cfg to enable flexible loading of .sql files
[oweals/gnunet.git] / src / pq / pq_connect.c
1 /*
2    This file is part of GNUnet
3    Copyright (C) 2017, 2019 GNUnet e.V.
4
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.
9
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.
14
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/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19  */
20 /**
21  * @file pq/pq_connect.c
22  * @brief functions to connect to libpq (PostGres)
23  * @author Christian Grothoff
24  */
25 #include "platform.h"
26 #include "pq.h"
27
28
29 /**
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.
33  *
34  * @param arg the SQL connection that was used
35  * @param res information about some libpq event
36  */
37 static void
38 pq_notice_receiver_cb (void *arg,
39                        const PGresult *res)
40 {
41   /* do nothing, intentionally */
42   (void) arg;
43   (void) res;
44 }
45
46
47 /**
48  * Function called by libpq whenever it wants to log something.
49  * We log those using the GNUnet logger.
50  *
51  * @param arg the SQL connection that was used
52  * @param message information about some libpq event
53  */
54 static void
55 pq_notice_processor_cb (void *arg,
56                         const char *message)
57 {
58   (void) arg;
59   GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
60                    "pq",
61                    "%s",
62                    message);
63 }
64
65
66 /**
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
73  * fails.
74  *
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
79  * reconnect.
80  *
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
87  */
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)
93 {
94   struct GNUNET_PQ_Context *db;
95   unsigned int elen = 0;
96   unsigned int plen = 0;
97
98   if (NULL != es)
99     while (NULL != es[elen].sql)
100       elen++;
101   if (NULL != ps)
102     while (NULL != ps[plen].name)
103       plen++;
104
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);
109   if (0 != elen)
110   {
111     db->es = GNUNET_new_array (elen + 1,
112                                struct GNUNET_PQ_ExecuteStatement);
113     memcpy (db->es,
114             es,
115             elen * sizeof (struct GNUNET_PQ_ExecuteStatement));
116   }
117   if (0 != plen)
118   {
119     db->ps = GNUNET_new_array (plen + 1,
120                                struct GNUNET_PQ_PreparedStatement);
121     memcpy (db->ps,
122             ps,
123             plen * sizeof (struct GNUNET_PQ_PreparedStatement));
124   }
125   GNUNET_PQ_reconnect (db);
126   if (NULL == db->conn)
127   {
128     GNUNET_free_non_null (db->load_path);
129     GNUNET_free (db->config_str);
130     GNUNET_free (db);
131     return NULL;
132   }
133   return db;
134 }
135
136
137 /**
138  * Reinitialize the database @a db if the connection is down.
139  *
140  * @param db database connection to reinitialize
141  */
142 void
143 GNUNET_PQ_reconnect_if_down (struct GNUNET_PQ_Context *db)
144 {
145   if (CONNECTION_BAD != PQstatus (db->conn))
146     return;
147   GNUNET_PQ_reconnect (db);
148 }
149
150
151 /**
152  * Reinitialize the database @a db.
153  *
154  * @param db database connection to reinitialize
155  */
156 void
157 GNUNET_PQ_reconnect (struct GNUNET_PQ_Context *db)
158 {
159   if (NULL != db->conn)
160     PQfinish (db->conn);
161   db->conn = PQconnectdb (db->config_str);
162   if ((NULL == db->conn) ||
163       (CONNECTION_OK != PQstatus (db->conn)))
164   {
165     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
166                      "pq",
167                      "Database connection to '%s' failed: %s\n",
168                      db->config_str,
169                      (NULL != db->conn) ?
170                      PQerrorMessage (db->conn)
171                      : "PQconnectdb returned NULL");
172     if (NULL != db->conn)
173     {
174       PQfinish (db->conn);
175       db->conn = NULL;
176     }
177     return;
178   }
179   PQsetNoticeReceiver (db->conn,
180                        &pq_notice_receiver_cb,
181                        db);
182   PQsetNoticeProcessor (db->conn,
183                         &pq_notice_processor_cb,
184                         db);
185   if (NULL != db->load_path)
186   {
187     size_t slen = strlen (db->load_path) + 10;
188
189     for (unsigned int i = 0; i<10000; i++)
190     {
191       char buf[slen];
192       struct GNUNET_OS_Process *psql;
193       enum GNUNET_OS_ProcessStatusType type;
194       unsigned long code;
195
196       GNUNET_snprintf (buf,
197                        sizeof (buf),
198                        "%s%04u.sql",
199                        db->load_path,
200                        i);
201       if (GNUNET_YES !=
202           GNUNET_DISK_file_test (buf))
203         break; /* We are done */
204       psql = GNUNET_OS_start_process (GNUNET_NO,
205                                       GNUNET_OS_INHERIT_STD_NONE,
206                                       NULL,
207                                       NULL,
208                                       NULL,
209                                       "psql",
210                                       "psql",
211                                       db->config_str,
212                                       "-f",
213                                       buf,
214                                       "-q",
215                                       NULL);
216       if (NULL == psql)
217       {
218         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
219                                   "exec",
220                                   "psql");
221         PQfinish (db->conn);
222         db->conn = NULL;
223         return;
224       }
225       GNUNET_assert (GNUNET_OK ==
226                      GNUNET_OS_process_wait_status (psql,
227                                                     &type,
228                                                     &code));
229       GNUNET_OS_process_destroy (psql);
230       if ( (GNUNET_OS_PROCESS_EXITED != type) ||
231            (0 != code) )
232       {
233         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
234                     "Could not run PSQL on file %s: %d",
235                     buf,
236                     (int) code);
237         PQfinish (db->conn);
238         db->conn = NULL;
239         return;
240       }
241     }
242   }
243   if ( (NULL != db->es) &&
244        (GNUNET_OK !=
245         GNUNET_PQ_exec_statements (db,
246                                    db->es)) )
247   {
248     PQfinish (db->conn);
249     db->conn = NULL;
250     return;
251   }
252   if ( (NULL != db->ps) &&
253        (GNUNET_OK !=
254         GNUNET_PQ_prepare_statements (db,
255                                       db->ps)) )
256   {
257     PQfinish (db->conn);
258     db->conn = NULL;
259     return;
260   }
261 }
262
263
264 /**
265  * Connect to a postgres database using the configuration
266  * option "CONFIG" in @a section.  Also ensures that the
267  * statements in @a es are executed whenever we (re)connect to the
268  * database, and that the prepared statements in @a ps are "ready".
269  *
270  * The caller does not have to ensure that @a es and @a ps remain allocated
271  * and initialized in memory until #GNUNET_PQ_disconnect() is called, as a copy will be made.
272  *
273  * @param cfg configuration
274  * @param section configuration section to use to get Postgres configuration options
275  * @param load_path_suffix suffix to append to the SQL_DIR in the configuration
276  * @param es #GNUNET_PQ_PREPARED_STATEMENT_END-terminated
277  *            array of statements to execute upon EACH connection, can be NULL
278  * @param ps array of prepared statements to prepare, can be NULL
279  * @return the postgres handle, NULL on error
280  */
281 struct GNUNET_PQ_Context *
282 GNUNET_PQ_connect_with_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
283                             const char *section,
284                             const char *load_path_suffix,
285                             const struct GNUNET_PQ_ExecuteStatement *es,
286                             const struct GNUNET_PQ_PreparedStatement *ps)
287 {
288   struct GNUNET_PQ_Context *db;
289   char *conninfo;
290   char *load_path;
291   char *sp;
292
293   if (GNUNET_OK !=
294       GNUNET_CONFIGURATION_get_value_string (cfg,
295                                              section,
296                                              "CONFIG",
297                                              &conninfo))
298     conninfo = NULL;
299   load_path = NULL;
300   sp = NULL;
301   if (GNUNET_OK ==
302       GNUNET_CONFIGURATION_get_value_filename (cfg,
303                                                section,
304                                                "SQL_DIR",
305                                                &sp))
306     GNUNET_asprintf (&load_path,
307                      "%s%s",
308                      sp,
309                      load_path_suffix);
310   db = GNUNET_PQ_connect (conninfo == NULL ? "" : conninfo,
311                           load_path,
312                           es,
313                           ps);
314   GNUNET_free_non_null (load_path);
315   GNUNET_free_non_null (sp);
316   GNUNET_free_non_null (conninfo);
317   return db;
318 }
319
320
321 /**
322  * Disconnect from the database, destroying the prepared statements
323  * and releasing other associated resources.
324  *
325  * @param db database handle to disconnect (will be free'd)
326  */
327 void
328 GNUNET_PQ_disconnect (struct GNUNET_PQ_Context *db)
329 {
330   GNUNET_free_non_null (db->es);
331   GNUNET_free_non_null (db->ps);
332   GNUNET_free_non_null (db->load_path);
333   GNUNET_free_non_null (db->config_str);
334   PQfinish (db->conn);
335   GNUNET_free (db);
336 }
337
338
339 /* end of pq/pq_connect.c */