diff options
Diffstat (limited to 'src/lib/pq/pq_eval.c')
-rw-r--r-- | src/lib/pq/pq_eval.c | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/src/lib/pq/pq_eval.c b/src/lib/pq/pq_eval.c new file mode 100644 index 000000000..e31475e13 --- /dev/null +++ b/src/lib/pq/pq_eval.c | |||
@@ -0,0 +1,248 @@ | |||
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 | enum GNUNET_DB_QueryStatus | ||
46 | GNUNET_PQ_eval_result (struct GNUNET_PQ_Context *db, | ||
47 | const char *statement_name, | ||
48 | PGresult *result) | ||
49 | { | ||
50 | ExecStatusType est; | ||
51 | |||
52 | if (NULL == result) | ||
53 | return GNUNET_DB_STATUS_SOFT_ERROR; | ||
54 | est = PQresultStatus (result); | ||
55 | if ((PGRES_COMMAND_OK != est) && | ||
56 | (PGRES_TUPLES_OK != est)) | ||
57 | { | ||
58 | const char *sqlstate; | ||
59 | ConnStatusType status; | ||
60 | |||
61 | if (CONNECTION_OK != (status = PQstatus (db->conn))) | ||
62 | { | ||
63 | GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, | ||
64 | "pq", | ||
65 | "Database connection failed during query `%s': %d (reconnecting)\n", | ||
66 | statement_name, | ||
67 | status); | ||
68 | GNUNET_PQ_reconnect (db); | ||
69 | return GNUNET_DB_STATUS_SOFT_ERROR; | ||
70 | } | ||
71 | |||
72 | sqlstate = PQresultErrorField (result, | ||
73 | PG_DIAG_SQLSTATE); | ||
74 | if (NULL == sqlstate) | ||
75 | { | ||
76 | /* very unexpected... */ | ||
77 | GNUNET_break (0); | ||
78 | return GNUNET_DB_STATUS_HARD_ERROR; | ||
79 | } | ||
80 | if ((0 == strcmp (sqlstate, | ||
81 | PQ_DIAG_SQLSTATE_DEADLOCK)) || | ||
82 | (0 == strcmp (sqlstate, | ||
83 | PQ_DIAG_SQLSTATE_SERIALIZATION_FAILURE))) | ||
84 | { | ||
85 | /* These two can be retried and have a fair chance of working | ||
86 | the next time */ | ||
87 | GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, | ||
88 | "pq", | ||
89 | "Query `%s' failed with result: %s/%s/%s/%s/%s\n", | ||
90 | statement_name, | ||
91 | PQresultErrorField (result, | ||
92 | PG_DIAG_MESSAGE_PRIMARY), | ||
93 | PQresultErrorField (result, | ||
94 | PG_DIAG_MESSAGE_DETAIL), | ||
95 | PQresultErrorMessage (result), | ||
96 | PQresStatus (PQresultStatus (result)), | ||
97 | PQerrorMessage (db->conn)); | ||
98 | return GNUNET_DB_STATUS_SOFT_ERROR; | ||
99 | } | ||
100 | if (0 == strcmp (sqlstate, | ||
101 | PQ_DIAG_SQLSTATE_UNIQUE_VIOLATION)) | ||
102 | { | ||
103 | /* Likely no need to retry, INSERT of "same" data. */ | ||
104 | GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, | ||
105 | "pq", | ||
106 | "Query `%s' failed with unique violation: %s/%s/%s/%s/%s\n", | ||
107 | statement_name, | ||
108 | PQresultErrorField (result, | ||
109 | PG_DIAG_MESSAGE_PRIMARY), | ||
110 | PQresultErrorField (result, | ||
111 | PG_DIAG_MESSAGE_DETAIL), | ||
112 | PQresultErrorMessage (result), | ||
113 | PQresStatus (PQresultStatus (result)), | ||
114 | PQerrorMessage (db->conn)); | ||
115 | return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; | ||
116 | } | ||
117 | GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, | ||
118 | "pq", | ||
119 | "Query `%s' failed with result: %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_HARD_ERROR; | ||
129 | } | ||
130 | return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; | ||
131 | } | ||
132 | |||
133 | |||
134 | enum GNUNET_DB_QueryStatus | ||
135 | GNUNET_PQ_eval_prepared_non_select (struct GNUNET_PQ_Context *db, | ||
136 | const char *statement_name, | ||
137 | const struct GNUNET_PQ_QueryParam *params) | ||
138 | { | ||
139 | PGresult *result; | ||
140 | enum GNUNET_DB_QueryStatus qs; | ||
141 | |||
142 | result = GNUNET_PQ_exec_prepared (db, | ||
143 | statement_name, | ||
144 | params); | ||
145 | if (NULL == result) | ||
146 | return GNUNET_DB_STATUS_SOFT_ERROR; | ||
147 | qs = GNUNET_PQ_eval_result (db, | ||
148 | statement_name, | ||
149 | result); | ||
150 | if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) | ||
151 | { | ||
152 | const char *tuples; | ||
153 | |||
154 | /* What an awful API, this function really does return a string */ | ||
155 | tuples = PQcmdTuples (result); | ||
156 | if (NULL != tuples) | ||
157 | qs = strtol (tuples, NULL, 10); | ||
158 | } | ||
159 | PQclear (result); | ||
160 | return qs; | ||
161 | } | ||
162 | |||
163 | |||
164 | enum GNUNET_DB_QueryStatus | ||
165 | GNUNET_PQ_eval_prepared_multi_select (struct GNUNET_PQ_Context *db, | ||
166 | const char *statement_name, | ||
167 | const struct GNUNET_PQ_QueryParam *params, | ||
168 | GNUNET_PQ_PostgresResultHandler rh, | ||
169 | void *rh_cls) | ||
170 | { | ||
171 | PGresult *result; | ||
172 | enum GNUNET_DB_QueryStatus qs; | ||
173 | unsigned int ret; | ||
174 | |||
175 | result = GNUNET_PQ_exec_prepared (db, | ||
176 | statement_name, | ||
177 | params); | ||
178 | if (NULL == result) | ||
179 | return GNUNET_DB_STATUS_SOFT_ERROR; | ||
180 | qs = GNUNET_PQ_eval_result (db, | ||
181 | statement_name, | ||
182 | result); | ||
183 | if (qs < 0) | ||
184 | { | ||
185 | PQclear (result); | ||
186 | return qs; | ||
187 | } | ||
188 | ret = PQntuples (result); | ||
189 | if (NULL != rh) | ||
190 | rh (rh_cls, | ||
191 | result, | ||
192 | ret); | ||
193 | PQclear (result); | ||
194 | return ret; | ||
195 | } | ||
196 | |||
197 | |||
198 | enum GNUNET_DB_QueryStatus | ||
199 | GNUNET_PQ_eval_prepared_singleton_select ( | ||
200 | struct GNUNET_PQ_Context *db, | ||
201 | const char *statement_name, | ||
202 | const struct GNUNET_PQ_QueryParam *params, | ||
203 | struct GNUNET_PQ_ResultSpec *rs) | ||
204 | { | ||
205 | PGresult *result; | ||
206 | enum GNUNET_DB_QueryStatus qs; | ||
207 | int ntuples; | ||
208 | |||
209 | result = GNUNET_PQ_exec_prepared (db, | ||
210 | statement_name, | ||
211 | params); | ||
212 | if (NULL == result) | ||
213 | return GNUNET_DB_STATUS_SOFT_ERROR; | ||
214 | qs = GNUNET_PQ_eval_result (db, | ||
215 | statement_name, | ||
216 | result); | ||
217 | if (qs < 0) | ||
218 | { | ||
219 | PQclear (result); | ||
220 | return qs; | ||
221 | } | ||
222 | ntuples = PQntuples (result); | ||
223 | if (0 == ntuples) | ||
224 | { | ||
225 | PQclear (result); | ||
226 | return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; | ||
227 | } | ||
228 | if (1 != ntuples) | ||
229 | { | ||
230 | /* more than one result, but there must be at most one */ | ||
231 | GNUNET_break (0); | ||
232 | PQclear (result); | ||
233 | return GNUNET_DB_STATUS_HARD_ERROR; | ||
234 | } | ||
235 | if (GNUNET_OK != | ||
236 | GNUNET_PQ_extract_result (result, | ||
237 | rs, | ||
238 | 0)) | ||
239 | { | ||
240 | PQclear (result); | ||
241 | return GNUNET_DB_STATUS_HARD_ERROR; | ||
242 | } | ||
243 | PQclear (result); | ||
244 | return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; | ||
245 | } | ||
246 | |||
247 | |||
248 | /* end of pq/pq_eval.c */ | ||