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