/*
     This file is part of GNUnet
     Copyright (C) 2009-2017 GNUnet e.V.

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

     GNUnet 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
     Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */

/**
 * @file datastore/plugin_datastore_postgres.c
 * @brief postgres-based datastore backend
 * @author Christian Grothoff
 */
#include "platform.h"
#include "gnunet_datastore_plugin.h"
#include "gnunet_pq_lib.h"


/**
 * After how many ms "busy" should a DB operation fail for good?
 * A low value makes sure that we are more responsive to requests
 * (especially PUTs).  A high value guarantees a higher success
 * rate (SELECTs in iterate can take several seconds despite LIMIT=1).
 *
 * The default value of 1s should ensure that users do not experience
 * huge latencies while at the same time allowing operations to succeed
 * with reasonable probability.
 */
#define BUSY_TIMEOUT GNUNET_TIME_UNIT_SECONDS


/**
 * Context for all functions in this plugin.
 */
struct Plugin
{
  /**
   * Our execution environment.
   */
  struct GNUNET_DATASTORE_PluginEnvironment *env;

  /**
   * Native Postgres database handle.
   */
  struct GNUNET_PQ_Context *dbh;
};


/**
 * @brief Get a database handle
 *
 * @param plugin global context
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
 */
static int
init_connection (struct Plugin *plugin)
{
  struct GNUNET_PQ_ExecuteStatement es[] = {
    /* FIXME: PostgreSQL does not have unsigned integers! This is ok for the type column because
     * we only test equality on it and can cast it to/from uint32_t. For repl, prio, and anonLevel
     * we do math or inequality tests, so we can't handle the entire range of uint32_t.
     * This will also cause problems for expiration times after 294247-01-10-04:00:54 UTC.
     * PostgreSQL also recommends against using WITH OIDS.
     */GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS gn090 ("
                            "  repl INTEGER NOT NULL DEFAULT 0,"
                            "  type INTEGER NOT NULL DEFAULT 0,"
                            "  prio INTEGER NOT NULL DEFAULT 0,"
                            "  anonLevel INTEGER NOT NULL DEFAULT 0,"
                            "  expire BIGINT NOT NULL DEFAULT 0,"
                            "  rvalue BIGINT NOT NULL DEFAULT 0,"
                            "  hash BYTEA NOT NULL DEFAULT '',"
                            "  vhash BYTEA NOT NULL DEFAULT '',"
                            "  value BYTEA NOT NULL DEFAULT '')"
                            "WITH OIDS"),
    GNUNET_PQ_make_try_execute (
      "CREATE INDEX IF NOT EXISTS idx_hash ON gn090 (hash)"),
    GNUNET_PQ_make_try_execute (
      "CREATE INDEX IF NOT EXISTS idx_prio ON gn090 (prio)"),
    GNUNET_PQ_make_try_execute (
      "CREATE INDEX IF NOT EXISTS idx_expire ON gn090 (expire)"),
    GNUNET_PQ_make_try_execute (
      "CREATE INDEX IF NOT EXISTS idx_prio_anon ON gn090 (prio,anonLevel)"),
    GNUNET_PQ_make_try_execute (
      "CREATE INDEX IF NOT EXISTS idx_prio_hash_anon ON gn090 (prio,hash,anonLevel)"),
    GNUNET_PQ_make_try_execute (
      "CREATE INDEX IF NOT EXISTS idx_repl_rvalue ON gn090 (repl,rvalue)"),
    GNUNET_PQ_make_try_execute (
      "CREATE INDEX IF NOT EXISTS idx_expire_hash ON gn090 (expire,hash)"),
    GNUNET_PQ_make_execute (
      "ALTER TABLE gn090 ALTER value SET STORAGE EXTERNAL"),
    GNUNET_PQ_make_execute ("ALTER TABLE gn090 ALTER hash SET STORAGE PLAIN"),
    GNUNET_PQ_make_execute ("ALTER TABLE gn090 ALTER vhash SET STORAGE PLAIN"),
    GNUNET_PQ_EXECUTE_STATEMENT_END
  };

#define RESULT_COLUMNS "repl, type, prio, anonLevel, expire, hash, value, oid"
  struct GNUNET_PQ_PreparedStatement ps[] = {
    GNUNET_PQ_make_prepare ("get",
                            "SELECT " RESULT_COLUMNS " FROM gn090"
                            " WHERE oid >= $1::bigint AND"
                            " (rvalue >= $2 OR 0 = $3::smallint) AND"
                            " (hash = $4 OR 0 = $5::smallint) AND"
                            " (type = $6 OR 0 = $7::smallint)"
                            " ORDER BY oid ASC LIMIT 1",
                            7),
    GNUNET_PQ_make_prepare ("put",
                            "INSERT INTO gn090 (repl, type, prio, anonLevel, expire, rvalue, hash, vhash, value) "
                            "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
                            9),
    GNUNET_PQ_make_prepare ("update",
                            "UPDATE gn090"
                            " SET prio = prio + $1,"
                            " repl = repl + $2,"
                            " expire = GREATEST(expire, $3)"
                            " WHERE hash = $4 AND vhash = $5",
                            5),
    GNUNET_PQ_make_prepare ("decrepl",
                            "UPDATE gn090 SET repl = GREATEST (repl - 1, 0) "
                            "WHERE oid = $1",
                            1),
    GNUNET_PQ_make_prepare ("select_non_anonymous",
                            "SELECT " RESULT_COLUMNS " FROM gn090 "
                            "WHERE anonLevel = 0 AND type = $1 AND oid >= $2::bigint "
                            "ORDER BY oid ASC LIMIT 1",
                            2),
    GNUNET_PQ_make_prepare ("select_expiration_order",
                            "(SELECT " RESULT_COLUMNS " FROM gn090 "
                            "WHERE expire < $1 ORDER BY prio ASC LIMIT 1) "
                            "UNION "
                            "(SELECT " RESULT_COLUMNS " FROM gn090 "
                            "ORDER BY prio ASC LIMIT 1) "
                            "ORDER BY expire ASC LIMIT 1",
                            1),
    GNUNET_PQ_make_prepare ("select_replication_order",
                            "SELECT " RESULT_COLUMNS " FROM gn090 "
                            "ORDER BY repl DESC,RANDOM() LIMIT 1",
                            0),
    GNUNET_PQ_make_prepare ("delrow",
                            "DELETE FROM gn090 "
                            "WHERE oid=$1",
                            1),
    GNUNET_PQ_make_prepare ("remove",
                            "DELETE FROM gn090"
                            " WHERE hash = $1 AND"
                            " value = $2",
                            2),
    GNUNET_PQ_make_prepare ("get_keys",
                            "SELECT hash FROM gn090",
                            0),
    GNUNET_PQ_make_prepare ("estimate_size",
                            "SELECT CASE WHEN NOT EXISTS"
                            "  (SELECT 1 FROM gn090)"
                            "  THEN 0"
                            "  ELSE (SELECT SUM(LENGTH(value))+256*COUNT(*) FROM gn090)"
                            "END AS total",
                            0),
    GNUNET_PQ_PREPARED_STATEMENT_END
  };
#undef RESULT_COLUMNS

  plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->env->cfg,
                                            "datastore-postgres",
                                            NULL,
                                            es,
                                            ps);
  if (NULL == plugin->dbh)
    return GNUNET_SYSERR;
  return GNUNET_OK;
}


/**
 * Get an estimate of how much space the database is
 * currently using.
 *
 * @param cls our `struct Plugin *`
 * @return number of bytes used on disk
 */
static void
postgres_plugin_estimate_size (void *cls,
                               unsigned long long *estimate)
{
  struct Plugin *plugin = cls;
  uint64_t total;
  struct GNUNET_PQ_QueryParam params[] = {
    GNUNET_PQ_query_param_end
  };
  struct GNUNET_PQ_ResultSpec rs[] = {
    GNUNET_PQ_result_spec_uint64 ("total",
                                  &total),
    GNUNET_PQ_result_spec_end
  };
  enum GNUNET_DB_QueryStatus ret;

  if (NULL == estimate)
    return;
  ret = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
                                                  "estimate_size",
                                                  params,
                                                  rs);
  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ret)
  {
    *estimate = 0LL;
    return;
  }
  *estimate = total;
}


/**
 * Store an item in the datastore.
 *
 * @param cls closure with the `struct Plugin`
 * @param key key for the item
 * @param absent true if the key was not found in the bloom filter
 * @param size number of bytes in data
 * @param data content stored
 * @param type type of the content
 * @param priority priority of the content
 * @param anonymity anonymity-level for the content
 * @param replication replication-level for the content
 * @param expiration expiration time for the content
 * @param cont continuation called with success or failure status
 * @param cont_cls continuation closure
 */
static void
postgres_plugin_put (void *cls,
                     const struct GNUNET_HashCode *key,
                     bool absent,
                     uint32_t size,
                     const void *data,
                     enum GNUNET_BLOCK_Type type,
                     uint32_t priority,
                     uint32_t anonymity,
                     uint32_t replication,
                     struct GNUNET_TIME_Absolute expiration,
                     PluginPutCont cont,
                     void *cont_cls)
{
  struct Plugin *plugin = cls;
  struct GNUNET_HashCode vhash;
  enum GNUNET_DB_QueryStatus ret;

  GNUNET_CRYPTO_hash (data,
                      size,
                      &vhash);
  if (! absent)
  {
    struct GNUNET_PQ_QueryParam params[] = {
      GNUNET_PQ_query_param_uint32 (&priority),
      GNUNET_PQ_query_param_uint32 (&replication),
      GNUNET_PQ_query_param_absolute_time (&expiration),
      GNUNET_PQ_query_param_auto_from_type (key),
      GNUNET_PQ_query_param_auto_from_type (&vhash),
      GNUNET_PQ_query_param_end
    };
    ret = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
                                              "update",
                                              params);
    if (0 > ret)
    {
      cont (cont_cls,
            key,
            size,
            GNUNET_SYSERR,
            _ ("Postgress exec failure"));
      return;
    }
    bool affected = (0 != ret);
    if (affected)
    {
      cont (cont_cls,
            key,
            size,
            GNUNET_NO,
            NULL);
      return;
    }
  }

  {
    uint32_t utype = (uint32_t) type;
    uint64_t rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
                                                UINT64_MAX);
    struct GNUNET_PQ_QueryParam params[] = {
      GNUNET_PQ_query_param_uint32 (&replication),
      GNUNET_PQ_query_param_uint32 (&utype),
      GNUNET_PQ_query_param_uint32 (&priority),
      GNUNET_PQ_query_param_uint32 (&anonymity),
      GNUNET_PQ_query_param_absolute_time (&expiration),
      GNUNET_PQ_query_param_uint64 (&rvalue),
      GNUNET_PQ_query_param_auto_from_type (key),
      GNUNET_PQ_query_param_auto_from_type (&vhash),
      GNUNET_PQ_query_param_fixed_size (data, size),
      GNUNET_PQ_query_param_end
    };

    ret = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
                                              "put",
                                              params);
    if (0 > ret)
    {
      cont (cont_cls,
            key,
            size,
            GNUNET_SYSERR,
            "Postgress exec failure");
      return;
    }
  }
  plugin->env->duc (plugin->env->cls,
                    size + GNUNET_DATASTORE_ENTRY_OVERHEAD);
  GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
                   "datastore-postgres",
                   "Stored %u bytes in database\n",
                   (unsigned int) size);
  cont (cont_cls,
        key,
        size,
        GNUNET_OK,
        NULL);
}


/**
 * Closure for #process_result.
 */
struct ProcessResultContext
{
  /**
   * The plugin handle.
   */
  struct Plugin *plugin;

  /**
   * Function to call on each result.
   */
  PluginDatumProcessor proc;

  /**
   * Closure for @e proc.
   */
  void *proc_cls;
};


/**
 * Function invoked to process the result and call the processor of @a
 * cls.
 *
 * @param cls our `struct ProcessResultContext`
 * @param res result from exec
 * @param num_results number of results in @a res
 */
static void
process_result (void *cls,
                PGresult *res,
                unsigned int num_results)
{
  struct ProcessResultContext *prc = cls;
  struct Plugin *plugin = prc->plugin;

  if (0 == num_results)
  {
    /* no result */
    GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
                     "datastore-postgres",
                     "Ending iteration (no more results)\n");
    prc->proc (prc->proc_cls, NULL, 0, NULL, 0, 0, 0, 0,
               GNUNET_TIME_UNIT_ZERO_ABS, 0);
    return;
  }
  if (1 != num_results)
  {
    GNUNET_break (0);
    prc->proc (prc->proc_cls, NULL, 0, NULL, 0, 0, 0, 0,
               GNUNET_TIME_UNIT_ZERO_ABS, 0);
    return;
  }
  /* Technically we don't need the loop here, but nicer in case
     we ever relax the condition above. */
  for (unsigned int i = 0; i < num_results; i++)
  {
    int iret;
    uint32_t rowid;
    uint32_t utype;
    uint32_t anonymity;
    uint32_t replication;
    uint32_t priority;
    size_t size;
    void *data;
    struct GNUNET_TIME_Absolute expiration_time;
    struct GNUNET_HashCode key;
    struct GNUNET_PQ_ResultSpec rs[] = {
      GNUNET_PQ_result_spec_uint32 ("repl", &replication),
      GNUNET_PQ_result_spec_uint32 ("type", &utype),
      GNUNET_PQ_result_spec_uint32 ("prio", &priority),
      GNUNET_PQ_result_spec_uint32 ("anonLevel", &anonymity),
      GNUNET_PQ_result_spec_absolute_time ("expire", &expiration_time),
      GNUNET_PQ_result_spec_auto_from_type ("hash", &key),
      GNUNET_PQ_result_spec_variable_size ("value", &data, &size),
      GNUNET_PQ_result_spec_uint32 ("oid", &rowid),
      GNUNET_PQ_result_spec_end
    };

    if (GNUNET_OK !=
        GNUNET_PQ_extract_result (res,
                                  rs,
                                  i))
    {
      GNUNET_break (0);
      prc->proc (prc->proc_cls, NULL, 0, NULL, 0, 0, 0, 0,
                 GNUNET_TIME_UNIT_ZERO_ABS, 0);
      return;
    }

    GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
                     "datastore-postgres",
                     "Found result of size %u bytes and type %u in database\n",
                     (unsigned int) size,
                     (unsigned int) utype);
    iret = prc->proc (prc->proc_cls,
                      &key,
                      size,
                      data,
                      (enum GNUNET_BLOCK_Type) utype,
                      priority,
                      anonymity,
                      replication,
                      expiration_time,
                      rowid);
    if (iret == GNUNET_NO)
    {
      struct GNUNET_PQ_QueryParam param[] = {
        GNUNET_PQ_query_param_uint32 (&rowid),
        GNUNET_PQ_query_param_end
      };

      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Processor asked for item %u to be removed.\n",
                  (unsigned int) rowid);
      if (0 <
          GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
                                              "delrow",
                                              param))
      {
        GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
                         "datastore-postgres",
                         "Deleting %u bytes from database\n",
                         (unsigned int) size);
        plugin->env->duc (plugin->env->cls,
                          -(size + GNUNET_DATASTORE_ENTRY_OVERHEAD));
        GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
                         "datastore-postgres",
                         "Deleted %u bytes from database\n",
                         (unsigned int) size);
      }
    }
    GNUNET_PQ_cleanup_result (rs);
  }   /* for (i) */
}


/**
 * Get one of the results for a particular key in the datastore.
 *
 * @param cls closure with the `struct Plugin`
 * @param next_uid return the result with lowest uid >= next_uid
 * @param random if true, return a random result instead of using next_uid
 * @param key maybe NULL (to match all entries)
 * @param type entries of which type are relevant?
 *     Use 0 for any type.
 * @param proc function to call on the matching value;
 *        will be called with NULL if nothing matches
 * @param proc_cls closure for @a proc
 */
static void
postgres_plugin_get_key (void *cls,
                         uint64_t next_uid,
                         bool random,
                         const struct GNUNET_HashCode *key,
                         enum GNUNET_BLOCK_Type type,
                         PluginDatumProcessor proc,
                         void *proc_cls)
{
  struct Plugin *plugin = cls;
  uint32_t utype = type;
  uint16_t use_rvalue = random;
  uint16_t use_key = NULL != key;
  uint16_t use_type = GNUNET_BLOCK_TYPE_ANY != type;
  uint64_t rvalue;
  struct GNUNET_PQ_QueryParam params[] = {
    GNUNET_PQ_query_param_uint64 (&next_uid),
    GNUNET_PQ_query_param_uint64 (&rvalue),
    GNUNET_PQ_query_param_uint16 (&use_rvalue),
    GNUNET_PQ_query_param_auto_from_type (key),
    GNUNET_PQ_query_param_uint16 (&use_key),
    GNUNET_PQ_query_param_uint32 (&utype),
    GNUNET_PQ_query_param_uint16 (&use_type),
    GNUNET_PQ_query_param_end
  };
  struct ProcessResultContext prc;
  enum GNUNET_DB_QueryStatus res;

  if (random)
  {
    rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
                                       UINT64_MAX);
    next_uid = 0;
  }
  else
  {
    rvalue = 0;
  }
  prc.plugin = plugin;
  prc.proc = proc;
  prc.proc_cls = proc_cls;

  res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
                                              "get",
                                              params,
                                              &process_result,
                                              &prc);
  if (0 > res)
    proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0,
          GNUNET_TIME_UNIT_ZERO_ABS, 0);
}


/**
 * Select a subset of the items in the datastore and call
 * the given iterator for each of them.
 *
 * @param cls our `struct Plugin *`
 * @param next_uid return the result with lowest uid >= next_uid
 * @param type entries of which type should be considered?
 *        Must not be zero (ANY).
 * @param proc function to call on the matching value;
 *        will be called with NULL if no value matches
 * @param proc_cls closure for @a proc
 */
static void
postgres_plugin_get_zero_anonymity (void *cls,
                                    uint64_t next_uid,
                                    enum GNUNET_BLOCK_Type type,
                                    PluginDatumProcessor proc,
                                    void *proc_cls)
{
  struct Plugin *plugin = cls;
  uint32_t utype = type;
  struct GNUNET_PQ_QueryParam params[] = {
    GNUNET_PQ_query_param_uint32 (&utype),
    GNUNET_PQ_query_param_uint64 (&next_uid),
    GNUNET_PQ_query_param_end
  };
  struct ProcessResultContext prc;
  enum GNUNET_DB_QueryStatus res;

  prc.plugin = plugin;
  prc.proc = proc;
  prc.proc_cls = proc_cls;
  res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
                                              "select_non_anonymous",
                                              params,
                                              &process_result,
                                              &prc);
  if (0 > res)
    proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0,
          GNUNET_TIME_UNIT_ZERO_ABS, 0);
}


/**
 * Context for #repl_iter() function.
 */
struct ReplCtx
{
  /**
   * Plugin handle.
   */
  struct Plugin *plugin;

  /**
   * Function to call for the result (or the NULL).
   */
  PluginDatumProcessor proc;

  /**
   * Closure for @e proc.
   */
  void *proc_cls;
};


/**
 * Wrapper for the iterator for 'sqlite_plugin_replication_get'.
 * Decrements the replication counter and calls the original
 * iterator.
 *
 * @param cls closure with the `struct ReplCtx *`
 * @param key key for the content
 * @param size number of bytes in @a data
 * @param data content stored
 * @param type type of the content
 * @param priority priority of the content
 * @param anonymity anonymity-level for the content
 * @param replication replication-level for the content
 * @param expiration expiration time for the content
 * @param uid unique identifier for the datum;
 *        maybe 0 if no unique identifier is available
 * @return #GNUNET_SYSERR to abort the iteration,
 *         #GNUNET_OK to continue
 *         (continue on call to "next", of course),
 *         #GNUNET_NO to delete the item and continue (if supported)
 */
static int
repl_proc (void *cls,
           const struct GNUNET_HashCode *key,
           uint32_t size,
           const void *data,
           enum GNUNET_BLOCK_Type type,
           uint32_t priority,
           uint32_t anonymity,
           uint32_t replication,
           struct GNUNET_TIME_Absolute expiration,
           uint64_t uid)
{
  struct ReplCtx *rc = cls;
  struct Plugin *plugin = rc->plugin;
  int ret;
  uint32_t oid = (uint32_t) uid;
  struct GNUNET_PQ_QueryParam params[] = {
    GNUNET_PQ_query_param_uint32 (&oid),
    GNUNET_PQ_query_param_end
  };
  enum GNUNET_DB_QueryStatus qret;

  ret = rc->proc (rc->proc_cls,
                  key,
                  size,
                  data,
                  type,
                  priority,
                  anonymity,
                  replication,
                  expiration,
                  uid);
  if (NULL == key)
    return ret;
  qret = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
                                             "decrepl",
                                             params);
  if (0 > qret)
    return GNUNET_SYSERR;
  return ret;
}


/**
 * Get a random item for replication.  Returns a single, not expired,
 * random item from those with the highest replication counters.  The
 * item's replication counter is decremented by one IF it was positive
 * before.  Call @a proc with all values ZERO or NULL if the datastore
 * is empty.
 *
 * @param cls closure with the `struct Plugin`
 * @param proc function to call the value (once only).
 * @param proc_cls closure for @a proc
 */
static void
postgres_plugin_get_replication (void *cls,
                                 PluginDatumProcessor proc,
                                 void *proc_cls)
{
  struct Plugin *plugin = cls;
  struct GNUNET_PQ_QueryParam params[] = {
    GNUNET_PQ_query_param_end
  };
  struct ReplCtx rc;
  struct ProcessResultContext prc;
  enum GNUNET_DB_QueryStatus res;

  rc.plugin = plugin;
  rc.proc = proc;
  rc.proc_cls = proc_cls;
  prc.plugin = plugin;
  prc.proc = &repl_proc;
  prc.proc_cls = &rc;
  res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
                                              "select_replication_order",
                                              params,
                                              &process_result,
                                              &prc);
  if (0 > res)
    proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0,
          GNUNET_TIME_UNIT_ZERO_ABS, 0);
}


/**
 * Get a random item for expiration.  Call @a proc with all values
 * ZERO or NULL if the datastore is empty.
 *
 * @param cls closure with the `struct Plugin`
 * @param proc function to call the value (once only).
 * @param proc_cls closure for @a proc
 */
static void
postgres_plugin_get_expiration (void *cls,
                                PluginDatumProcessor proc,
                                void *proc_cls)
{
  struct Plugin *plugin = cls;
  struct GNUNET_TIME_Absolute now;
  struct GNUNET_PQ_QueryParam params[] = {
    GNUNET_PQ_query_param_absolute_time (&now),
    GNUNET_PQ_query_param_end
  };
  struct ProcessResultContext prc;

  now = GNUNET_TIME_absolute_get ();
  prc.plugin = plugin;
  prc.proc = proc;
  prc.proc_cls = proc_cls;
  (void) GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
                                               "select_expiration_order",
                                               params,
                                               &process_result,
                                               &prc);
}


/**
 * Closure for #process_keys.
 */
struct ProcessKeysContext
{
  /**
   * Function to call for each key.
   */
  PluginKeyProcessor proc;

  /**
   * Closure for @e proc.
   */
  void *proc_cls;
};


/**
 * Function to be called with the results of a SELECT statement
 * that has returned @a num_results results.
 *
 * @param cls closure with a `struct ProcessKeysContext`
 * @param result the postgres result
 * @param num_result the number of results in @a result
 */
static void
process_keys (void *cls,
              PGresult *result,
              unsigned int num_results)
{
  struct ProcessKeysContext *pkc = cls;

  for (unsigned i = 0; i < num_results; i++)
  {
    struct GNUNET_HashCode key;
    struct GNUNET_PQ_ResultSpec rs[] = {
      GNUNET_PQ_result_spec_auto_from_type ("hash",
                                            &key),
      GNUNET_PQ_result_spec_end
    };

    if (GNUNET_OK !=
        GNUNET_PQ_extract_result (result,
                                  rs,
                                  i))
    {
      GNUNET_break (0);
      continue;
    }
    pkc->proc (pkc->proc_cls,
               &key,
               1);
    GNUNET_PQ_cleanup_result (rs);
  }
}


/**
 * Get all of the keys in the datastore.
 *
 * @param cls closure with the `struct Plugin *`
 * @param proc function to call on each key
 * @param proc_cls closure for @a proc
 */
static void
postgres_plugin_get_keys (void *cls,
                          PluginKeyProcessor proc,
                          void *proc_cls)
{
  struct Plugin *plugin = cls;
  struct GNUNET_PQ_QueryParam params[] = {
    GNUNET_PQ_query_param_end
  };
  struct ProcessKeysContext pkc;

  pkc.proc = proc;
  pkc.proc_cls = proc_cls;
  (void) GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
                                               "get_keys",
                                               params,
                                               &process_keys,
                                               &pkc);
  proc (proc_cls,
        NULL,
        0);
}


/**
 * Drop database.
 *
 * @param cls closure with the `struct Plugin *`
 */
static void
postgres_plugin_drop (void *cls)
{
  struct Plugin *plugin = cls;
  struct GNUNET_PQ_ExecuteStatement es[] = {
    GNUNET_PQ_make_execute ("DROP TABLE gn090"),
    GNUNET_PQ_EXECUTE_STATEMENT_END
  };

  if (GNUNET_OK !=
      GNUNET_PQ_exec_statements (plugin->dbh,
                                 es))
    GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING,
                     "postgres",
                     _ ("Failed to drop table from database.\n"));
}


/**
 * Remove a particular key in the datastore.
 *
 * @param cls closure
 * @param key key for the content
 * @param size number of bytes in data
 * @param data content stored
 * @param cont continuation called with success or failure status
 * @param cont_cls continuation closure for @a cont
 */
static void
postgres_plugin_remove_key (void *cls,
                            const struct GNUNET_HashCode *key,
                            uint32_t size,
                            const void *data,
                            PluginRemoveCont cont,
                            void *cont_cls)
{
  struct Plugin *plugin = cls;
  enum GNUNET_DB_QueryStatus ret;
  struct GNUNET_PQ_QueryParam params[] = {
    GNUNET_PQ_query_param_auto_from_type (key),
    GNUNET_PQ_query_param_fixed_size (data, size),
    GNUNET_PQ_query_param_end
  };

  ret = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
                                            "remove",
                                            params);
  if (0 > ret)
  {
    cont (cont_cls,
          key,
          size,
          GNUNET_SYSERR,
          _ ("Postgress exec failure"));
    return;
  }
  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == ret)
  {
    cont (cont_cls,
          key,
          size,
          GNUNET_NO,
          NULL);
    return;
  }
  plugin->env->duc (plugin->env->cls,
                    -(size + GNUNET_DATASTORE_ENTRY_OVERHEAD));
  GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
                   "datastore-postgres",
                   "Deleted %u bytes from database\n",
                   (unsigned int) size);
  cont (cont_cls,
        key,
        size,
        GNUNET_OK,
        NULL);
}


/**
 * Entry point for the plugin.
 *
 * @param cls the `struct GNUNET_DATASTORE_PluginEnvironment*`
 * @return our `struct Plugin *`
 */
void *
libgnunet_plugin_datastore_postgres_init (void *cls)
{
  struct GNUNET_DATASTORE_PluginEnvironment *env = cls;
  struct GNUNET_DATASTORE_PluginFunctions *api;
  struct Plugin *plugin;

  plugin = GNUNET_new (struct Plugin);
  plugin->env = env;
  if (GNUNET_OK != init_connection (plugin))
  {
    GNUNET_free (plugin);
    return NULL;
  }
  api = GNUNET_new (struct GNUNET_DATASTORE_PluginFunctions);
  api->cls = plugin;
  api->estimate_size = &postgres_plugin_estimate_size;
  api->put = &postgres_plugin_put;
  api->get_key = &postgres_plugin_get_key;
  api->get_replication = &postgres_plugin_get_replication;
  api->get_expiration = &postgres_plugin_get_expiration;
  api->get_zero_anonymity = &postgres_plugin_get_zero_anonymity;
  api->get_keys = &postgres_plugin_get_keys;
  api->drop = &postgres_plugin_drop;
  api->remove_key = &postgres_plugin_remove_key;
  GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
                   "datastore-postgres",
                   _ ("Postgres database running\n"));
  return api;
}


/**
 * Exit point from the plugin.
 *
 * @param cls our `struct Plugin *`
 * @return always NULL
 */
void *
libgnunet_plugin_datastore_postgres_done (void *cls)
{
  struct GNUNET_DATASTORE_PluginFunctions *api = cls;
  struct Plugin *plugin = api->cls;

  GNUNET_PQ_disconnect (plugin->dbh);
  GNUNET_free (plugin);
  GNUNET_free (api);
  return NULL;
}


/* end of plugin_datastore_postgres.c */
