/*
  This file is part of TALER
  Copyright (C) 2014-2022 Taler Systems SA
  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.
  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see 
*/
/**
 * @file plugin_auditordb_postgres.c
 * @brief Low-level (statement-level) Postgres database access for the auditor
 * @author Christian Grothoff
 * @author Gabor X Toth
 */
#include "platform.h"
#include "taler_pq_lib.h"
#include 
#include 
#include "pg_helper.h"
#include "pg_insert_auditor_progress_reserve.h"
#include "pg_update_auditor_progress_reserve.h"
#include "pg_get_auditor_progress_reserve.h"
#include "pg_insert_auditor_progress_purse.h"
#include "pg_update_auditor_progress_purse.h"
#include "pg_get_auditor_progress_purse.h"
#include "pg_insert_auditor_progress_aggregation.h"
#include "pg_update_auditor_progress_aggregation.h"
#include "pg_get_auditor_progress_aggregation.h"
#include "pg_insert_auditor_progress_deposit_confirmation.h"
#include "pg_update_auditor_progress_deposit_confirmation.h"
#include "pg_get_auditor_progress_deposit_confirmation.h"
#include "pg_insert_auditor_progress_coin.h"
#include "pg_update_auditor_progress_coin.h"
#include "pg_get_auditor_progress_coin.h"
#include "pg_insert_wire_auditor_account_progress.h"
#include "pg_update_wire_auditor_account_progress.h"
#include "pg_get_wire_auditor_account_progress.h"
#include "pg_insert_wire_auditor_progress.h"
#include "pg_update_wire_auditor_progress.h"
#include "pg_get_wire_auditor_progress.h"
#include "pg_insert_reserve_info.h"
#include "pg_update_reserve_info.h"
#include "pg_del_reserve_info.h"
#include "pg_get_reserve_info.h"
#include "pg_insert_reserve_summary.h"
#include "pg_update_reserve_summary.h"
#include "pg_get_reserve_summary.h"
#include "pg_insert_wire_fee_summary.h"
#include "pg_update_wire_fee_summary.h"
#include "pg_get_wire_fee_summary.h"
#include "pg_insert_denomination_balance.h"
#include "pg_update_denomination_balance.h"
#include "pg_get_denomination_balance.h"
#include "pg_insert_balance_summary.h"
#include "pg_update_balance_summary.h"
#include "pg_get_balance_summary.h"
#include "pg_insert_historic_denom_revenue.h"
#include "pg_select_historic_denom_revenue.h"
#include "pg_insert_historic_reserve_revenue.h"
#include "pg_select_historic_reserve_revenue.h"
#include "pg_insert_predicted_result.h"
#include "pg_update_predicted_result.h"
#include "pg_get_predicted_balance.h"
#include "pg_insert_exchange.h"
#include "pg_list_exchanges.h"
#include "pg_delete_exchange.h"
#include "pg_insert_exchange_signkey.h"
#include "pg_insert_deposit_confirmation.h"
#include "pg_get_deposit_confirmations.h"
#include "pg_insert_auditor_progress_coin.h"
#include "pg_update_auditor_progress_coin.h"
#include "pg_get_auditor_progress_coin.h"
#include "pg_insert_auditor_progress_purse.h"
#include "pg_update_auditor_progress_purse.h"
#include "pg_get_auditor_progress_purse.h"
#include "pg_get_reserve_info.h"
#include "pg_insert_historic_reserve_revenue.h"
#include "pg_insert_wire_auditor_progress.h"
#include "pg_update_wire_auditor_progress.h"
#include "pg_get_wire_auditor_progress.h"
#include "pg_insert_historic_reserve_revenue.h"
#include "pg_helper.h"
#include "pg_get_purse_info.h"
#include "pg_delete_purse_info.h"
#include "pg_update_purse_info.h"
#include "pg_insert_purse_info.h"
#include "pg_get_purse_summary.h"
#include "pg_select_purse_expired.h"
#include "pg_insert_purse_summary.h"
#include "pg_update_purse_summary.h"
#define LOG(kind,...) GNUNET_log_from (kind, "taler-auditordb-postgres", \
                                       __VA_ARGS__)
/**
 * Drop all auditor tables OR deletes recoverable auditor state.
 * This should only be used by testcases or when restarting the
 * auditor from scratch.
 *
 * @param cls the `struct PostgresClosure` with the plugin-specific state
 * @param drop_exchangelist drop all tables, including schema versioning
 *        and the exchange and deposit_confirmations table; NOT to be
 *        used when restarting the auditor
 * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
 */
static enum GNUNET_GenericReturnValue
postgres_drop_tables (void *cls,
                      bool drop_exchangelist)
{
  struct PostgresClosure *pc = cls;
  struct GNUNET_PQ_Context *conn;
  enum GNUNET_GenericReturnValue ret;
  conn = GNUNET_PQ_connect_with_cfg (pc->cfg,
                                     "auditordb-postgres",
                                     NULL,
                                     NULL,
                                     NULL);
  if (NULL == conn)
    return GNUNET_SYSERR;
  ret = GNUNET_PQ_exec_sql (conn,
                            (drop_exchangelist) ? "drop" : "restart");
  GNUNET_PQ_disconnect (conn);
  return ret;
}
/**
 * Create the necessary tables if they are not present
 *
 * @param cls the `struct PostgresClosure` with the plugin-specific state
 * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
 */
static enum GNUNET_GenericReturnValue
postgres_create_tables (void *cls)
{
  struct PostgresClosure *pc = cls;
  struct GNUNET_PQ_Context *conn;
  struct GNUNET_PQ_ExecuteStatement es[] = {
    GNUNET_PQ_make_try_execute ("SET search_path TO auditor;"),
    GNUNET_PQ_EXECUTE_STATEMENT_END
  };
  conn = GNUNET_PQ_connect_with_cfg (pc->cfg,
                                     "auditordb-postgres",
                                     "auditor-",
                                     es,
                                     NULL);
  if (NULL == conn)
    return GNUNET_SYSERR;
  GNUNET_PQ_disconnect (conn);
  return GNUNET_OK;
}
/**
 * Connect to the db if the connection does not exist yet.
 *
 * @param[in,out] pg the plugin-specific state
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
setup_connection (struct PostgresClosure *pg)
{
  struct GNUNET_PQ_ExecuteStatement es[] = {
    GNUNET_PQ_make_try_execute ("SET search_path TO auditor;"),
    GNUNET_PQ_EXECUTE_STATEMENT_END
  };
  struct GNUNET_PQ_Context *db_conn;
  if (NULL != pg->conn)
  {
    GNUNET_PQ_reconnect_if_down (pg->conn);
    return GNUNET_OK;
  }
  db_conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
                                        "auditordb-postgres",
                                        NULL,
                                        es,
                                        NULL);
  if (NULL == db_conn)
    return GNUNET_SYSERR;
  pg->conn = db_conn;
  pg->prep_gen++;
  return GNUNET_OK;
}
/**
 * Do a pre-flight check that we are not in an uncommitted transaction.
 * If we are, rollback the previous transaction and output a warning.
 *
 * @param cls the `struct PostgresClosure` with the plugin-specific state
 * @return #GNUNET_OK on success,
 *         #GNUNET_NO if we rolled back an earlier transaction
 *         #GNUNET_SYSERR if we have no DB connection
 */
static enum GNUNET_GenericReturnValue
postgres_preflight (void *cls)
{
  struct PostgresClosure *pg = cls;
  struct GNUNET_PQ_ExecuteStatement es[] = {
    GNUNET_PQ_make_execute ("ROLLBACK"),
    GNUNET_PQ_EXECUTE_STATEMENT_END
  };
  if (NULL == pg->conn)
  {
    if (GNUNET_OK !=
        setup_connection (pg))
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
  }
  if (NULL == pg->transaction_name)
    return GNUNET_OK; /* all good */
  if (GNUNET_OK ==
      GNUNET_PQ_exec_statements (pg->conn,
                                 es))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "BUG: Preflight check rolled back transaction `%s'!\n",
                pg->transaction_name);
  }
  else
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "BUG: Preflight check failed to rollback transaction `%s'!\n",
                pg->transaction_name);
  }
  pg->transaction_name = NULL;
  return GNUNET_NO;
}
/**
 * Start a transaction.
 *
 * @param cls the `struct PostgresClosure` with the plugin-specific state
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
postgres_start (void *cls)
{
  struct PostgresClosure *pg = cls;
  struct GNUNET_PQ_ExecuteStatement es[] = {
    GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"),
    GNUNET_PQ_EXECUTE_STATEMENT_END
  };
  postgres_preflight (cls);
  if (GNUNET_OK !=
      GNUNET_PQ_exec_statements (pg->conn,
                                 es))
  {
    TALER_LOG_ERROR ("Failed to start transaction\n");
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}
/**
 * Roll back the current transaction of a database connection.
 *
 * @param cls the `struct PostgresClosure` with the plugin-specific state
 */
static void
postgres_rollback (void *cls)
{
  struct PostgresClosure *pg = cls;
  struct GNUNET_PQ_ExecuteStatement es[] = {
    GNUNET_PQ_make_execute ("ROLLBACK"),
    GNUNET_PQ_EXECUTE_STATEMENT_END
  };
  GNUNET_break (GNUNET_OK ==
                GNUNET_PQ_exec_statements (pg->conn,
                                           es));
}
/**
 * Commit the current transaction of a database connection.
 *
 * @param cls the `struct PostgresClosure` with the plugin-specific state
 * @return transaction status code
 */
enum GNUNET_DB_QueryStatus
postgres_commit (void *cls)
{
  struct PostgresClosure *pg = cls;
  struct GNUNET_PQ_QueryParam params[] = {
    GNUNET_PQ_query_param_end
  };
  PREPARE (pg,
           "do_commit",
           "COMMIT");
  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
                                             "do_commit",
                                             params);
}
/**
 * Function called to perform "garbage collection" on the
 * database, expiring records we no longer require.
 *
 * @param cls closure
 * @return #GNUNET_OK on success,
 *         #GNUNET_SYSERR on DB errors
 */
static enum GNUNET_GenericReturnValue
postgres_gc (void *cls)
{
  struct PostgresClosure *pg = cls;
  struct GNUNET_TIME_Absolute now = {0};
  struct GNUNET_PQ_QueryParam params_time[] = {
    GNUNET_PQ_query_param_absolute_time (&now),
    GNUNET_PQ_query_param_end
  };
  struct GNUNET_PQ_Context *conn;
  enum GNUNET_DB_QueryStatus qs;
  struct GNUNET_PQ_PreparedStatement ps[] = {
#if 0
    GNUNET_PQ_make_prepare ("gc_auditor",
                            "TODO: #4960",
                            0),
#endif
    GNUNET_PQ_PREPARED_STATEMENT_END
  };
  struct GNUNET_PQ_ExecuteStatement es[] = {
    GNUNET_PQ_make_try_execute ("SET search_path TO auditor;"),
    GNUNET_PQ_EXECUTE_STATEMENT_END
  };
  now = GNUNET_TIME_absolute_get ();
  conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
                                     "auditordb-postgres",
                                     NULL,
                                     es,
                                     ps);
  if (NULL == conn)
    return GNUNET_SYSERR;
  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
              "TODO: Auditor GC not implemented (#4960)\n");
  qs = GNUNET_PQ_eval_prepared_non_select (conn,
                                           "gc_auditor",
                                           params_time);
  GNUNET_PQ_disconnect (conn);
  if (0 > qs)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}
/**
 * Initialize Postgres database subsystem.
 *
 * @param cls a configuration instance
 * @return NULL on error, otherwise a `struct TALER_AUDITORDB_Plugin`
 */
void *
libtaler_plugin_auditordb_postgres_init (void *cls)
{
  const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
  struct PostgresClosure *pg;
  struct TALER_AUDITORDB_Plugin *plugin;
  pg = GNUNET_new (struct PostgresClosure);
  pg->cfg = cfg;
  if (GNUNET_OK !=
      TALER_config_get_currency (cfg,
                                 &pg->currency))
  {
    GNUNET_free (pg);
    return NULL;
  }
  plugin = GNUNET_new (struct TALER_AUDITORDB_Plugin);
  plugin->cls = pg;
  plugin->preflight = &postgres_preflight;
  plugin->drop_tables = &postgres_drop_tables;
  plugin->create_tables = &postgres_create_tables;
  plugin->start = &postgres_start;
  plugin->commit = &postgres_commit;
  plugin->rollback = &postgres_rollback;
  plugin->gc = &postgres_gc;
  plugin->insert_exchange
    = &TAH_PG_insert_exchange;
  plugin->delete_exchange
    = &TAH_PG_delete_exchange;
  plugin->list_exchanges
    = &TAH_PG_list_exchanges;
  plugin->insert_exchange_signkey
    = &TAH_PG_insert_exchange_signkey;
  plugin->insert_deposit_confirmation
    = &TAH_PG_insert_deposit_confirmation;
  plugin->get_deposit_confirmations
    = &TAH_PG_get_deposit_confirmations;
  plugin->get_auditor_progress_reserve
    = &TAH_PG_get_auditor_progress_reserve;
  plugin->update_auditor_progress_reserve
    = &TAH_PG_update_auditor_progress_reserve;
  plugin->insert_auditor_progress_reserve
    = &TAH_PG_insert_auditor_progress_reserve;
  plugin->get_auditor_progress_purse
    = &TAH_PG_get_auditor_progress_purse;
  plugin->update_auditor_progress_purse
    = &TAH_PG_update_auditor_progress_purse;
  plugin->insert_auditor_progress_purse
    = &TAH_PG_insert_auditor_progress_purse;
  plugin->get_auditor_progress_aggregation
    = &TAH_PG_get_auditor_progress_aggregation;
  plugin->update_auditor_progress_aggregation
    = &TAH_PG_update_auditor_progress_aggregation;
  plugin->insert_auditor_progress_aggregation
    = &TAH_PG_insert_auditor_progress_aggregation;
  plugin->get_auditor_progress_deposit_confirmation
    = &TAH_PG_get_auditor_progress_deposit_confirmation;
  plugin->update_auditor_progress_deposit_confirmation
    = &TAH_PG_update_auditor_progress_deposit_confirmation;
  plugin->insert_auditor_progress_deposit_confirmation
    = &TAH_PG_insert_auditor_progress_deposit_confirmation;
  plugin->get_auditor_progress_coin
    = &TAH_PG_get_auditor_progress_coin;
  plugin->update_auditor_progress_coin
    = &TAH_PG_update_auditor_progress_coin;
  plugin->insert_auditor_progress_coin
    = &TAH_PG_insert_auditor_progress_coin;
  plugin->get_wire_auditor_account_progress
    = &TAH_PG_get_wire_auditor_account_progress;
  plugin->update_wire_auditor_account_progress
    = &TAH_PG_update_wire_auditor_account_progress;
  plugin->insert_wire_auditor_account_progress
    = &TAH_PG_insert_wire_auditor_account_progress;
  plugin->get_wire_auditor_progress
    = &TAH_PG_get_wire_auditor_progress;
  plugin->update_wire_auditor_progress
    = &TAH_PG_update_wire_auditor_progress;
  plugin->insert_wire_auditor_progress
    = &TAH_PG_insert_wire_auditor_progress;
  plugin->del_reserve_info
    = &TAH_PG_del_reserve_info;
  plugin->get_reserve_info
    = &TAH_PG_get_reserve_info;
  plugin->update_reserve_info
    = &TAH_PG_update_reserve_info;
  plugin->insert_reserve_info
    = &TAH_PG_insert_reserve_info;
  plugin->get_reserve_summary
    = &TAH_PG_get_reserve_summary;
  plugin->update_reserve_summary
    = &TAH_PG_update_reserve_summary;
  plugin->insert_reserve_summary
    = &TAH_PG_insert_reserve_summary;
  plugin->get_wire_fee_summary
    = &TAH_PG_get_wire_fee_summary;
  plugin->update_wire_fee_summary
    = &TAH_PG_update_wire_fee_summary;
  plugin->insert_wire_fee_summary
    = &TAH_PG_insert_wire_fee_summary;
  plugin->get_denomination_balance
    = &TAH_PG_get_denomination_balance;
  plugin->update_denomination_balance
    = &TAH_PG_update_denomination_balance;
  plugin->insert_denomination_balance
    = &TAH_PG_insert_denomination_balance;
  plugin->get_balance_summary
    = &TAH_PG_get_balance_summary;
  plugin->update_balance_summary
    = &TAH_PG_update_balance_summary;
  plugin->insert_balance_summary
    = &TAH_PG_insert_balance_summary;
  plugin->select_historic_denom_revenue
    = &TAH_PG_select_historic_denom_revenue;
  plugin->insert_historic_denom_revenue
    = &TAH_PG_insert_historic_denom_revenue;
  plugin->select_historic_reserve_revenue
    = &TAH_PG_select_historic_reserve_revenue;
  plugin->insert_historic_reserve_revenue
    = &TAH_PG_insert_historic_reserve_revenue;
  plugin->get_predicted_balance
    = &TAH_PG_get_predicted_balance;
  plugin->update_predicted_result
    = &TAH_PG_update_predicted_result;
  plugin->insert_predicted_result
    = &TAH_PG_insert_predicted_result;
  plugin->get_purse_info
    = &TAH_PG_get_purse_info;
  plugin->delete_purse_info
    = &TAH_PG_delete_purse_info;
  plugin->update_purse_info
    = &TAH_PG_update_purse_info;
  plugin->insert_purse_info
    = &TAH_PG_insert_purse_info;
  plugin->get_purse_summary
    = &TAH_PG_get_purse_summary;
  plugin->select_purse_expired
    = &TAH_PG_select_purse_expired;
  plugin->insert_purse_summary
    = &TAH_PG_insert_purse_summary;
  plugin->update_purse_summary
    = &TAH_PG_update_purse_summary;
  return plugin;
}
/**
 * Shutdown Postgres database subsystem.
 *
 * @param cls a `struct TALER_AUDITORDB_Plugin`
 * @return NULL (always)
 */
void *
libtaler_plugin_auditordb_postgres_done (void *cls)
{
  struct TALER_AUDITORDB_Plugin *plugin = cls;
  struct PostgresClosure *pg = plugin->cls;
  if (NULL != pg->conn)
    GNUNET_PQ_disconnect (pg->conn);
  GNUNET_free (pg->currency);
  GNUNET_free (pg);
  GNUNET_free (plugin);
  return NULL;
}
/* end of plugin_auditordb_postgres.c */