/*
   This file is part of TALER
   Copyright (C) 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 pg_get_coin_transactions.c
 * @brief Low-level (statement-level) Postgres database access for the exchange
 * @author Christian Grothoff
 */
#include "platform.h"
#include "taler_error_codes.h"
#include "taler_dbevents.h"
#include "taler_pq_lib.h"
#include "pg_get_coin_transactions.h"
#include "pg_helper.h"
#include "plugin_exchangedb_common.h"
/**
 * Closure for callbacks called from #postgres_get_coin_transactions()
 */
struct CoinHistoryContext
{
  /**
   * Head of the coin's history list.
   */
  struct TALER_EXCHANGEDB_TransactionList *head;
  /**
   * Public key of the coin we are building the history for.
   */
  const struct TALER_CoinSpendPublicKeyP *coin_pub;
  /**
   * Closure for all callbacks of this database plugin.
   */
  void *db_cls;
  /**
   * Plugin context.
   */
  struct PostgresClosure *pg;
  /**
   * Set to 'true' if the transaction failed.
   */
  bool failed;
  /**
   * Set to 'true' if we found a deposit or melt (for invariant check).
   */
  bool have_deposit_or_melt;
};
/**
 * Function to be called with the results of a SELECT statement
 * that has returned @a num_results results.
 *
 * @param cls closure of type `struct CoinHistoryContext`
 * @param result the postgres result
 * @param num_results the number of results in @a result
 */
static void
add_coin_deposit (void *cls,
                  PGresult *result,
                  unsigned int num_results)
{
  struct CoinHistoryContext *chc = cls;
  struct PostgresClosure *pg = chc->pg;
  for (unsigned int i = 0; i < num_results; i++)
  {
    struct TALER_EXCHANGEDB_DepositListEntry *deposit;
    struct TALER_EXCHANGEDB_TransactionList *tl;
    uint64_t serial_id;
    chc->have_deposit_or_melt = true;
    deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry);
    {
      struct GNUNET_PQ_ResultSpec rs[] = {
        TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
                                     &deposit->amount_with_fee),
        TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
                                     &deposit->deposit_fee),
        GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
                                              &deposit->h_denom_pub),
        GNUNET_PQ_result_spec_allow_null (
          GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
                                                &deposit->h_age_commitment),
          &deposit->no_age_commitment),
        GNUNET_PQ_result_spec_timestamp ("wallet_timestamp",
                                         &deposit->timestamp),
        GNUNET_PQ_result_spec_timestamp ("refund_deadline",
                                         &deposit->refund_deadline),
        GNUNET_PQ_result_spec_timestamp ("wire_deadline",
                                         &deposit->wire_deadline),
        GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
                                              &deposit->merchant_pub),
        GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
                                              &deposit->h_contract_terms),
        GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
                                              &deposit->wire_salt),
        GNUNET_PQ_result_spec_string ("payto_uri",
                                      &deposit->receiver_wire_account),
        GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
                                              &deposit->csig),
        GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
                                      &serial_id),
        GNUNET_PQ_result_spec_auto_from_type ("done",
                                              &deposit->done),
        GNUNET_PQ_result_spec_end
      };
      if (GNUNET_OK !=
          GNUNET_PQ_extract_result (result,
                                    rs,
                                    i))
      {
        GNUNET_break (0);
        GNUNET_free (deposit);
        chc->failed = true;
        return;
      }
    }
    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    tl->next = chc->head;
    tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
    tl->details.deposit = deposit;
    tl->serial_id = serial_id;
    chc->head = tl;
  }
}
/**
 * Function to be called with the results of a SELECT statement
 * that has returned @a num_results results.
 *
 * @param cls closure of type `struct CoinHistoryContext`
 * @param result the postgres result
 * @param num_results the number of results in @a result
 */
static void
add_coin_purse_deposit (void *cls,
                        PGresult *result,
                        unsigned int num_results)
{
  struct CoinHistoryContext *chc = cls;
  struct PostgresClosure *pg = chc->pg;
  for (unsigned int i = 0; i < num_results; i++)
  {
    struct TALER_EXCHANGEDB_PurseDepositListEntry *deposit;
    struct TALER_EXCHANGEDB_TransactionList *tl;
    uint64_t serial_id;
    chc->have_deposit_or_melt = true;
    deposit = GNUNET_new (struct TALER_EXCHANGEDB_PurseDepositListEntry);
    {
      bool not_finished;
      struct GNUNET_PQ_ResultSpec rs[] = {
        TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
                                     &deposit->amount),
        TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
                                     &deposit->deposit_fee),
        GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
                                              &deposit->purse_pub),
        GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id",
                                      &serial_id),
        GNUNET_PQ_result_spec_allow_null (
          GNUNET_PQ_result_spec_string ("partner_base_url",
                                        &deposit->exchange_base_url),
          NULL),
        GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
                                              &deposit->coin_sig),
        GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
                                              &deposit->h_age_commitment),
        GNUNET_PQ_result_spec_allow_null (
          GNUNET_PQ_result_spec_bool ("refunded",
                                      &deposit->refunded),
          ¬_finished),
        GNUNET_PQ_result_spec_end
      };
      if (GNUNET_OK !=
          GNUNET_PQ_extract_result (result,
                                    rs,
                                    i))
      {
        GNUNET_break (0);
        GNUNET_free (deposit);
        chc->failed = true;
        return;
      }
      if (not_finished)
        deposit->refunded = false;
      deposit->no_age_commitment = GNUNET_is_zero (&deposit->h_age_commitment);
    }
    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    tl->next = chc->head;
    tl->type = TALER_EXCHANGEDB_TT_PURSE_DEPOSIT;
    tl->details.purse_deposit = deposit;
    tl->serial_id = serial_id;
    chc->head = tl;
  }
}
/**
 * Function to be called with the results of a SELECT statement
 * that has returned @a num_results results.
 *
 * @param cls closure of type `struct CoinHistoryContext`
 * @param result the postgres result
 * @param num_results the number of results in @a result
 */
static void
add_coin_melt (void *cls,
               PGresult *result,
               unsigned int num_results)
{
  struct CoinHistoryContext *chc = cls;
  struct PostgresClosure *pg = chc->pg;
  for (unsigned int i = 0; ihave_deposit_or_melt = true;
    melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry);
    {
      struct GNUNET_PQ_ResultSpec rs[] = {
        GNUNET_PQ_result_spec_auto_from_type ("rc",
                                              &melt->rc),
        /* oldcoin_index not needed */
        GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
                                              &melt->h_denom_pub),
        GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
                                              &melt->coin_sig),
        TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
                                     &melt->amount_with_fee),
        TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
                                     &melt->melt_fee),
        GNUNET_PQ_result_spec_allow_null (
          GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
                                                &melt->h_age_commitment),
          &melt->no_age_commitment),
        GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
                                      &serial_id),
        GNUNET_PQ_result_spec_end
      };
      if (GNUNET_OK !=
          GNUNET_PQ_extract_result (result,
                                    rs,
                                    i))
      {
        GNUNET_break (0);
        GNUNET_free (melt);
        chc->failed = true;
        return;
      }
    }
    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    tl->next = chc->head;
    tl->type = TALER_EXCHANGEDB_TT_MELT;
    tl->details.melt = melt;
    tl->serial_id = serial_id;
    chc->head = tl;
  }
}
/**
 * Function to be called with the results of a SELECT statement
 * that has returned @a num_results results.
 *
 * @param cls closure of type `struct CoinHistoryContext`
 * @param result the postgres result
 * @param num_results the number of results in @a result
 */
static void
add_coin_refund (void *cls,
                 PGresult *result,
                 unsigned int num_results)
{
  struct CoinHistoryContext *chc = cls;
  struct PostgresClosure *pg = chc->pg;
  for (unsigned int i = 0; imerchant_pub),
        GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
                                              &refund->merchant_sig),
        GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
                                              &refund->h_contract_terms),
        GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
                                      &refund->rtransaction_id),
        TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
                                     &refund->refund_amount),
        TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
                                     &refund->refund_fee),
        GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
                                      &serial_id),
        GNUNET_PQ_result_spec_end
      };
      if (GNUNET_OK !=
          GNUNET_PQ_extract_result (result,
                                    rs,
                                    i))
      {
        GNUNET_break (0);
        GNUNET_free (refund);
        chc->failed = true;
        return;
      }
    }
    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    tl->next = chc->head;
    tl->type = TALER_EXCHANGEDB_TT_REFUND;
    tl->details.refund = refund;
    tl->serial_id = serial_id;
    chc->head = tl;
  }
}
/**
 * Function to be called with the results of a SELECT statement
 * that has returned @a num_results results.
 *
 * @param cls closure of type `struct CoinHistoryContext`
 * @param result the postgres result
 * @param num_results the number of results in @a result
 */
static void
add_coin_purse_decision (void *cls,
                         PGresult *result,
                         unsigned int num_results)
{
  struct CoinHistoryContext *chc = cls;
  struct PostgresClosure *pg = chc->pg;
  for (unsigned int i = 0; ipurse_pub),
        TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
                                     &prefund->refund_amount),
        TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
                                     &prefund->refund_fee),
        GNUNET_PQ_result_spec_uint64 ("purse_decision_serial_id",
                                      &serial_id),
        GNUNET_PQ_result_spec_end
      };
      if (GNUNET_OK !=
          GNUNET_PQ_extract_result (result,
                                    rs,
                                    i))
      {
        GNUNET_break (0);
        GNUNET_free (prefund);
        chc->failed = true;
        return;
      }
    }
    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    tl->next = chc->head;
    tl->type = TALER_EXCHANGEDB_TT_PURSE_REFUND;
    tl->details.purse_refund = prefund;
    tl->serial_id = serial_id;
    chc->head = tl;
  }
}
/**
 * Function to be called with the results of a SELECT statement
 * that has returned @a num_results results.
 *
 * @param cls closure of type `struct CoinHistoryContext`
 * @param result the postgres result
 * @param num_results the number of results in @a result
 */
static void
add_old_coin_recoup (void *cls,
                     PGresult *result,
                     unsigned int num_results)
{
  struct CoinHistoryContext *chc = cls;
  struct PostgresClosure *pg = chc->pg;
  for (unsigned int i = 0; icoin.coin_pub),
        GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
                                              &recoup->coin_sig),
        GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
                                              &recoup->coin_blind),
        TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
                                     &recoup->value),
        GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
                                         &recoup->timestamp),
        GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
                                              &recoup->coin.denom_pub_hash),
        TALER_PQ_result_spec_denom_sig ("denom_sig",
                                        &recoup->coin.denom_sig),
        GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
                                      &serial_id),
        GNUNET_PQ_result_spec_end
      };
      if (GNUNET_OK !=
          GNUNET_PQ_extract_result (result,
                                    rs,
                                    i))
      {
        GNUNET_break (0);
        GNUNET_free (recoup);
        chc->failed = true;
        return;
      }
      recoup->old_coin_pub = *chc->coin_pub;
    }
    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    tl->next = chc->head;
    tl->type = TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP;
    tl->details.old_coin_recoup = recoup;
    tl->serial_id = serial_id;
    chc->head = tl;
  }
}
/**
 * Function to be called with the results of a SELECT statement
 * that has returned @a num_results results.
 *
 * @param cls closure of type `struct CoinHistoryContext`
 * @param result the postgres result
 * @param num_results the number of results in @a result
 */
static void
add_coin_recoup (void *cls,
                 PGresult *result,
                 unsigned int num_results)
{
  struct CoinHistoryContext *chc = cls;
  struct PostgresClosure *pg = chc->pg;
  for (unsigned int i = 0; ireserve_pub),
        GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
                                              &recoup->coin_sig),
        GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
                                              &recoup->h_denom_pub),
        GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
                                              &recoup->coin_blind),
        TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
                                     &recoup->value),
        GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
                                         &recoup->timestamp),
        GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
                                      &serial_id),
        GNUNET_PQ_result_spec_end
      };
      if (GNUNET_OK !=
          GNUNET_PQ_extract_result (result,
                                    rs,
                                    i))
      {
        GNUNET_break (0);
        GNUNET_free (recoup);
        chc->failed = true;
        return;
      }
    }
    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    tl->next = chc->head;
    tl->type = TALER_EXCHANGEDB_TT_RECOUP;
    tl->details.recoup = recoup;
    tl->serial_id = serial_id;
    chc->head = tl;
  }
}
/**
 * Function to be called with the results of a SELECT statement
 * that has returned @a num_results results.
 *
 * @param cls closure of type `struct CoinHistoryContext`
 * @param result the postgres result
 * @param num_results the number of results in @a result
 */
static void
add_coin_recoup_refresh (void *cls,
                         PGresult *result,
                         unsigned int num_results)
{
  struct CoinHistoryContext *chc = cls;
  struct PostgresClosure *pg = chc->pg;
  for (unsigned int i = 0; iold_coin_pub),
        GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
                                              &recoup->coin_sig),
        GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
                                              &recoup->coin_blind),
        TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
                                     &recoup->value),
        GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
                                         &recoup->timestamp),
        GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
                                              &recoup->coin.denom_pub_hash),
        TALER_PQ_result_spec_denom_sig ("denom_sig",
                                        &recoup->coin.denom_sig),
        GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
                                      &serial_id),
        GNUNET_PQ_result_spec_end
      };
      if (GNUNET_OK !=
          GNUNET_PQ_extract_result (result,
                                    rs,
                                    i))
      {
        GNUNET_break (0);
        GNUNET_free (recoup);
        chc->failed = true;
        return;
      }
      recoup->coin.coin_pub = *chc->coin_pub;
    }
    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    tl->next = chc->head;
    tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH;
    tl->details.recoup_refresh = recoup;
    tl->serial_id = serial_id;
    chc->head = tl;
  }
}
/**
 * Function to be called with the results of a SELECT statement
 * that has returned @a num_results results.
 *
 * @param cls closure of type `struct CoinHistoryContext`
 * @param result the postgres result
 * @param num_results the number of results in @a result
 */
static void
add_coin_reserve_open (void *cls,
                       PGresult *result,
                       unsigned int num_results)
{
  struct CoinHistoryContext *chc = cls;
  struct PostgresClosure *pg = chc->pg;
  for (unsigned int i = 0; ireserve_sig),
        GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
                                              &role->coin_sig),
        TALER_PQ_RESULT_SPEC_AMOUNT ("contribution",
                                     &role->coin_contribution),
        GNUNET_PQ_result_spec_uint64 ("reserve_open_deposit_uuid",
                                      &serial_id),
        GNUNET_PQ_result_spec_end
      };
      if (GNUNET_OK !=
          GNUNET_PQ_extract_result (result,
                                    rs,
                                    i))
      {
        GNUNET_break (0);
        GNUNET_free (role);
        chc->failed = true;
        return;
      }
    }
    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    tl->next = chc->head;
    tl->type = TALER_EXCHANGEDB_TT_RESERVE_OPEN;
    tl->details.reserve_open = role;
    tl->serial_id = serial_id;
    chc->head = tl;
  }
}
/**
 * Work we need to do.
 */
struct Work
{
  /**
   * SQL prepared statement name.
   */
  const char *statement;
  /**
   * Function to call to handle the result(s).
   */
  GNUNET_PQ_PostgresResultHandler cb;
};
enum GNUNET_DB_QueryStatus
TEH_PG_get_coin_transactions (
  void *cls,
  const struct TALER_CoinSpendPublicKeyP *coin_pub,
  struct TALER_EXCHANGEDB_TransactionList **tlp)
{
  struct PostgresClosure *pg = cls;
  static const struct Work work[] = {
    /** #TALER_EXCHANGEDB_TT_DEPOSIT */
    { "get_deposit_with_coin_pub",
      &add_coin_deposit },
    /** #TALER_EXCHANGEDB_TT_MELT */
    { "get_refresh_session_by_coin",
      &add_coin_melt },
    /** #TALER_EXCHANGEDB_TT_PURSE_DEPOSIT */
    { "get_purse_deposit_by_coin_pub",
      &add_coin_purse_deposit },
    /** #TALER_EXCHANGEDB_TT_PURSE_REFUND */
    { "get_purse_decision_by_coin_pub",
      &add_coin_purse_decision },
    /** #TALER_EXCHANGEDB_TT_REFUND */
    { "get_refunds_by_coin",
      &add_coin_refund },
    /** #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP */
    { "recoup_by_old_coin",
      &add_old_coin_recoup },
    /** #TALER_EXCHANGEDB_TT_RECOUP */
    { "recoup_by_coin",
      &add_coin_recoup },
    /** #TALER_EXCHANGEDB_TT_RECOUP_REFRESH */
    { "recoup_by_refreshed_coin",
      &add_coin_recoup_refresh },
    /** #TALER_EXCHANGEDB_TT_RESERVE_OPEN */
    { "reserve_open_by_coin",
      &add_coin_reserve_open },
    { NULL, NULL }
  };
  struct GNUNET_PQ_QueryParam params[] = {
    GNUNET_PQ_query_param_auto_from_type (coin_pub),
    GNUNET_PQ_query_param_end
  };
  enum GNUNET_DB_QueryStatus qs;
  struct CoinHistoryContext chc = {
    .head = NULL,
    .coin_pub = coin_pub,
    .pg = pg,
    .db_cls = cls
  };
  PREPARE (pg,
           "get_deposit_with_coin_pub",
           "SELECT"
           " dep.amount_with_fee_val"
           ",dep.amount_with_fee_frac"
           ",denoms.fee_deposit_val"
           ",denoms.fee_deposit_frac"
           ",denoms.denom_pub_hash"
           ",kc.age_commitment_hash"
           ",dep.wallet_timestamp"
           ",dep.refund_deadline"
           ",dep.wire_deadline"
           ",dep.merchant_pub"
           ",dep.h_contract_terms"
           ",dep.wire_salt"
           ",wt.payto_uri"
           ",dep.coin_sig"
           ",dep.deposit_serial_id"
           ",dep.done"
           " FROM deposits dep"
           "    JOIN wire_targets wt"
           "      USING (wire_target_h_payto)"
           "    JOIN known_coins kc"
           "      ON (kc.coin_pub = dep.coin_pub)"
           "    JOIN denominations denoms"
           "      USING (denominations_serial)"
           " WHERE dep.coin_pub=$1;");
  PREPARE (pg,
           "get_refresh_session_by_coin",
           "SELECT"
           " rc"
           ",old_coin_sig"
           ",amount_with_fee_val"
           ",amount_with_fee_frac"
           ",denoms.denom_pub_hash"
           ",denoms.fee_refresh_val"
           ",denoms.fee_refresh_frac"
           ",kc.age_commitment_hash"
           ",melt_serial_id"
           " FROM refresh_commitments"
           " JOIN known_coins kc"
           "   ON (refresh_commitments.old_coin_pub = kc.coin_pub)"
           " JOIN denominations denoms"
           "   USING (denominations_serial)"
           " WHERE old_coin_pub=$1;");
  PREPARE (pg,
           "get_purse_deposit_by_coin_pub",
           "SELECT"
           " partner_base_url"
           ",pd.amount_with_fee_val"
           ",pd.amount_with_fee_frac"
           ",denoms.fee_deposit_val"
           ",denoms.fee_deposit_frac"
           ",pd.purse_pub"
           ",kc.age_commitment_hash"
           ",pd.coin_sig"
           ",pd.purse_deposit_serial_id"
           ",pdes.refunded"
           " FROM purse_deposits pd"
           " LEFT JOIN partners"
           "   USING (partner_serial_id)"
           " JOIN purse_requests pr"
           "   USING (purse_pub)"
           " LEFT JOIN purse_decision pdes"
           "   USING (purse_pub)"
           " JOIN known_coins kc"
           "   ON (pd.coin_pub = kc.coin_pub)"
           " JOIN denominations denoms"
           "   USING (denominations_serial)"
           // FIXME: use to-be-created materialized index
           // on coin_pub (query crosses partitions!)
           " WHERE pd.coin_pub=$1;");
  PREPARE (pg,
           "get_refunds_by_coin",
           "SELECT"
           " dep.merchant_pub"
           ",ref.merchant_sig"
           ",dep.h_contract_terms"
           ",ref.rtransaction_id"
           ",ref.amount_with_fee_val"
           ",ref.amount_with_fee_frac"
           ",denom.fee_refund_val "
           ",denom.fee_refund_frac "
           ",ref.refund_serial_id"
           " FROM refunds ref"
           " JOIN deposits dep"
           "   ON (ref.coin_pub = dep.coin_pub AND ref.deposit_serial_id = dep.deposit_serial_id)"
           " JOIN known_coins kc"
           "   ON (ref.coin_pub = kc.coin_pub)"
           " JOIN denominations denom"
           "   USING (denominations_serial)"
           " WHERE ref.coin_pub=$1;");
  PREPARE (pg,
           "get_purse_decision_by_coin_pub",
           "SELECT"
           " pdes.purse_pub"
           ",pd.amount_with_fee_val"
           ",pd.amount_with_fee_frac"
           ",denom.fee_refund_val "
           ",denom.fee_refund_frac "
           ",pdes.purse_decision_serial_id"
           " FROM purse_deposits pd"
           " JOIN purse_decision pdes"
           "   USING (purse_pub)"
           " JOIN known_coins kc"
           "   ON (pd.coin_pub = kc.coin_pub)"
           " JOIN denominations denom"
           "   USING (denominations_serial)"
           " WHERE pd.coin_pub=$1"
           "   AND pdes.refunded;");
  PREPARE (pg,
           "recoup_by_old_coin",
           "SELECT"
           " coins.coin_pub"
           ",coin_sig"
           ",coin_blind"
           ",amount_val"
           ",amount_frac"
           ",recoup_timestamp"
           ",denoms.denom_pub_hash"
           ",coins.denom_sig"
           ",recoup_refresh_uuid"
           " FROM recoup_refresh"
           " JOIN known_coins coins"
           "   USING (coin_pub)"
           " JOIN denominations denoms"
           "   USING (denominations_serial)"
           " WHERE rrc_serial IN"
           "   (SELECT rrc.rrc_serial"
           "    FROM refresh_commitments"
           "       JOIN refresh_revealed_coins rrc"
           "           USING (melt_serial_id)"
           "    WHERE old_coin_pub=$1);");
  PREPARE (pg,
           "recoup_by_coin",
           "SELECT"
           " reserves.reserve_pub"
           ",denoms.denom_pub_hash"
           ",coin_sig"
           ",coin_blind"
           ",amount_val"
           ",amount_frac"
           ",recoup_timestamp"
           ",recoup_uuid"
           " FROM recoup rcp"
           /* NOTE: suboptimal JOIN follows: crosses shards!
              Could theoretically be improved via a materialized
              index. But likely not worth it (query is rare and
              number of reserve shards might be limited) */
           " JOIN reserves_out ro"
           "   USING (reserve_out_serial_id)"
           " JOIN reserves"
           "   USING (reserve_uuid)"
           " JOIN known_coins coins"
           "   USING (coin_pub)"
           " JOIN denominations denoms"
           "   ON (denoms.denominations_serial = coins.denominations_serial)"
           " WHERE coins.coin_pub=$1;");
  /* Used in #postgres_get_coin_transactions() to obtain recoup transactions
     for a refreshed coin */
  PREPARE (pg,
           "recoup_by_refreshed_coin",
           "SELECT"
           " old_coins.coin_pub AS old_coin_pub"
           ",coin_sig"
           ",coin_blind"
           ",amount_val"
           ",amount_frac"
           ",recoup_timestamp"
           ",denoms.denom_pub_hash"
           ",coins.denom_sig"
           ",recoup_refresh_uuid"
           " FROM recoup_refresh"
           "    JOIN refresh_revealed_coins rrc"
           "      USING (rrc_serial)"
           "    JOIN refresh_commitments rfc"
           "      ON (rrc.melt_serial_id = rfc.melt_serial_id)"
           "    JOIN known_coins old_coins"
           "      ON (rfc.old_coin_pub = old_coins.coin_pub)"
           "    JOIN known_coins coins"
           "      ON (recoup_refresh.coin_pub = coins.coin_pub)"
           "    JOIN denominations denoms"
           "      ON (denoms.denominations_serial = coins.denominations_serial)"
           " WHERE coins.coin_pub=$1;");
  PREPARE (pg,
           "reserve_open_by_coin",
           "SELECT"
           " reserve_open_deposit_uuid"
           ",coin_sig"
           ",reserve_sig"
           ",contribution_val"
           ",contribution_frac"
           " FROM reserves_open_deposits"
           " WHERE coin_pub=$1;");
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Getting transactions for coin %s\n",
              TALER_B2S (coin_pub));
  for (unsigned int i = 0; NULL != work[i].statement; i++)
  {
    qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
                                               work[i].statement,
                                               params,
                                               work[i].cb,
                                               &chc);
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Coin %s yielded %d transactions of type %s\n",
                TALER_B2S (coin_pub),
                qs,
                work[i].statement);
    if ( (0 > qs) ||
         (chc.failed) )
    {
      if (NULL != chc.head)
        TEH_COMMON_free_coin_transaction_list (cls,
                                               chc.head);
      *tlp = NULL;
      if (chc.failed)
        qs = GNUNET_DB_STATUS_HARD_ERROR;
      return qs;
    }
  }
  *tlp = chc.head;
  if (NULL == chc.head)
    return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
  return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}