/*
   This file is part of TALER
   Copyright (C) 2022, 2026 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 <http://www.gnu.org/licenses/>
 */
/**
 * @file exchangedb/begin_shard.c
 * @brief Implementation of the begin_shard function for Postgres
 * @author Christian Grothoff
 */
#include "taler/taler_pq_lib.h"
#include "exchange-database/begin_shard.h"
#include "helper.h"


/**
 * How often do we retry when another worker opened the very shard we were
 * about to open?  Each round is one round-trip and the loser of the race can
 * simply take the shard after it, so this converges quickly.
 */
#define MAX_RETRIES 10


enum GNUNET_DB_QueryStatus
TALER_EXCHANGEDB_begin_shard (struct TALER_EXCHANGEDB_PostgresContext *pg,
                              const char *job_name,
                              struct GNUNET_TIME_Relative delay,
                              uint64_t shard_size,
                              uint64_t *start_row,
                              uint64_t *end_row,
                              uint64_t *progress_row)
{
  PREPARE (pg,
           "begin_shard",
           "SELECT"
           " out_start_row"
           ",out_end_row"
           ",out_progress_row"
           " FROM exchange_do_begin_shard"
           " ($1,$2,$3,$4);");
  for (unsigned int retries = 0; retries<MAX_RETRIES; retries++)
  {
    struct GNUNET_TIME_Absolute now
      = GNUNET_TIME_absolute_get ();
    struct GNUNET_TIME_Absolute lease_until
      = GNUNET_TIME_relative_to_absolute (delay);
    struct GNUNET_PQ_QueryParam params[] = {
      GNUNET_PQ_query_param_string (job_name),
      GNUNET_PQ_query_param_absolute_time (&now),
      GNUNET_PQ_query_param_absolute_time (&lease_until),
      GNUNET_PQ_query_param_uint64 (&shard_size),
      GNUNET_PQ_query_param_end
    };
    bool lost_race = false;
    struct GNUNET_PQ_ResultSpec rs[] = {
      GNUNET_PQ_result_spec_allow_null (
        GNUNET_PQ_result_spec_uint64 ("out_start_row",
                                      start_row),
        &lost_race),
      GNUNET_PQ_result_spec_allow_null (
        GNUNET_PQ_result_spec_uint64 ("out_end_row",
                                      end_row),
        NULL),
      GNUNET_PQ_result_spec_allow_null (
        GNUNET_PQ_result_spec_uint64 ("out_progress_row",
                                      progress_row),
        NULL),
      GNUNET_PQ_result_spec_end
    };
    enum GNUNET_DB_QueryStatus qs;

    qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
                                                   "begin_shard",
                                                   params,
                                                   rs);
    if (0 > qs)
      return qs;
    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    {
      GNUNET_break (0); /* the function always returns exactly one row */
      return GNUNET_DB_STATUS_HARD_ERROR;
    }
    if (lost_race)
    {
      /* Someone else opened the shard we picked.  Go around: either take over
         an even older one, or open the one after theirs. */
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Lost the race to open a shard of %s, trying again\n",
                  job_name);
      continue;
    }
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Acquired shard %s (%llu,%llu], resuming at %llu\n",
                job_name,
                (unsigned long long) *start_row,
                (unsigned long long) *end_row,
                (unsigned long long) *progress_row);
    return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
  }
  /* Persistent contention.  Not an error, but the caller should back off. */
  return GNUNET_DB_STATUS_SOFT_ERROR;
}
