LCOV - code coverage report
Current view: top level - exchangedb - pg.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 67.8 % 90 61
Test Date: 2026-09-11 18:55:36 Functions: 100.0 % 6 6

            Line data    Source code
       1              : /*
       2              :    This file is part of TALER
       3              :    Copyright (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 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              : /**
      18              :  * @file pg.c
      19              :  * @brief Low-level (statement-level) Postgres database access for the exchange
      20              :  * @author Florian Dold
      21              :  * @author Christian Grothoff
      22              :  * @author Sree Harsha Totakura
      23              :  * @author Marcello Stanisci
      24              :  * @author Özgür Kesim
      25              :  */
      26              : #include <poll.h>
      27              : #include <pthread.h>
      28              : #include <libpq-fe.h>
      29              : struct TALER_EXCHANGEDB_PostgresContext;
      30              : #define GNUNET_PQ_RECONNECT_CALLBACK_CLOSURE \
      31              :         struct TALER_EXCHANGEDB_PostgresContext
      32              : #include "helper.h"
      33              : #include "exchangedb_lib.h"
      34              : #include "exchange-database/preflight.h"
      35              : 
      36              : /**
      37              :  * Set to 1 to enable Postgres auto_explain module. This will
      38              :  * slow down things a _lot_, but also provide extensive logging
      39              :  * in the Postgres database logger for performance analysis.
      40              :  */
      41              : #define AUTO_EXPLAIN 0
      42              : 
      43              : /**
      44              :  * Counts how often we have established a fresh @e conn
      45              :  * to the database. Used to re-prepare statements.
      46              :  */
      47              : unsigned long long TEH_PG_prep_gen_;
      48              : 
      49              : /**
      50              :  * Function called each time we connect or reconnect to the
      51              :  * database. Gives the application a chance to run some
      52              :  * per-connection initialization logic.
      53              :  *
      54              :  * @param pg database context of the exchange
      55              :  * @param pq database connection handle
      56              :  */
      57              : static void
      58          286 : reconnect_cb (struct TALER_EXCHANGEDB_PostgresContext *pg,
      59              :               struct GNUNET_PQ_Context *pq)
      60              : {
      61              : #if AUTO_EXPLAIN
      62              :   /* Enable verbose logging to see where queries do not
      63              :      properly use indices */
      64              :   struct GNUNET_PQ_ExecuteStatement es[] = {
      65              :     GNUNET_PQ_make_try_execute ("LOAD 'auto_explain';"),
      66              :     GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"),
      67              :     GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"),
      68              :     GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"),
      69              :     /* https://wiki.postgresql.org/wiki/Serializable suggests to really
      70              :        force the default to 'serializable' if SSI is to be used. */
      71              :     GNUNET_PQ_make_try_execute (
      72              :       "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
      73              :     GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
      74              :     GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
      75              :     GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
      76              :     /* Mergejoin causes issues, see Postgres #18380 */
      77              :     GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"),
      78              :     GNUNET_PQ_EXECUTE_STATEMENT_END
      79              :   };
      80              : #else
      81          286 :   struct GNUNET_PQ_ExecuteStatement es[] = {
      82          286 :     GNUNET_PQ_make_try_execute (
      83              :       "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
      84          286 :     GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
      85          286 :     GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
      86              :     /* Mergejoin causes issues, see Postgres #18380 */
      87          286 :     GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"),
      88          286 :     GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
      89              :     GNUNET_PQ_EXECUTE_STATEMENT_END
      90              :   };
      91              : #endif
      92              : 
      93          286 :   if (GNUNET_OK !=
      94          286 :       GNUNET_PQ_exec_statements (pq,
      95              :                                  es))
      96              :   {
      97            0 :     GNUNET_break (0);
      98            0 :     return;
      99              :   }
     100              :   {
     101              :     char *set_time;
     102              :     enum GNUNET_GenericReturnValue ret;
     103              : 
     104          286 :     GNUNET_asprintf (&set_time,
     105              :                      "SET taler.timetravel_us = '%lld'",
     106              :                      GNUNET_TIME_get_offset ());
     107              :     {
     108          286 :       struct GNUNET_PQ_ExecuteStatement time_es[] = {
     109          286 :         GNUNET_PQ_make_execute (set_time),
     110              :         GNUNET_PQ_EXECUTE_STATEMENT_END
     111              :       };
     112              : 
     113          286 :       ret = GNUNET_PQ_exec_statements (pq,
     114              :                                       time_es);
     115              :     }
     116          286 :     GNUNET_free (set_time);
     117          286 :     if (GNUNET_OK != ret)
     118              :     {
     119            0 :       GNUNET_break (0);
     120            0 :       return;
     121              :     }
     122              :   }
     123          286 :   TEH_PG_prep_gen_++;
     124              : }
     125              : 
     126              : 
     127              : /**
     128              :  * Connect to the db if the connection does not exist yet.
     129              :  *
     130              :  * @param[in,out] pg the database state
     131              :  * @return #GNUNET_OK on success
     132              :  */
     133              : static enum GNUNET_GenericReturnValue
     134          285 : internal_setup (struct TALER_EXCHANGEDB_PostgresContext *pg)
     135              : {
     136              :   struct GNUNET_PQ_Context *db_conn;
     137              : 
     138          285 :   if (NULL != pg->conn)
     139            0 :     return GNUNET_OK;
     140          285 :   db_conn = GNUNET_PQ_init (pg->cfg,
     141              :                             "exchangedb-postgres",
     142              :                             &reconnect_cb,
     143              :                             pg);
     144          285 :   if (NULL == db_conn)
     145            0 :     return GNUNET_SYSERR;
     146          285 :   if (0 == TEH_PG_prep_gen_)
     147              :   {
     148            0 :     GNUNET_PQ_disconnect (db_conn);
     149            0 :     return GNUNET_SYSERR;
     150              :   }
     151          285 :   pg->conn = db_conn;
     152          285 :   return GNUNET_OK;
     153              : }
     154              : 
     155              : 
     156              : /**
     157              :  * Initialize the database connection.
     158              :  *
     159              :  * @param cfg configuration to use
     160              :  * @param check_current true to check if the database schema is current
     161              :  * @return NULL on failure
     162              :  */
     163              : static struct TALER_EXCHANGEDB_PostgresContext *
     164          285 : do_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
     165              :             bool check_current)
     166              : {
     167              :   struct TALER_EXCHANGEDB_PostgresContext *pg;
     168              :   unsigned long long dpl;
     169              : 
     170          285 :   pg = GNUNET_new (struct TALER_EXCHANGEDB_PostgresContext);
     171          285 :   pg->cfg = cfg;
     172          285 :   if (GNUNET_OK !=
     173          285 :       GNUNET_CONFIGURATION_get_value_filename (cfg,
     174              :                                                "exchangedb-postgres",
     175              :                                                "SQL_DIR",
     176              :                                                &pg->sql_dir))
     177              :   {
     178            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     179              :                                "exchangedb-postgres",
     180              :                                "SQL_DIR");
     181            0 :     goto fail;
     182              :   }
     183          285 :   if (GNUNET_OK !=
     184          285 :       GNUNET_CONFIGURATION_get_value_string (cfg,
     185              :                                              "exchange",
     186              :                                              "BASE_URL",
     187              :                                              &pg->exchange_url))
     188              :   {
     189            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     190              :                                "exchange",
     191              :                                "BASE_URL");
     192            0 :     goto fail;
     193              :   }
     194          285 :   if (GNUNET_OK !=
     195          285 :       GNUNET_CONFIGURATION_get_value_time (cfg,
     196              :                                            "exchangedb",
     197              :                                            "IDLE_RESERVE_EXPIRATION_TIME",
     198              :                                            &pg->idle_reserve_expiration_time))
     199              :   {
     200            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     201              :                                "exchangedb",
     202              :                                "IDLE_RESERVE_EXPIRATION_TIME");
     203            0 :     goto fail;
     204              :   }
     205          285 :   if (GNUNET_OK !=
     206          285 :       GNUNET_CONFIGURATION_get_value_time (cfg,
     207              :                                            "exchangedb",
     208              :                                            "MAX_AML_PROGRAM_RUNTIME",
     209              :                                            &pg->max_aml_program_runtime))
     210              :   {
     211            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     212              :                                "exchangedb",
     213              :                                "MAX_AML_PROGRAM_RUNTIME");
     214            0 :     goto fail;
     215              :   }
     216          285 :   if (GNUNET_OK !=
     217          285 :       GNUNET_CONFIGURATION_get_value_time (cfg,
     218              :                                            "exchangedb",
     219              :                                            "LEGAL_RESERVE_EXPIRATION_TIME",
     220              :                                            &pg->legal_reserve_expiration_time))
     221              :   {
     222            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     223              :                                "exchangedb",
     224              :                                "LEGAL_RESERVE_EXPIRATION_TIME");
     225            0 :     goto fail;
     226              :   }
     227          285 :   if (GNUNET_OK !=
     228          285 :       GNUNET_CONFIGURATION_get_value_time (cfg,
     229              :                                            "exchangedb",
     230              :                                            "AGGREGATOR_SHIFT",
     231              :                                            &pg->aggregator_shift))
     232              :   {
     233            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
     234              :                                "exchangedb",
     235              :                                "AGGREGATOR_SHIFT");
     236              :   }
     237          285 :   if (GNUNET_OK !=
     238          285 :       GNUNET_CONFIGURATION_get_value_number (cfg,
     239              :                                              "exchangedb",
     240              :                                              "DEFAULT_PURSE_LIMIT",
     241              :                                              &dpl))
     242              :   {
     243            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
     244              :                                "exchangedb",
     245              :                                "DEFAULT_PURSE_LIMIT");
     246            0 :     pg->def_purse_limit = 1;
     247              :   }
     248              :   else
     249              :   {
     250          285 :     pg->def_purse_limit = (uint32_t) dpl;
     251              :   }
     252              : 
     253          285 :   if (GNUNET_OK !=
     254          285 :       TALER_config_get_currency (cfg,
     255              :                                  "exchange",
     256              :                                  &pg->currency))
     257              :   {
     258            0 :     goto fail;
     259              :   }
     260          285 :   if (GNUNET_OK !=
     261          285 :       internal_setup (pg))
     262              :   {
     263            0 :     goto fail;
     264              :   }
     265          493 :   if (check_current &&
     266              :       (GNUNET_OK !=
     267          208 :        GNUNET_PQ_check_current (pg->conn,
     268              :                                 "exchange-")) )
     269              :   {
     270            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     271              :                 "Database schema is not up-to-date. Try running taler-exchange-dbinit or taler-exchange-dbconfig!\n");
     272            0 :     goto fail;
     273              :   }
     274          285 :   return pg;
     275              : 
     276            0 : fail:
     277            0 :   TALER_EXCHANGEDB_disconnect (pg);
     278            0 :   return NULL;
     279              : }
     280              : 
     281              : 
     282              : struct TALER_EXCHANGEDB_PostgresContext *
     283          208 : TALER_EXCHANGEDB_connect (
     284              :   const struct GNUNET_CONFIGURATION_Handle *cfg)
     285              : {
     286          208 :   return do_connect (cfg,
     287              :                      true);
     288              : }
     289              : 
     290              : 
     291              : struct TALER_EXCHANGEDB_PostgresContext *
     292           77 : TALER_EXCHANGEDB_connect_admin (
     293              :   const struct GNUNET_CONFIGURATION_Handle *cfg)
     294              : {
     295           77 :   return do_connect (cfg,
     296              :                      false);
     297              : }
     298              : 
     299              : 
     300              : void
     301          285 : TALER_EXCHANGEDB_disconnect (struct TALER_EXCHANGEDB_PostgresContext *pg)
     302              : {
     303          285 :   if (NULL == pg)
     304            0 :     return;
     305          285 :   if (NULL != pg->conn)
     306              :   {
     307          285 :     GNUNET_PQ_disconnect (pg->conn);
     308          285 :     pg->conn = NULL;
     309              :   }
     310          285 :   GNUNET_free (pg->exchange_url);
     311          285 :   GNUNET_free (pg->sql_dir);
     312          285 :   GNUNET_free (pg->currency);
     313          285 :   GNUNET_free (pg);
     314              : }
     315              : 
     316              : 
     317              : /* end of pg.c */
        

Generated by: LCOV version 2.0-1