commit c2b5ac518c05250e15ce720c131fb2814f7fa3eb
parent 666ef72f72439c9e608a67aa45ee13df749ac12d
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 22 Mar 2026 15:16:04 +0100
use stored procedures instead of manual transactions
Diffstat:
14 files changed, 512 insertions(+), 547 deletions(-)
diff --git a/src/syncdb/.gitignore b/src/syncdb/.gitignore
@@ -3,3 +3,4 @@ test_sync_db-postgres
.libs
test-suite.log
sync-dbinit
+procedures.sql
diff --git a/src/syncdb/Makefile.am b/src/syncdb/Makefile.am
@@ -13,11 +13,30 @@ endif
sqldir = $(prefix)/share/sync/sql/
+sqlinputs = \
+ syncdb_increment_lifetime_TR.sql \
+ syncdb_lookup_account_TR.sql \
+ syncdb_lookup_backup_TR.sql \
+ syncdb_lookup_pending_payments_by_account_TR.sql \
+ syncdb_store_backup_TR.sql \
+ syncdb_store_payment_TR.sql \
+ syncdb_update_backup_TR.sql \
+ procedures.sql.in
+
sql_DATA = \
versioning.sql \
+ procedures.sql \
sync-0001.sql \
drop.sql
+BUILT_SOURCES = \
+ procedures.sql
+
+procedures.sql: procedures.sql.in syncdb_*_TR.sql
+ chmod +w $@ || true
+ gcc -E -P -undef - < procedures.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@
+ chmod ugo-w $@
+
bin_PROGRAMS = \
sync-dbinit
@@ -81,4 +100,5 @@ TESTS = \
EXTRA_DIST = \
$(pkgcfg_DATA) \
$(sql_DATA) \
+ $(sqlinputs) \
test_sync_db_postgres.conf
diff --git a/src/syncdb/procedures.sql.in b/src/syncdb/procedures.sql.in
@@ -0,0 +1,26 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 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/>
+--
+
+BEGIN;
+
+SET search_path TO sync;
+
+#include "syncdb_increment_lifetime_TR.sql"
+#include "syncdb_lookup_account_TR.sql"
+#include "syncdb_store_backup_TR.sql"
+#include "syncdb_update_backup_TR.sql"
+
+COMMIT;
diff --git a/src/syncdb/syncdb_create_tables.c b/src/syncdb/syncdb_create_tables.c
@@ -29,6 +29,7 @@ SYNCDB_create_tables (void)
GNUNET_PQ_make_execute ("SET search_path TO sync;"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
+ enum GNUNET_GenericReturnValue ret;
conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
"syncdb-postgres",
@@ -37,8 +38,10 @@ SYNCDB_create_tables (void)
NULL);
if (NULL == conn)
return GNUNET_SYSERR;
+ ret = GNUNET_PQ_exec_sql (conn,
+ "procedures");
GNUNET_PQ_disconnect (conn);
- return GNUNET_OK;
+ return ret;
}
diff --git a/src/syncdb/syncdb_increment_lifetime_TR.c b/src/syncdb/syncdb_increment_lifetime_TR.c
@@ -27,166 +27,44 @@ SYNCDB_increment_lifetime_TR (
const char *order_id,
struct GNUNET_TIME_Relative lifetime)
{
- struct GNUNET_TIME_Timestamp expiration;
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+ bool no_payment;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (account_pub),
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_relative_time (&lifetime),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("out_no_payment",
+ &no_payment),
+ GNUNET_PQ_result_spec_end
+ };
enum GNUNET_DB_QueryStatus qs;
- if (GNUNET_OK !=
- begin_transaction ("increment lifetime"))
- {
- GNUNET_break (0);
- return SYNC_DB_HARD_ERROR;
- }
-
- PREPARE ("increment_lifetime_payment_done",
- "UPDATE payments "
- "SET"
- " paid=TRUE "
- "WHERE"
- " order_id=$1"
- " AND"
- " account_pub=$2"
- " AND"
- " paid=FALSE;");
-
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (order_id),
- GNUNET_PQ_query_param_auto_from_type (account_pub),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "increment_lifetime_payment_done",
- params);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- rollback ();
- return SYNC_DB_HARD_ERROR;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- rollback ();
- return SYNC_DB_SOFT_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- rollback ();
- return SYNC_DB_NO_RESULTS;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- }
-
- PREPARE ("increment_lifetime_account_select",
+ SYNCDB_preflight ();
+ PREPARE ("do_increment_lifetime",
"SELECT"
- " expiration_date "
- "FROM"
- " accounts "
- "WHERE"
- " account_pub=$1;");
-
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (account_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_timestamp ("expiration_date",
- &expiration),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "increment_lifetime_account_select",
- params,
- rs);
- }
-
- PREPARE ("increment_lifetime_account_insert",
- "INSERT INTO accounts "
- "(account_pub"
- ",expiration_date"
- ") VALUES "
- "($1,$2);");
-
- PREPARE ("increment_lifetime_account_update",
- "UPDATE accounts "
- "SET"
- " expiration_date=$1 "
- "WHERE"
- " account_pub=$2;");
-
+ " out_no_payment"
+ " FROM sync_do_increment_lifetime"
+ " ($1,$2,$3,$4);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "do_increment_lifetime",
+ params,
+ rs);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
- rollback ();
return SYNC_DB_HARD_ERROR;
case GNUNET_DB_STATUS_SOFT_ERROR:
- rollback ();
return SYNC_DB_SOFT_ERROR;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (account_pub),
- GNUNET_PQ_query_param_timestamp (&expiration),
- GNUNET_PQ_query_param_end
- };
-
- expiration = GNUNET_TIME_relative_to_timestamp (lifetime);
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "increment_lifetime_account_insert",
- params);
- }
- break;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_timestamp (&expiration),
- GNUNET_PQ_query_param_auto_from_type (account_pub),
- GNUNET_PQ_query_param_end
- };
-
- expiration = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (expiration.abs_time,
- lifetime));
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "increment_lifetime_account_update",
- params);
- }
- break;
- default:
GNUNET_break (0);
return SYNC_DB_HARD_ERROR;
- }
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- rollback ();
- return SYNC_DB_HARD_ERROR;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- rollback ();
- GNUNET_break (0);
- return SYNC_DB_SOFT_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_break (0);
- rollback ();
- return SYNC_DB_NO_RESULTS;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- default:
- GNUNET_break (0);
- return SYNC_DB_HARD_ERROR;
- }
- qs = commit_transaction ();
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- return SYNC_DB_HARD_ERROR;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return SYNC_DB_SOFT_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return SYNC_DB_ONE_RESULT;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ if (no_payment)
+ return SYNC_DB_NO_RESULTS;
return SYNC_DB_ONE_RESULT;
default:
GNUNET_break (0);
diff --git a/src/syncdb/syncdb_increment_lifetime_TR.sql b/src/syncdb/syncdb_increment_lifetime_TR.sql
@@ -0,0 +1,66 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 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 sync_do_increment_lifetime;
+CREATE FUNCTION sync_do_increment_lifetime (
+ IN in_account_pub BYTEA,
+ IN in_order_id TEXT,
+ IN in_lifetime_us INT8,
+ IN in_now_us INT8,
+ OUT out_no_payment BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_expiration INT8;
+BEGIN
+ out_no_payment = FALSE;
+
+ -- Mark payment as done
+ UPDATE payments
+ SET paid=TRUE
+ WHERE order_id=in_order_id
+ AND account_pub=in_account_pub
+ AND paid=FALSE;
+
+ IF NOT FOUND
+ THEN
+ out_no_payment = TRUE;
+ RETURN;
+ END IF;
+
+ -- Get current expiration
+ SELECT expiration_date
+ INTO my_expiration
+ FROM accounts
+ WHERE account_pub=in_account_pub;
+
+ IF NOT FOUND
+ THEN
+ -- No account yet, create one with expiration = now + lifetime
+ INSERT INTO accounts
+ (account_pub
+ ,expiration_date)
+ VALUES
+ (in_account_pub
+ ,in_now_us + in_lifetime_us);
+ ELSE
+ -- Account exists, extend expiration
+ UPDATE accounts
+ SET expiration_date=my_expiration + in_lifetime_us
+ WHERE account_pub=in_account_pub;
+ END IF;
+END $$;
diff --git a/src/syncdb/syncdb_lookup_account_TR.c b/src/syncdb/syncdb_lookup_account_TR.c
@@ -26,34 +26,35 @@ SYNCDB_lookup_account_TR (
const struct SYNC_AccountPublicKeyP *account_pub,
struct GNUNET_HashCode *backup_hash)
{
- enum GNUNET_DB_QueryStatus qs;
+ bool payment_required;
+ bool no_backup;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (account_pub),
GNUNET_PQ_query_param_end
};
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("out_backup_hash",
+ backup_hash),
+ GNUNET_PQ_result_spec_bool ("out_payment_required",
+ &payment_required),
+ GNUNET_PQ_result_spec_bool ("out_no_backup",
+ &no_backup),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
SYNCDB_preflight ();
-
- PREPARE ("lookup_account_backup_select_hash",
- "SELECT "
- " backup_hash "
- "FROM"
- " backups "
- "WHERE"
- " account_pub=$1;");
-
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("backup_hash",
- backup_hash),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_account_backup_select_hash",
- params,
- rs);
- }
+ PREPARE ("do_lookup_account",
+ "SELECT"
+ " out_backup_hash"
+ ",out_payment_required"
+ ",out_no_backup"
+ " FROM sync_do_lookup_account"
+ " ($1);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "do_lookup_account",
+ params,
+ rs);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -62,49 +63,14 @@ SYNCDB_lookup_account_TR (
GNUNET_break (0);
return SYNC_DB_SOFT_ERROR;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- break; /* handle interesting case below */
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- return SYNC_DB_ONE_RESULT;
- default:
GNUNET_break (0);
return SYNC_DB_HARD_ERROR;
- }
-
- PREPARE ("lookup_account_account_select",
- "SELECT"
- " expiration_date "
- "FROM"
- " accounts "
- "WHERE"
- " account_pub=$1;");
-
- /* check if account exists */
- {
- struct GNUNET_TIME_Timestamp expiration;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("expiration_date",
- &expiration),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_account_account_select",
- params,
- rs);
- }
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- return SYNC_DB_HARD_ERROR;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return SYNC_DB_SOFT_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* indicates: no account */
- return SYNC_DB_PAYMENT_REQUIRED;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* indicates: no backup */
- return SYNC_DB_NO_RESULTS;
+ if (payment_required)
+ return SYNC_DB_PAYMENT_REQUIRED;
+ if (no_backup)
+ return SYNC_DB_NO_RESULTS;
+ return SYNC_DB_ONE_RESULT;
default:
GNUNET_break (0);
return SYNC_DB_HARD_ERROR;
diff --git a/src/syncdb/syncdb_lookup_account_TR.sql b/src/syncdb/syncdb_lookup_account_TR.sql
@@ -0,0 +1,54 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 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 sync_do_lookup_account;
+CREATE FUNCTION sync_do_lookup_account (
+ IN in_account_pub BYTEA,
+ OUT out_backup_hash BYTEA,
+ OUT out_payment_required BOOLEAN,
+ OUT out_no_backup BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ out_payment_required = FALSE;
+ out_no_backup = FALSE;
+
+ -- Check if backup exists
+ SELECT backup_hash
+ INTO out_backup_hash
+ FROM backups
+ WHERE account_pub=in_account_pub;
+
+ IF FOUND
+ THEN
+ RETURN;
+ END IF;
+
+ -- No backup; check if account exists
+ PERFORM 1
+ FROM accounts
+ WHERE account_pub=in_account_pub;
+
+ IF NOT FOUND
+ THEN
+ out_payment_required = TRUE;
+ RETURN;
+ END IF;
+
+ -- Account exists but no backup
+ out_no_backup = TRUE;
+END $$;
diff --git a/src/syncdb/syncdb_pg.c b/src/syncdb/syncdb_pg.c
@@ -29,65 +29,6 @@ struct PostgresClosure *pg;
enum GNUNET_GenericReturnValue
-begin_transaction (const char *name)
-{
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- SYNCDB_preflight ();
- pg->transaction_name = name;
- if (GNUNET_OK !=
- GNUNET_PQ_exec_statements (pg->conn,
- es))
- {
- TALER_LOG_ERROR ("Failed to start transaction\n");
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-void
-rollback (void)
-{
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("ROLLBACK"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_exec_statements (pg->conn,
- es))
- {
- TALER_LOG_ERROR ("Failed to rollback transaction\n");
- GNUNET_break (0);
- }
- pg->transaction_name = NULL;
-}
-
-
-enum GNUNET_DB_QueryStatus
-commit_transaction (void)
-{
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam no_params[] = {
- GNUNET_PQ_query_param_end
- };
-
- PREPARE ("do_commit",
- "COMMIT");
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "do_commit",
- no_params);
- pg->transaction_name = NULL;
- return qs;
-}
-
-
-enum GNUNET_GenericReturnValue
SYNCDB_init (const struct GNUNET_CONFIGURATION_Handle *cfg,
bool skip_preflight)
{
diff --git a/src/syncdb/syncdb_pg.h b/src/syncdb/syncdb_pg.h
@@ -74,33 +74,6 @@ extern struct PostgresClosure *pg;
/**
- * Start a transaction.
- *
- * @param name unique name identifying the transaction (for debugging),
- * must point to a constant
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-begin_transaction (const char *name);
-
-
-/**
- * Roll back the current transaction of a database connection.
- */
-void
-rollback (void);
-
-
-/**
- * Commit the current transaction of a database connection.
- *
- * @return transaction status code
- */
-enum GNUNET_DB_QueryStatus
-commit_transaction (void);
-
-
-/**
* Prepares SQL statement @a sql under @a name for
* connection @a pg once.
* Returns with #GNUNET_DB_STATUS_HARD_ERROR on failure.
diff --git a/src/syncdb/syncdb_store_backup_TR.c b/src/syncdb/syncdb_store_backup_TR.c
@@ -29,80 +29,46 @@ SYNCDB_store_backup_TR (
size_t backup_size,
const void *backup)
{
+ uint32_t bs = (uint32_t) backup_size;
+ bool payment_required;
+ bool old_backup_mismatch;
+ bool no_change;
+ bool inserted;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (account_pub),
+ GNUNET_PQ_query_param_auto_from_type (account_sig),
+ GNUNET_PQ_query_param_auto_from_type (backup_hash),
+ GNUNET_PQ_query_param_uint32 (&bs),
+ GNUNET_PQ_query_param_fixed_size (backup,
+ backup_size),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("out_payment_required",
+ &payment_required),
+ GNUNET_PQ_result_spec_bool ("out_old_backup_mismatch",
+ &old_backup_mismatch),
+ GNUNET_PQ_result_spec_bool ("out_no_change",
+ &no_change),
+ GNUNET_PQ_result_spec_bool ("out_inserted",
+ &inserted),
+ GNUNET_PQ_result_spec_end
+ };
enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_HashCode bh;
- static struct GNUNET_HashCode no_previous_hash;
SYNCDB_preflight ();
-
- PREPARE ("store_backup_backup_insert",
- "INSERT INTO backups "
- "(account_pub"
- ",account_sig"
- ",prev_hash"
- ",backup_hash"
- ",data"
- ") VALUES "
- "($1,$2,$3,$4,$5);");
-
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (account_pub),
- GNUNET_PQ_query_param_auto_from_type (account_sig),
- GNUNET_PQ_query_param_auto_from_type (&no_previous_hash),
- GNUNET_PQ_query_param_auto_from_type (backup_hash),
- GNUNET_PQ_query_param_fixed_size (backup,
- backup_size),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "store_backup_backup_insert",
- params);
- }
- switch (qs)
- {
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return SYNC_DB_SOFT_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return SYNC_DB_NO_RESULTS;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- return SYNC_DB_ONE_RESULT;
- case GNUNET_DB_STATUS_HARD_ERROR:
- /* handle interesting case below */
- break;
- default:
- GNUNET_break (0);
- return SYNC_DB_HARD_ERROR;
- }
-
- PREPARE ("store_backup_account_select",
+ PREPARE ("do_store_backup",
"SELECT"
- " expiration_date "
- "FROM"
- " accounts "
- "WHERE"
- " account_pub=$1;");
-
- /* First, check if account exists */
- {
- struct GNUNET_TIME_Timestamp ed;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (account_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("expiration_date",
- &ed),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "store_backup_account_select",
- params,
- rs);
- }
+ " out_payment_required"
+ ",out_old_backup_mismatch"
+ ",out_no_change"
+ ",out_inserted"
+ " FROM sync_do_store_backup"
+ " ($1,$2,$3,$4,$5);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "do_store_backup",
+ params,
+ rs);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -111,66 +77,23 @@ SYNCDB_store_backup_TR (
GNUNET_break (0);
return SYNC_DB_SOFT_ERROR;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return SYNC_DB_PAYMENT_REQUIRED;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* handle interesting case below */
- break;
- default:
GNUNET_break (0);
return SYNC_DB_HARD_ERROR;
- }
-
- PREPARE ("store_backup_backup_select_hash",
- "SELECT "
- " backup_hash "
- "FROM"
- " backups "
- "WHERE"
- " account_pub=$1;");
-
-
- /* account exists, check if existing backup conflicts */
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (account_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("backup_hash",
- &bh),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "store_backup_backup_select_hash",
- params,
- rs);
- }
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- return SYNC_DB_HARD_ERROR;
- case GNUNET_DB_STATUS_SOFT_ERROR:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ if (inserted)
+ return SYNC_DB_ONE_RESULT;
+ if (payment_required)
+ return SYNC_DB_PAYMENT_REQUIRED;
+ if (old_backup_mismatch)
+ return SYNC_DB_OLD_BACKUP_MISMATCH;
+ if (no_change)
+ return SYNC_DB_NO_RESULTS;
GNUNET_break (0);
- return SYNC_DB_SOFT_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* original error must have been a hard error, oddly enough */
return SYNC_DB_HARD_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* handle interesting case below */
- break;
default:
GNUNET_break (0);
return SYNC_DB_HARD_ERROR;
}
-
- /* had an existing backup, is it identical? */
- if (0 != GNUNET_memcmp (&bh,
- backup_hash))
- /* previous conflicting backup exists */
- return SYNC_DB_OLD_BACKUP_MISMATCH;
- /* backup identical to what was provided, no change */
- return SYNC_DB_NO_RESULTS;
}
diff --git a/src/syncdb/syncdb_store_backup_TR.sql b/src/syncdb/syncdb_store_backup_TR.sql
@@ -0,0 +1,93 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 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 sync_do_store_backup;
+CREATE FUNCTION sync_do_store_backup (
+ IN in_account_pub BYTEA,
+ IN in_account_sig BYTEA,
+ IN in_backup_hash BYTEA,
+ IN in_backup_size INT4,
+ IN in_data BYTEA,
+ OUT out_payment_required BOOLEAN,
+ OUT out_old_backup_mismatch BOOLEAN,
+ OUT out_no_change BOOLEAN,
+ OUT out_inserted BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_no_previous_hash BYTEA;
+ my_existing_hash BYTEA;
+BEGIN
+ out_payment_required = FALSE;
+ out_old_backup_mismatch = FALSE;
+ out_no_change = FALSE;
+ out_inserted = FALSE;
+ my_no_previous_hash = '\x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
+
+ -- Check if account exists
+ PERFORM 1
+ FROM accounts
+ WHERE account_pub=in_account_pub;
+
+ IF NOT FOUND
+ THEN
+ out_payment_required = TRUE;
+ RETURN;
+ END IF;
+
+ -- Try to insert the backup
+ INSERT INTO backups
+ (account_pub
+ ,account_sig
+ ,prev_hash
+ ,backup_hash
+ ,data)
+ VALUES
+ (in_account_pub
+ ,in_account_sig
+ ,my_no_previous_hash
+ ,in_backup_hash
+ ,in_data)
+ ON CONFLICT DO NOTHING;
+
+ IF FOUND
+ THEN
+ out_inserted = TRUE;
+ RETURN;
+ END IF;
+
+ -- Check for existing backup hash
+ SELECT backup_hash
+ INTO my_existing_hash
+ FROM backups
+ WHERE account_pub=in_account_pub;
+
+ IF NOT FOUND
+ THEN
+ -- Shouldn't happen: account exists but insert failed and no backup exists
+ RAISE EXCEPTION 'unexpected state: account exists but no backup and insert failed';
+ END IF;
+
+ IF my_existing_hash <> in_backup_hash
+ THEN
+ out_old_backup_mismatch = TRUE;
+ RETURN;
+ END IF;
+
+ -- Backup identical to what was provided
+ out_no_change = TRUE;
+END $$;
diff --git a/src/syncdb/syncdb_update_backup_TR.c b/src/syncdb/syncdb_update_backup_TR.c
@@ -30,81 +30,51 @@ SYNCDB_update_backup_TR (
size_t backup_size,
const void *backup)
{
+ uint32_t bs = (uint32_t) backup_size;
+ bool updated;
+ bool payment_required;
+ bool old_backup_missing;
+ bool old_backup_mismatch;
+ bool no_change;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (account_pub),
+ GNUNET_PQ_query_param_auto_from_type (old_backup_hash),
+ GNUNET_PQ_query_param_auto_from_type (account_sig),
+ GNUNET_PQ_query_param_auto_from_type (backup_hash),
+ GNUNET_PQ_query_param_uint32 (&bs),
+ GNUNET_PQ_query_param_fixed_size (backup,
+ backup_size),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("out_updated",
+ &updated),
+ GNUNET_PQ_result_spec_bool ("out_payment_required",
+ &payment_required),
+ GNUNET_PQ_result_spec_bool ("out_old_backup_missing",
+ &old_backup_missing),
+ GNUNET_PQ_result_spec_bool ("out_old_backup_mismatch",
+ &old_backup_mismatch),
+ GNUNET_PQ_result_spec_bool ("out_no_change",
+ &no_change),
+ GNUNET_PQ_result_spec_end
+ };
enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_HashCode bh;
SYNCDB_preflight ();
- PREPARE ("backup_update",
- "UPDATE backups "
- " SET"
- " backup_hash=$1"
- ",account_sig=$2"
- ",prev_hash=$3"
- ",data=$4"
- " WHERE"
- " account_pub=$5"
- " AND"
- " backup_hash=$6;");
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (backup_hash),
- GNUNET_PQ_query_param_auto_from_type (account_sig),
- GNUNET_PQ_query_param_auto_from_type (old_backup_hash),
- GNUNET_PQ_query_param_fixed_size (backup,
- backup_size),
- GNUNET_PQ_query_param_auto_from_type (account_pub),
- GNUNET_PQ_query_param_auto_from_type (old_backup_hash),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "backup_update",
- params);
- }
- switch (qs)
- {
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return SYNC_DB_SOFT_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* handle interesting case below */
- break;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- return SYNC_DB_ONE_RESULT;
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- return SYNC_DB_HARD_ERROR;
- default:
- GNUNET_break (0);
- return SYNC_DB_HARD_ERROR;
- }
-
- PREPARE ("update_backup_account_select",
+ PREPARE ("do_update_backup",
"SELECT"
- " expiration_date "
- "FROM"
- " accounts "
- "WHERE"
- " account_pub=$1;");
-
- /* First, check if account exists */
- {
- struct GNUNET_TIME_Timestamp ed;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (account_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("expiration_date",
- &ed),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "update_backup_account_select",
- params,
- rs);
- }
+ " out_updated"
+ ",out_payment_required"
+ ",out_old_backup_missing"
+ ",out_old_backup_mismatch"
+ ",out_no_change"
+ " FROM sync_do_update_backup"
+ " ($1,$2,$3,$4,$5,$6);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "do_update_backup",
+ params,
+ rs);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -113,71 +83,25 @@ SYNCDB_update_backup_TR (
GNUNET_break (0);
return SYNC_DB_SOFT_ERROR;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return SYNC_DB_PAYMENT_REQUIRED;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* handle interesting case below */
- break;
- default:
GNUNET_break (0);
return SYNC_DB_HARD_ERROR;
- }
-
- PREPARE ("update_backup_backup_select_hash",
- "SELECT "
- " backup_hash "
- "FROM"
- " backups "
- "WHERE"
- " account_pub=$1;");
-
- /* account exists, check if existing backup conflicts */
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (account_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("backup_hash",
- &bh),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "update_backup_backup_select_hash",
- params,
- rs);
- }
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- return SYNC_DB_HARD_ERROR;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return SYNC_DB_SOFT_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return SYNC_DB_OLD_BACKUP_MISSING;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* handle interesting case below */
- break;
+ if (updated)
+ return SYNC_DB_ONE_RESULT;
+ if (payment_required)
+ return SYNC_DB_PAYMENT_REQUIRED;
+ if (old_backup_missing)
+ return SYNC_DB_OLD_BACKUP_MISSING;
+ if (old_backup_mismatch)
+ return SYNC_DB_OLD_BACKUP_MISMATCH;
+ if (no_change)
+ return SYNC_DB_NO_RESULTS;
+ GNUNET_break (0);
+ return SYNC_DB_HARD_ERROR;
default:
GNUNET_break (0);
return SYNC_DB_HARD_ERROR;
}
-
- /* had an existing backup, is it identical? */
- if (0 == GNUNET_memcmp (&bh,
- backup_hash))
- {
- /* backup identical to what was provided, no change */
- return SYNC_DB_NO_RESULTS;
- }
- if (0 == GNUNET_memcmp (&bh,
- old_backup_hash))
- /* all constraints seem satisfied, original error must
- have been a hard error */
- return SYNC_DB_HARD_ERROR;
- /* previous backup does not match old_backup_hash */
- return SYNC_DB_OLD_BACKUP_MISMATCH;
}
diff --git a/src/syncdb/syncdb_update_backup_TR.sql b/src/syncdb/syncdb_update_backup_TR.sql
@@ -0,0 +1,97 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 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 sync_do_update_backup;
+CREATE FUNCTION sync_do_update_backup (
+ IN in_account_pub BYTEA,
+ IN in_old_backup_hash BYTEA,
+ IN in_account_sig BYTEA,
+ IN in_backup_hash BYTEA,
+ IN in_backup_size INT4,
+ IN in_data BYTEA,
+ OUT out_updated BOOLEAN,
+ OUT out_payment_required BOOLEAN,
+ OUT out_old_backup_missing BOOLEAN,
+ OUT out_old_backup_mismatch BOOLEAN,
+ OUT out_no_change BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_existing_hash BYTEA;
+BEGIN
+ out_updated = FALSE;
+ out_payment_required = FALSE;
+ out_old_backup_missing = FALSE;
+ out_old_backup_mismatch = FALSE;
+ out_no_change = FALSE;
+
+ -- Try to update the backup (matches on account_pub AND old backup_hash)
+ UPDATE backups
+ SET backup_hash=in_backup_hash
+ ,account_sig=in_account_sig
+ ,prev_hash=in_old_backup_hash
+ ,data=in_data
+ WHERE account_pub=in_account_pub
+ AND backup_hash=in_old_backup_hash;
+
+ IF FOUND
+ THEN
+ out_updated = TRUE;
+ RETURN;
+ END IF;
+
+ -- Update failed; check if account exists
+ PERFORM 1
+ FROM accounts
+ WHERE account_pub=in_account_pub;
+
+ IF NOT FOUND
+ THEN
+ out_payment_required = TRUE;
+ RETURN;
+ END IF;
+
+ -- Account exists; check existing backup hash
+ SELECT backup_hash
+ INTO my_existing_hash
+ FROM backups
+ WHERE account_pub=in_account_pub;
+
+ IF NOT FOUND
+ THEN
+ out_old_backup_missing = TRUE;
+ RETURN;
+ END IF;
+
+ -- Had an existing backup; is the new hash identical?
+ IF my_existing_hash = in_backup_hash
+ THEN
+ out_no_change = TRUE;
+ RETURN;
+ END IF;
+
+ -- Check if existing hash matches expected old hash
+ IF my_existing_hash = in_old_backup_hash
+ THEN
+ -- All constraints satisfied but update failed => hard error
+ -- (this shouldn't normally happen)
+ RAISE EXCEPTION 'unexpected state: backup hash matches but update failed';
+ END IF;
+
+ -- Previous backup does not match old_backup_hash
+ out_old_backup_mismatch = TRUE;
+END $$;