sync

Backup service to store encrypted wallet databases (experimental)
Log | Files | Refs | Submodules | README | LICENSE

syncdb_object_refcounts.sql (4205B)


      1 --
      2 -- This file is part of TALER
      3 -- Copyright (C) 2026 Taler Systems SA
      4 --
      5 -- TALER is free software; you can redistribute it and/or modify it under the
      6 -- terms of the GNU General Public License as published by the Free Software
      7 -- Foundation; either version 3, or (at your option) any later version.
      8 --
      9 -- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10 -- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11 -- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 --
     13 -- You should have received a copy of the GNU General Public License along with
     14 -- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 --
     16 
     17 --
     18 -- Helper to update object refcounts for a given account and parallel
     19 -- arrays of object uuids and increments.
     20 --
     21 -- Increments are aggregated per UID before they are applied, so the
     22 -- outcome does not depend on the order UIDs appear in the arrays, and
     23 -- agrees with sync_check_object_refcounts_pre() by construction.
     24 --
     25 DROP FUNCTION IF EXISTS sync_update_object_refcounts;
     26 CREATE FUNCTION sync_update_object_refcounts(
     27   IN in_account_pub BYTEA,
     28   IN ina_object_uids BYTEA[],
     29   IN ina_object_incs INT2[])
     30 RETURNS TABLE (
     31   out_negative_refcount BOOLEAN,
     32   out_refs_updated BOOLEAN)
     33 LANGUAGE plpgsql
     34 AS $$
     35 DECLARE
     36   my_rec RECORD;
     37   my_new_refcount INT8;
     38 BEGIN
     39   out_negative_refcount := FALSE;
     40   out_refs_updated := TRUE;
     41 
     42   IF array_length(ina_object_uids,1) IS DISTINCT FROM array_length(ina_object_incs,1)
     43   THEN
     44     RAISE EXCEPTION 'uuid and inc arrays must have same length';
     45   END IF;
     46 
     47   IF COALESCE(array_length(ina_object_uids, 1), 0) = 0
     48   THEN
     49     RETURN NEXT;
     50     RETURN;
     51   END IF;
     52 
     53   FOR my_rec IN
     54     SELECT uid, SUM(inc)::INT8 AS total_inc
     55       FROM unnest(ina_object_uids, ina_object_incs) AS t(uid, inc)
     56      GROUP BY uid
     57   LOOP
     58     UPDATE objects o
     59        SET refcount = o.refcount + my_rec.total_inc
     60      WHERE o.uid = my_rec.uid
     61        AND o.account_pub = in_account_pub
     62     RETURNING o.refcount INTO my_new_refcount;
     63 
     64     IF NOT FOUND
     65     THEN
     66       -- No such object for this account
     67       out_refs_updated := FALSE;
     68       RETURN NEXT;
     69       RETURN;
     70     END IF;
     71 
     72     IF my_new_refcount < 0
     73     THEN
     74       out_negative_refcount := TRUE;
     75       out_refs_updated := FALSE;
     76       RETURN NEXT;
     77       RETURN;
     78     END IF;
     79 
     80     -- Collect the object when its last reference goes away.  A
     81     -- freshly uploaded object also sits at refcount 0, hence the
     82     -- decrement check.
     83     IF (my_new_refcount = 0) AND
     84        (my_rec.total_inc < 0)
     85     THEN
     86       DELETE FROM objects
     87        WHERE uid = my_rec.uid
     88          AND account_pub = in_account_pub;
     89     END IF;
     90   END LOOP;
     91 
     92   RETURN NEXT;
     93   RETURN;
     94 END $$;
     95 
     96 --
     97 -- Checks, without modifying anything, whether the given increments can
     98 -- be applied: every referenced UID must exist for the account, and no
     99 -- aggregated decrement may drive a refcount below zero.  To be called
    100 -- before any state is mutated.
    101 --
    102 DROP FUNCTION IF EXISTS sync_check_object_refcounts_pre;
    103 CREATE FUNCTION sync_check_object_refcounts_pre(
    104   IN in_account_pub BYTEA,
    105   IN ina_object_uids BYTEA[],
    106   IN ina_object_incs INT2[])
    107 RETURNS TABLE (
    108   out_negative_refcount BOOLEAN,
    109   out_refs_missing BOOLEAN)
    110 LANGUAGE plpgsql
    111 AS $$
    112 BEGIN
    113   out_negative_refcount := FALSE;
    114   out_refs_missing := FALSE;
    115 
    116   IF array_length(ina_object_uids,1) IS DISTINCT FROM array_length(ina_object_incs,1)
    117   THEN
    118     RAISE EXCEPTION 'uuid and inc arrays must have same length';
    119   END IF;
    120 
    121   IF COALESCE(array_length(ina_object_uids, 1), 0) = 0
    122   THEN
    123     RETURN NEXT;
    124     RETURN;
    125   END IF;
    126 
    127   SELECT
    128     bool_or(o.uid IS NOT NULL AND a.total_inc < 0 AND o.refcount < (- a.total_inc))
    129    ,bool_or(o.uid IS NULL)
    130     INTO out_negative_refcount
    131         ,out_refs_missing
    132     FROM (
    133       SELECT uid, SUM(inc)::INT8 AS total_inc
    134         FROM unnest(ina_object_uids, ina_object_incs) AS t(uid, inc)
    135        GROUP BY uid
    136     ) a
    137     LEFT JOIN objects o
    138            ON o.uid = a.uid
    139           AND o.account_pub = in_account_pub;
    140 
    141   out_negative_refcount := COALESCE(out_negative_refcount, FALSE);
    142   out_refs_missing := COALESCE(out_refs_missing, FALSE);
    143   RETURN NEXT;
    144   RETURN;
    145 END $$;