LCOV - code coverage report
Current view: top level - backenddb - start.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 88.0 % 50 44
Test Date: 2026-09-04 23:42:01 Functions: 100.0 % 4 4

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   (C) 2014--2025 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU Lesser General Public License as published by the Free Software
       7              :   Foundation; either version 3, or (at your option) any later version.
       8              : 
       9              :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10              :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11              :   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU General Public License along with
      14              :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15              : */
      16              : /**
      17              :  * @file src/backenddb/start.c
      18              :  * @brief implementation of the start, rollback and commit functions
      19              :  * @author Christian Grothoff
      20              :  */
      21              : #include <gnunet/gnunet_util_lib.h>
      22              : #include "merchantdb_lib.h"
      23              : #include "merchant-database/start.h"
      24              : #include "merchant-database/preflight.h"
      25              : #include "helper.h"
      26              : 
      27              : 
      28              : enum GNUNET_GenericReturnValue
      29          522 : TALER_MERCHANTDB_start (
      30              :   struct TALER_MERCHANTDB_PostgresContext *pg,
      31              :   const char *name)
      32              : {
      33          522 :   struct GNUNET_PQ_ExecuteStatement es[] = {
      34          522 :     GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"),
      35              :     GNUNET_PQ_EXECUTE_STATEMENT_END
      36              :   };
      37              : 
      38          522 :   GNUNET_assert (NULL != name);
      39          522 :   TALER_MERCHANTDB_preflight (pg);
      40          522 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
      41              :               "Starting merchant DB transaction `%s'\n",
      42              :               name);
      43          522 :   if (GNUNET_OK !=
      44          522 :       GNUNET_PQ_exec_statements (pg->conn,
      45              :                                  es))
      46              :   {
      47            0 :     TALER_LOG_ERROR ("Failed to start transaction\n");
      48            0 :     GNUNET_break (0);
      49            0 :     return GNUNET_SYSERR;
      50              :   }
      51          522 :   pg->transaction_name = name;
      52          522 :   return GNUNET_OK;
      53              : }
      54              : 
      55              : 
      56              : enum GNUNET_GenericReturnValue
      57            1 : TALER_MERCHANTDB_start_read_committed (
      58              :   struct TALER_MERCHANTDB_PostgresContext *pg,
      59              :   const char *name)
      60              : {
      61            1 :   struct GNUNET_PQ_ExecuteStatement es[] = {
      62            1 :     GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL READ COMMITTED"),
      63              :     GNUNET_PQ_EXECUTE_STATEMENT_END
      64              :   };
      65              : 
      66            1 :   GNUNET_assert (NULL != name);
      67            1 :   TALER_MERCHANTDB_preflight (pg);
      68            1 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
      69              :               "Starting merchant DB transaction %s (READ COMMITTED)\n",
      70              :               name);
      71            1 :   if (GNUNET_OK !=
      72            1 :       GNUNET_PQ_exec_statements (pg->conn,
      73              :                                  es))
      74              :   {
      75            0 :     TALER_LOG_ERROR ("Failed to start transaction\n");
      76            0 :     GNUNET_break (0);
      77            0 :     return GNUNET_SYSERR;
      78              :   }
      79            1 :   pg->transaction_name = name;
      80            1 :   return GNUNET_OK;
      81              : }
      82              : 
      83              : 
      84              : void
      85          123 : TALER_MERCHANTDB_rollback (
      86              :   struct TALER_MERCHANTDB_PostgresContext *pg)
      87              : {
      88          123 :   struct GNUNET_PQ_ExecuteStatement es[] = {
      89          123 :     GNUNET_PQ_make_execute ("ROLLBACK"),
      90              :     GNUNET_PQ_EXECUTE_STATEMENT_END
      91              :   };
      92              : 
      93          123 :   if (NULL == pg->transaction_name)
      94           10 :     return;
      95          113 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
      96              :               "Rolling back merchant DB transaction `%s'\n",
      97              :               pg->transaction_name);
      98          113 :   GNUNET_break (GNUNET_OK ==
      99              :                 GNUNET_PQ_exec_statements (pg->conn,
     100              :                                            es));
     101          113 :   pg->transaction_name = NULL;
     102              : }
     103              : 
     104              : 
     105              : enum GNUNET_DB_QueryStatus
     106          410 : TALER_MERCHANTDB_commit (
     107              :   struct TALER_MERCHANTDB_PostgresContext *pg)
     108              : {
     109          410 :   struct GNUNET_PQ_QueryParam params[] = {
     110              :     GNUNET_PQ_query_param_end
     111              :   };
     112              :   enum GNUNET_DB_QueryStatus qs;
     113              : 
     114          410 :   GNUNET_break (NULL != pg->transaction_name);
     115          410 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     116              :               "Committing transaction `%s'\n",
     117              :               pg->transaction_name);
     118          410 :   PREPARE (pg,
     119              :            "commit",
     120              :            "COMMIT");
     121              :   {
     122              :     PGresult *result;
     123              : 
     124          410 :     result = GNUNET_PQ_exec_prepared (pg->conn,
     125              :                                       "commit",
     126              :                                       params);
     127          410 :     qs = GNUNET_PQ_eval_result (pg->conn,
     128              :                                 "commit",
     129              :                                 result);
     130              :     /* PostgreSQL accepts COMMIT on a transaction it has already aborted: it
     131              :        silently rolls the transaction back and answers PGRES_COMMAND_OK with the
     132              :        command tag ROLLBACK.  GNUNET_PQ_eval_result() cannot tell that apart from
     133              :        a real commit, so every caller took the success path for a transaction
     134              :        whose writes are gone.  Report it as a soft error instead: sessions run
     135              :        SERIALIZABLE, so the overwhelmingly likely cause is an unhandled
     136              :        40001/40P01 for which retrying is right; a caller with a retry loop gives
     137              :        up after MAX_RETRIES and fails visibly, one without fails immediately.
     138              : 
     139              :        The *success* path deliberately keeps returning exactly what
     140              :        GNUNET_PQ_eval_prepared_non_select() returned before.  That function is
     141              :        GNUNET_PQ_exec_prepared() + GNUNET_PQ_eval_result() plus a
     142              :        strtol(PQcmdTuples()) step that only runs when the status is already
     143              :        GNUNET_DB_STATUS_SUCCESS_NO_RESULTS, and PQcmdTuples() on a COMMIT command
     144              :        tag is the empty string -- so that step yields 0, which is
     145              :        GNUNET_DB_STATUS_SUCCESS_NO_RESULTS again. */
     146          410 :     if ( (0 <= qs) &&
     147          410 :          (NULL != result) &&
     148          410 :          (0 == strcmp ("ROLLBACK",
     149          410 :                        PQcmdStatus (result))) )
     150              :     {
     151            1 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     152              :                   "Transaction `%s' was rolled back by the database instead of committed; a previous statement must have failed without being handled\n",
     153              :                   pg->transaction_name);
     154            1 :       qs = GNUNET_DB_STATUS_SOFT_ERROR;
     155              :     }
     156          410 :     PQclear (result);
     157              :   }
     158          410 :   pg->transaction_name = NULL;
     159          410 :   return qs;
     160              : }
        

Generated by: LCOV version 2.0-1