/*
  This file is part of Challenger
  (C) 2023 Taler Systems SA

  Challenger is free software; you can redistribute it and/or modify it under the
  terms of the GNU Lesser General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.

  Challenger 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
  Challenger; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file src/challengerdb/pg.c
 * @brief database helper functions for postgres used by challenger
 * @author Christian Grothoff
 */
struct CHALLENGERDB_PostgresContext;
#define GNUNET_PQ_RECONNECT_CALLBACK_CLOSURE \
        struct CHALLENGERDB_PostgresContext
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_db_lib.h>
#include <gnunet/gnunet_pq_lib.h>
#include <taler/taler_pq_lib.h>
#include "challenger_database_lib.h"
#include "challenger_util.h"
#include "pg_helper.h"

/**
 * Counts how often we have established a fresh @e conn
 * to the database. Used to re-prepare statements.
 */
unsigned long long CH_PG_prep_gen_;

/**
 * Function called each time we connect or reconnect to the
 * database. Gives the application a chance to run some
 * per-connection initialization logic.
 *
 * @param pg challenger database context
 * @param pq database connection handle
 */
static void
reconnect_cb (struct CHALLENGERDB_PostgresContext *pg,
              struct GNUNET_PQ_Context *pq)
{
#if AUTO_EXPLAIN
  /* Enable verbose logging to see where queries do not
     properly use indices */
  struct GNUNET_PQ_ExecuteStatement es[] = {
    GNUNET_PQ_make_try_execute ("LOAD 'auto_explain';"),
    GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"),
    GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"),
    GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"),
    /* https://wiki.postgresql.org/wiki/Serializable suggests to really
       force the default to 'serializable' if SSI is to be used. */
    GNUNET_PQ_make_try_execute (
      "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
    GNUNET_PQ_make_execute ("SET search_path TO challenger;"),
    GNUNET_PQ_EXECUTE_STATEMENT_END
  };
#else
  struct GNUNET_PQ_ExecuteStatement es[] = {
    /* https://wiki.postgresql.org/wiki/Serializable suggests to really
       force the default to 'serializable' if SSI is to be used. */
    GNUNET_PQ_make_try_execute (
      "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
    GNUNET_PQ_make_execute ("SET search_path TO challenger;"),
    GNUNET_PQ_EXECUTE_STATEMENT_END
  };
#endif

  if (GNUNET_OK !=
      GNUNET_PQ_exec_statements (pq,
                                 es))
  {
    GNUNET_break (0);
    return;
  }
  CH_PG_prep_gen_++;
}


/**
 * Connect to the db if the connection does not exist yet.
 *
 * @param[in,out] pg the database state
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
internal_setup (struct CHALLENGERDB_PostgresContext *pg)
{
  struct GNUNET_PQ_Context *db_conn;

  if (NULL != pg->conn)
    return GNUNET_OK;
  db_conn = GNUNET_PQ_init (pg->cfg,
                            "challengerdb-postgres",
                            &reconnect_cb,
                            pg);
  if (NULL == db_conn)
    return GNUNET_SYSERR;
  if (0 == CH_PG_prep_gen_)
  {
    GNUNET_PQ_disconnect (db_conn);
    return GNUNET_SYSERR;
  }
  pg->conn = db_conn;
  return GNUNET_OK;
}


static struct CHALLENGERDB_PostgresContext *
do_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
            bool check_current)
{
  struct CHALLENGERDB_PostgresContext *pg;

  pg = GNUNET_new (struct CHALLENGERDB_PostgresContext);
  pg->cfg = cfg;
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_filename (cfg,
                                               "challengerdb-postgres",
                                               "SQL_DIR",
                                               &pg->sql_dir))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "challengerdb-postgres",
                               "SQL_DIR");
    GNUNET_free (pg);
    return NULL;
  }
  if (GNUNET_OK !=
      internal_setup (pg))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to ensure DB is initialized\n");
    CHALLENGERDB_disconnect (pg);
    return NULL;
  }
  if (check_current &&
      (GNUNET_OK !=
       GNUNET_PQ_check_current (pg->conn,
                                "challenger-")) )
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Database schema is not up-to-date. Try running challenger-dbinit or challenger-dbconfig!\n");
    CHALLENGERDB_disconnect (pg);
    return NULL;
  }
  return pg;
}


struct CHALLENGERDB_PostgresContext *
CHALLENGERDB_connect (
  const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  return do_connect (cfg,
                     true);
}


struct CHALLENGERDB_PostgresContext *
CHALLENGERDB_connect_admin (
  const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  return do_connect (cfg,
                     false);
}


void
CHALLENGERDB_disconnect (struct CHALLENGERDB_PostgresContext *pg)
{
  if (NULL == pg)
    return;
  if (NULL != pg->conn)
    GNUNET_PQ_disconnect (pg->conn);
  GNUNET_free (pg->sql_dir);
  GNUNET_free (pg);
}


/* end of pg.c */
