--
-- 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/>
--

DROP FUNCTION IF EXISTS exchange_do_begin_shard;
CREATE FUNCTION exchange_do_begin_shard(
  IN in_job_name TEXT,
  IN in_now INT8,
  IN in_lease_until INT8,
  IN in_shard_size INT8,
  OUT out_start_row INT8,
  OUT out_end_row INT8,
  OUT out_progress_row INT8)
LANGUAGE plpgsql
AS $$
BEGIN
  -- Prefer an existing shard nobody is working on any more. FOR UPDATE SKIP
  -- LOCKED hands concurrent workers *different* shards instead of letting
  -- them collide and retry, which is what the explicit serializable
  -- transaction this function replaced used to do.
  SELECT start_row
        ,end_row
        ,progress_row
    INTO out_start_row
        ,out_end_row
        ,out_progress_row
    FROM work_shards
   WHERE job_name=in_job_name
     AND completed=FALSE
     AND last_attempt<in_now
   ORDER BY last_attempt ASC
   LIMIT 1
     FOR UPDATE SKIP LOCKED;

  IF FOUND
  THEN
    -- Take over the lease. Whatever progress the previous worker committed
    -- stands, and we return it so the caller resumes instead of restarting.
    UPDATE work_shards
       SET last_attempt=in_lease_until
     WHERE job_name=in_job_name
       AND start_row=out_start_row
       AND end_row=out_end_row;
    RETURN;
  END IF;

  -- No shard to take over, so open the next one past the last one known.
  SELECT end_row
    INTO out_start_row
    FROM work_shards
   WHERE job_name=in_job_name
   ORDER BY end_row DESC
   LIMIT 1;
  IF NOT FOUND
  THEN
    out_start_row = 0; -- base case: no shards for this job yet
  END IF;
  out_end_row = out_start_row + in_shard_size;
  out_progress_row = out_start_row;

  INSERT INTO work_shards
    (job_name
    ,last_attempt
    ,start_row
    ,end_row
    ,progress_row
    ) VALUES (
     in_job_name
    ,in_lease_until
    ,out_start_row
    ,out_end_row
    ,out_start_row
    )
    ON CONFLICT DO NOTHING;

  IF NOT FOUND
  THEN
    -- Someone else opened this very shard between our two statements. Report
    -- "nothing acquired" and let the caller come back around.
    out_start_row = NULL;
    out_end_row = NULL;
    out_progress_row = NULL;
  END IF;
END $$;

COMMENT ON FUNCTION exchange_do_begin_shard
  IS 'Acquires a lease on a work shard for in_job_name, resuming an abandoned shard if there is one and opening a fresh shard of in_shard_size rows otherwise. Returns NULL columns if another worker won the race to open the same shard.';
