use NULL value in load_path_suffix to NOT load any files
[oweals/gnunet.git] / src / pq / pq_eval.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_eval.c
22  * @brief functions to execute SQL statements with arguments and/or results (PostGres)
23  * @author Christian Grothoff
24  */
25 #include "platform.h"
26 #include "pq.h"
27
28
29 /**
30  * Error code returned by Postgres for deadlock.
31  */
32 #define PQ_DIAG_SQLSTATE_DEADLOCK "40P01"
33
34 /**
35  * Error code returned by Postgres for uniqueness violation.
36  */
37 #define PQ_DIAG_SQLSTATE_UNIQUE_VIOLATION "23505"
38
39 /**
40  * Error code returned by Postgres on serialization failure.
41  */
42 #define PQ_DIAG_SQLSTATE_SERIALIZATION_FAILURE "40001"
43
44
45 /**
46  * Check the @a result's error code to see what happened.
47  * Also logs errors.
48  *
49  * @param db database to execute the statement with
50  * @param statement_name name of the statement that created @a result
51  * @param result result to check
52  * @return status code from the result, mapping PQ status
53  *         codes to `enum GNUNET_DB_QueryStatus`.  Never
54  *         returns positive values as this function does
55  *         not look at the result set.
56  * @deprecated (low level, let's see if we can do with just the high-level functions)
57  */
58 enum GNUNET_DB_QueryStatus
59 GNUNET_PQ_eval_result (struct GNUNET_PQ_Context *db,
60                        const char *statement_name,
61                        PGresult *result)
62 {
63   ExecStatusType est;
64
65   if (NULL == result)
66     return GNUNET_DB_STATUS_SOFT_ERROR;
67   est = PQresultStatus (result);
68   if ((PGRES_COMMAND_OK != est) &&
69       (PGRES_TUPLES_OK != est))
70   {
71     const char *sqlstate;
72     ConnStatusType status;
73
74     if (CONNECTION_OK != (status = PQstatus (db->conn)))
75     {
76       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
77                        "pq",
78                        "Database connection failed during query `%s': %d (reconnecting)\n",
79                        statement_name,
80                        status);
81       GNUNET_PQ_reconnect (db);
82       return GNUNET_DB_STATUS_SOFT_ERROR;
83     }
84
85     sqlstate = PQresultErrorField (result,
86                                    PG_DIAG_SQLSTATE);
87     if (NULL == sqlstate)
88     {
89       /* very unexpected... */
90       GNUNET_break (0);
91       return GNUNET_DB_STATUS_HARD_ERROR;
92     }
93     if ((0 == strcmp (sqlstate,
94                       PQ_DIAG_SQLSTATE_DEADLOCK)) ||
95         (0 == strcmp (sqlstate,
96                       PQ_DIAG_SQLSTATE_SERIALIZATION_FAILURE)))
97     {
98       /* These two can be retried and have a fair chance of working
99          the next time */
100       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
101                        "pq",
102                        "Query `%s' failed with result: %s/%s/%s/%s/%s\n",
103                        statement_name,
104                        PQresultErrorField (result,
105                                            PG_DIAG_MESSAGE_PRIMARY),
106                        PQresultErrorField (result,
107                                            PG_DIAG_MESSAGE_DETAIL),
108                        PQresultErrorMessage (result),
109                        PQresStatus (PQresultStatus (result)),
110                        PQerrorMessage (db->conn));
111       return GNUNET_DB_STATUS_SOFT_ERROR;
112     }
113     if (0 == strcmp (sqlstate,
114                      PQ_DIAG_SQLSTATE_UNIQUE_VIOLATION))
115     {
116       /* Likely no need to retry, INSERT of "same" data. */
117       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
118                        "pq",
119                        "Query `%s' failed with unique violation: %s/%s/%s/%s/%s\n",
120                        statement_name,
121                        PQresultErrorField (result,
122                                            PG_DIAG_MESSAGE_PRIMARY),
123                        PQresultErrorField (result,
124                                            PG_DIAG_MESSAGE_DETAIL),
125                        PQresultErrorMessage (result),
126                        PQresStatus (PQresultStatus (result)),
127                        PQerrorMessage (db->conn));
128       return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
129     }
130     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
131                      "pq",
132                      "Query `%s' failed with result: %s/%s/%s/%s/%s\n",
133                      statement_name,
134                      PQresultErrorField (result,
135                                          PG_DIAG_MESSAGE_PRIMARY),
136                      PQresultErrorField (result,
137                                          PG_DIAG_MESSAGE_DETAIL),
138                      PQresultErrorMessage (result),
139                      PQresStatus (PQresultStatus (result)),
140                      PQerrorMessage (db->conn));
141     return GNUNET_DB_STATUS_HARD_ERROR;
142   }
143   return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
144 }
145
146
147 /**
148  * Execute a named prepared @a statement that is NOT a SELECT
149  * statement in @a connnection using the given @a params.  Returns the
150  * resulting session state.
151  *
152  * @param db database to execute the statement with
153  * @param statement_name name of the statement
154  * @param params parameters to give to the statement (#GNUNET_PQ_query_param_end-terminated)
155  * @return status code from the result, mapping PQ status
156  *         codes to `enum GNUNET_DB_QueryStatus`.  If the
157  *         statement was a DELETE or UPDATE statement, the
158  *         number of affected rows is returned.; if the
159  *         statment was an INSERT statement, and no row
160  *         was added due to a UNIQUE violation, we return
161  *         zero; if INSERT was successful, we return one.
162  */
163 enum GNUNET_DB_QueryStatus
164 GNUNET_PQ_eval_prepared_non_select (struct GNUNET_PQ_Context *db,
165                                     const char *statement_name,
166                                     const struct GNUNET_PQ_QueryParam *params)
167 {
168   PGresult *result;
169   enum GNUNET_DB_QueryStatus qs;
170
171   result = GNUNET_PQ_exec_prepared (db,
172                                     statement_name,
173                                     params);
174   if (NULL == result)
175     return GNUNET_DB_STATUS_SOFT_ERROR;
176   qs = GNUNET_PQ_eval_result (db,
177                               statement_name,
178                               result);
179   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
180   {
181     const char *tuples;
182
183     /* What an awful API, this function really does return a string */
184     tuples = PQcmdTuples (result);
185     if (NULL != tuples)
186       qs = strtol (tuples, NULL, 10);
187   }
188   PQclear (result);
189   return qs;
190 }
191
192
193 /**
194  * Execute a named prepared @a statement that is a SELECT statement
195  * which may return multiple results in @a connection using the given
196  * @a params.  Call @a rh with the results.  Returns the query
197  * status including the number of results given to @a rh (possibly zero).
198  * @a rh will not have been called if the return value is negative.
199  *
200  * @param db database to execute the statement with
201  * @param statement_name name of the statement
202  * @param params parameters to give to the statement (#GNUNET_PQ_query_param_end-terminated)
203  * @param rh function to call with the result set, NULL to ignore
204  * @param rh_cls closure to pass to @a rh
205  * @return status code from the result, mapping PQ status
206  *         codes to `enum GNUNET_DB_QueryStatus`.
207  */
208 enum GNUNET_DB_QueryStatus
209 GNUNET_PQ_eval_prepared_multi_select (struct GNUNET_PQ_Context *db,
210                                       const char *statement_name,
211                                       const struct GNUNET_PQ_QueryParam *params,
212                                       GNUNET_PQ_PostgresResultHandler rh,
213                                       void *rh_cls)
214 {
215   PGresult *result;
216   enum GNUNET_DB_QueryStatus qs;
217   unsigned int ret;
218
219   result = GNUNET_PQ_exec_prepared (db,
220                                     statement_name,
221                                     params);
222   if (NULL == result)
223     return GNUNET_DB_STATUS_SOFT_ERROR;
224   qs = GNUNET_PQ_eval_result (db,
225                               statement_name,
226                               result);
227   if (qs < 0)
228   {
229     PQclear (result);
230     return qs;
231   }
232   ret = PQntuples (result);
233   if (NULL != rh)
234     rh (rh_cls,
235         result,
236         ret);
237   PQclear (result);
238   return ret;
239 }
240
241
242 /**
243  * Execute a named prepared @a statement that is a SELECT statement
244  * which must return a single result in @a connection using the given
245  * @a params.  Stores the result (if any) in @a rs, which the caller
246  * must then clean up using #GNUNET_PQ_cleanup_result() if the return
247  * value was #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT.  Returns the
248  * resulting session status.
249  *
250  * @param db database to execute the statement with
251  * @param statement_name name of the statement
252  * @param params parameters to give to the statement (#GNUNET_PQ_query_param_end-terminated)
253  * @param[in,out] rs result specification to use for storing the result of the query
254  * @return status code from the result, mapping PQ status
255  *         codes to `enum GNUNET_DB_QueryStatus`.
256  */
257 enum GNUNET_DB_QueryStatus
258 GNUNET_PQ_eval_prepared_singleton_select (struct GNUNET_PQ_Context *db,
259                                           const char *statement_name,
260                                           const struct
261                                           GNUNET_PQ_QueryParam *params,
262                                           struct GNUNET_PQ_ResultSpec *rs)
263 {
264   PGresult *result;
265   enum GNUNET_DB_QueryStatus qs;
266
267   result = GNUNET_PQ_exec_prepared (db,
268                                     statement_name,
269                                     params);
270   if (NULL == result)
271     return GNUNET_DB_STATUS_SOFT_ERROR;
272   qs = GNUNET_PQ_eval_result (db,
273                               statement_name,
274                               result);
275   if (qs < 0)
276   {
277     PQclear (result);
278     return qs;
279   }
280   if (0 == PQntuples (result))
281   {
282     PQclear (result);
283     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
284   }
285   if (1 != PQntuples (result))
286   {
287     /* more than one result, but there must be at most one */
288     GNUNET_break (0);
289     PQclear (result);
290     return GNUNET_DB_STATUS_HARD_ERROR;
291   }
292   if (GNUNET_OK !=
293       GNUNET_PQ_extract_result (result,
294                                 rs,
295                                 0))
296   {
297     PQclear (result);
298     return GNUNET_DB_STATUS_HARD_ERROR;
299   }
300   PQclear (result);
301   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
302 }
303
304
305 /* end of pq/pq_eval.c */