pq_eval.c 10.0 KB

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