/*
   This file is part of GNUnet
   Copyright (C) 2014, 2015, 2016, 2017, 2019 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 pq/pq.c
 * @brief helper functions for libpq (PostGres) interactions
 * @author Sree Harsha Totakura <sreeharsha@totakura.in>
 * @author Florian Dold
 * @author Christian Grothoff
 */
#include "platform.h"
#include "pq.h"

/**
 * Execute a prepared statement.
 *
 * @param db database handle
 * @param name name of the prepared statement
 * @param params parameters to the statement
 * @return postgres result
 */
PGresult *
GNUNET_PQ_exec_prepared (struct GNUNET_PQ_Context *db,
                         const char *name,
                         const struct GNUNET_PQ_QueryParam *params)
{
  unsigned int len;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Running prepared statement `%s' on %p\n",
              name,
              db);
  /* count the number of parameters */
  len = 0;
  for (unsigned int i = 0; 0 != params[i].num_params; i++)
    len += params[i].num_params;

  /* new scope to allow stack allocation without alloca */
  {
    /* Scratch buffer for temporary storage */
    void *scratch[len];
    /* Parameter array we are building for the query */
    void *param_values[len];
    int param_lengths[len];
    int param_formats[len];
    unsigned int off;
    /* How many entries in the scratch buffer are in use? */
    unsigned int soff;
    PGresult *res;
    int ret;
    ConnStatusType status;

    off = 0;
    soff = 0;
    for (unsigned int i = 0; 0 != params[i].num_params; i++)
    {
      const struct GNUNET_PQ_QueryParam *x = &params[i];

      ret = x->conv (x->conv_cls,
                     x->data,
                     x->size,
                     &param_values[off],
                     &param_lengths[off],
                     &param_formats[off],
                     x->num_params,
                     &scratch[soff],
                     len - soff);
      if (ret < 0)
      {
        for (off = 0; off < soff; off++)
          GNUNET_free (scratch[off]);
        return NULL;
      }
      soff += ret;
      off += x->num_params;
    }
    GNUNET_assert (off == len);
    GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
                     "pq",
                     "Executing prepared SQL statement `%s'\n",
                     name);
    res = PQexecPrepared (db->conn,
                          name,
                          len,
                          (const char **) param_values,
                          param_lengths,
                          param_formats,
                          1);
    if ( (PGRES_COMMAND_OK != PQresultStatus (res)) &&
         (CONNECTION_OK != (status = PQstatus (db->conn))) )
    {
      GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
                       "pq",
                       "Database disconnected on SQL statement `%s' (reconnecting)\n",
                       name);
      GNUNET_PQ_reconnect (db);
      res = NULL;
    }

    for (off = 0; off < soff; off++)
      GNUNET_free (scratch[off]);
    return res;
  }
}


/**
 * Free all memory that was allocated in @a rs during
 * #GNUNET_PQ_extract_result().
 *
 * @param rs reult specification to clean up
 */
void
GNUNET_PQ_cleanup_result (struct GNUNET_PQ_ResultSpec *rs)
{
  for (unsigned int i = 0; NULL != rs[i].conv; i++)
    if (NULL != rs[i].cleaner)
      rs[i].cleaner (rs[i].cls,
                     rs[i].dst);
}


/**
 * Extract results from a query result according to the given
 * specification.
 *
 * @param result result to process
 * @param[in,out] rs result specification to extract for
 * @param row row from the result to extract
 * @return
 *   #GNUNET_YES if all results could be extracted
 *   #GNUNET_SYSERR if a result was invalid (non-existing field)
 */
int
GNUNET_PQ_extract_result (PGresult *result,
                          struct GNUNET_PQ_ResultSpec *rs,
                          int row)
{
  if (NULL == result)
    return GNUNET_SYSERR;
  for (unsigned int i = 0; NULL != rs[i].conv; i++)
  {
    struct GNUNET_PQ_ResultSpec *spec;
    int ret;

    spec = &rs[i];
    ret = spec->conv (spec->cls,
                      result,
                      row,
                      spec->fname,
                      &spec->dst_size,
                      spec->dst);
    if (GNUNET_OK != ret)
    {
      for (unsigned int j = 0; j < i; j++)
        if (NULL != rs[j].cleaner)
          rs[j].cleaner (rs[j].cls,
                         rs[j].dst);
      return GNUNET_SYSERR;
    }
    if (NULL != spec->result_size)
      *spec->result_size = spec->dst_size;
  }
  return GNUNET_OK;
}


/* end of pq/pq.c */
