exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

pg.c (9300B)


      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 /**
     45  * Function called each time we connect or reconnect to the
     46  * database. Gives the application a chance to run some
     47  * per-connection initialization logic.
     48  *
     49  * @param pg database conntext in the exchange
     50  * @param pq database connection handle
     51  */
     52 static void
     53 reconnect_cb (struct TALER_EXCHANGEDB_PostgresContext *pg,
     54               struct GNUNET_PQ_Context *pq)
     55 {
     56 #if AUTO_EXPLAIN
     57   /* Enable verbose logging to see where queries do not
     58      properly use indices */
     59   struct GNUNET_PQ_ExecuteStatement es[] = {
     60     GNUNET_PQ_make_try_execute ("LOAD 'auto_explain';"),
     61     GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"),
     62     GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"),
     63     GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"),
     64     /* https://wiki.postgresql.org/wiki/Serializable suggests to really
     65        force the default to 'serializable' if SSI is to be used. */
     66     GNUNET_PQ_make_try_execute (
     67       "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
     68     GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
     69     GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
     70     GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
     71     /* Mergejoin causes issues, see Postgres #18380 */
     72     GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"),
     73     GNUNET_PQ_EXECUTE_STATEMENT_END
     74   };
     75 #else
     76   struct GNUNET_PQ_ExecuteStatement es[] = {
     77     GNUNET_PQ_make_try_execute (
     78       "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
     79     GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
     80     GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
     81     /* Mergejoin causes issues, see Postgres #18380 */
     82     GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"),
     83     GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
     84     GNUNET_PQ_EXECUTE_STATEMENT_END
     85   };
     86 #endif
     87 
     88   if (GNUNET_OK !=
     89       GNUNET_PQ_exec_statements (pq,
     90                                  es))
     91   {
     92     GNUNET_break (0);
     93     return;
     94   }
     95   pg->prep_gen++;
     96 }
     97 
     98 
     99 /**
    100  * Connect to the db if the connection does not exist yet.
    101  *
    102  * @param[in,out] pg the database state
    103  * @return #GNUNET_OK on success
    104  */
    105 static enum GNUNET_GenericReturnValue
    106 internal_setup (struct TALER_EXCHANGEDB_PostgresContext *pg)
    107 {
    108   struct GNUNET_PQ_Context *db_conn;
    109 
    110   if (NULL != pg->conn)
    111     return GNUNET_OK;
    112   db_conn = GNUNET_PQ_init (pg->cfg,
    113                             "exchangedb-postgres",
    114                             &reconnect_cb,
    115                             pg);
    116   if (NULL == db_conn)
    117     return GNUNET_SYSERR;
    118   if (0 == pg->prep_gen)
    119   {
    120     GNUNET_PQ_disconnect (db_conn);
    121     return GNUNET_SYSERR;
    122   }
    123   if (0 == pg->prep_gen)
    124   {
    125     GNUNET_PQ_disconnect (db_conn);
    126     return GNUNET_SYSERR;
    127   }
    128   pg->conn = db_conn;
    129   return GNUNET_OK;
    130 }
    131 
    132 
    133 /**
    134  * Initialize the database connection.
    135  *
    136  * @param cfg configuration to use
    137  * @param check_current true to check if the database schema is current
    138  * @return NULL on failure
    139  */
    140 static struct TALER_EXCHANGEDB_PostgresContext *
    141 do_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
    142             bool check_current)
    143 {
    144   struct TALER_EXCHANGEDB_PostgresContext *pg;
    145   unsigned long long dpl;
    146 
    147   pg = GNUNET_new (struct TALER_EXCHANGEDB_PostgresContext);
    148   pg->cfg = cfg;
    149   if (GNUNET_OK !=
    150       GNUNET_CONFIGURATION_get_value_filename (cfg,
    151                                                "exchangedb-postgres",
    152                                                "SQL_DIR",
    153                                                &pg->sql_dir))
    154   {
    155     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    156                                "exchangedb-postgres",
    157                                "SQL_DIR");
    158     goto fail;
    159   }
    160   if (GNUNET_OK !=
    161       GNUNET_CONFIGURATION_get_value_string (cfg,
    162                                              "exchange",
    163                                              "BASE_URL",
    164                                              &pg->exchange_url))
    165   {
    166     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    167                                "exchange",
    168                                "BASE_URL");
    169     goto fail;
    170   }
    171   if (GNUNET_OK !=
    172       GNUNET_CONFIGURATION_get_value_time (cfg,
    173                                            "exchangedb",
    174                                            "IDLE_RESERVE_EXPIRATION_TIME",
    175                                            &pg->idle_reserve_expiration_time))
    176   {
    177     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    178                                "exchangedb",
    179                                "IDLE_RESERVE_EXPIRATION_TIME");
    180     goto fail;
    181   }
    182   if (GNUNET_OK !=
    183       GNUNET_CONFIGURATION_get_value_time (cfg,
    184                                            "exchangedb",
    185                                            "MAX_AML_PROGRAM_RUNTIME",
    186                                            &pg->max_aml_program_runtime))
    187   {
    188     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    189                                "exchangedb",
    190                                "MAX_AML_PROGRAM_RUNTIME");
    191     goto fail;
    192   }
    193   if (GNUNET_OK !=
    194       GNUNET_CONFIGURATION_get_value_time (cfg,
    195                                            "exchangedb",
    196                                            "LEGAL_RESERVE_EXPIRATION_TIME",
    197                                            &pg->legal_reserve_expiration_time))
    198   {
    199     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    200                                "exchangedb",
    201                                "LEGAL_RESERVE_EXPIRATION_TIME");
    202     goto fail;
    203   }
    204   if (GNUNET_OK !=
    205       GNUNET_CONFIGURATION_get_value_time (cfg,
    206                                            "exchangedb",
    207                                            "AGGREGATOR_SHIFT",
    208                                            &pg->aggregator_shift))
    209   {
    210     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
    211                                "exchangedb",
    212                                "AGGREGATOR_SHIFT");
    213   }
    214   if (GNUNET_OK !=
    215       GNUNET_CONFIGURATION_get_value_number (cfg,
    216                                              "exchangedb",
    217                                              "DEFAULT_PURSE_LIMIT",
    218                                              &dpl))
    219   {
    220     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
    221                                "exchangedb",
    222                                "DEFAULT_PURSE_LIMIT");
    223     pg->def_purse_limit = 1;
    224   }
    225   else
    226   {
    227     pg->def_purse_limit = (uint32_t) dpl;
    228   }
    229 
    230   if (GNUNET_OK !=
    231       TALER_config_get_currency (cfg,
    232                                  "exchange",
    233                                  &pg->currency))
    234   {
    235     goto fail;
    236   }
    237   if (GNUNET_OK !=
    238       internal_setup (pg))
    239   {
    240     goto fail;
    241   }
    242   if (check_current &&
    243       (GNUNET_OK !=
    244        GNUNET_PQ_check_current (pg->conn,
    245                                 "exchange-")) )
    246   {
    247     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    248                 "Database schema is not up-to-date. Try running taler-exchange-dbinit or taler-exchange-dbconfig!\n");
    249     goto fail;
    250   }
    251   return pg;
    252 
    253 fail:
    254   TALER_EXCHANGEDB_disconnect (pg);
    255   GNUNET_free (pg);
    256   return NULL;
    257 }
    258 
    259 
    260 struct TALER_EXCHANGEDB_PostgresContext *
    261 TALER_EXCHANGEDB_connect (
    262   const struct GNUNET_CONFIGURATION_Handle *cfg)
    263 {
    264   return do_connect (cfg,
    265                      true);
    266 }
    267 
    268 
    269 struct TALER_EXCHANGEDB_PostgresContext *
    270 TALER_EXCHANGEDB_connect_admin (
    271   const struct GNUNET_CONFIGURATION_Handle *cfg)
    272 {
    273   return do_connect (cfg,
    274                      false);
    275 }
    276 
    277 
    278 void
    279 TALER_EXCHANGEDB_disconnect (struct TALER_EXCHANGEDB_PostgresContext *pg)
    280 {
    281   if (NULL == pg)
    282     return;
    283   if (NULL != pg->conn)
    284   {
    285     GNUNET_PQ_disconnect (pg->conn);
    286     pg->conn = NULL;
    287   }
    288   GNUNET_free (pg->exchange_url);
    289   GNUNET_free (pg->sql_dir);
    290   GNUNET_free (pg->currency);
    291   GNUNET_free (pg);
    292 }
    293 
    294 
    295 /* end of plugin_exchangedb_postgres.c */