commit a13d30ecd89da7cf234f6a2bab9d16d5a785d553
parent 41b3950cde9ea9a6fe7e0d5c668c7a768b3b6ad4
Author: Christian Grothoff <christian@grothoff.org>
Date: Fri, 7 Aug 2026 00:17:59 +0200
fix history PK, also give clear indication of unique constraint violation on insert
Diffstat:
5 files changed, 92 insertions(+), 5 deletions(-)
diff --git a/src/donaudb/donau-0003.sql b/src/donaudb/donau-0003.sql
@@ -0,0 +1,34 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 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/>
+--
+
+BEGIN;
+
+SELECT _v.register_patch('donau-0003', NULL, NULL);
+SET search_path TO donau;
+
+-- The `history' table is documented as holding "the yearly donation amount
+-- for each charity", but its primary key was `charity_id' alone, so it could
+-- hold at most ONE year per charity: the second year was a unique violation.
+-- The key must include the year.
+ALTER TABLE history
+ DROP CONSTRAINT history_pkey;
+ALTER TABLE history
+ ADD CONSTRAINT history_pkey PRIMARY KEY (charity_id, donation_year);
+
+COMMENT ON COLUMN history.donation_year
+ IS 'Year the final_amount was accumulated in; part of the primary key.';
+
+COMMIT;
diff --git a/src/donaudb/insert_history_entry.c b/src/donaudb/insert_history_entry.c
@@ -38,7 +38,17 @@ DONAUDB_insert_history_entry (struct DONAUDB_PostgresContext *ctx,
GNUNET_PQ_query_param_uint64 (&donation_year),
GNUNET_PQ_query_param_end
};
+ uint64_t out_charity_id;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("charity_id",
+ &out_charity_id),
+ GNUNET_PQ_result_spec_end
+ };
+ /* The RETURNING clause is what makes "written" distinguishable from
+ "a row for this (charity,year) already exists": without it, the
+ unique violation would be mapped to SUCCESS_NO_RESULTS, which is
+ also what a caller checking only for `qs < 0' reads as success. */
PREPARE (ctx,
"insert_history_entry",
"INSERT INTO history "
@@ -46,8 +56,11 @@ DONAUDB_insert_history_entry (struct DONAUDB_PostgresContext *ctx,
",final_amount"
",donation_year"
") VALUES "
- "($1, $2, $3);");
- return GNUNET_PQ_eval_prepared_non_select (ctx->conn,
- "insert_history_entry",
- params);
+ "($1, $2, $3)"
+ " ON CONFLICT (charity_id, donation_year) DO NOTHING"
+ " RETURNING charity_id;");
+ return GNUNET_PQ_eval_prepared_singleton_select (ctx->conn,
+ "insert_history_entry",
+ params,
+ rs);
}
diff --git a/src/donaudb/meson.build b/src/donaudb/meson.build
@@ -30,6 +30,7 @@ generated_sql = [
['procedures.sql', procedures_sql],
['donau-0001.sql', ['donau-0001.sql']],
['donau-0002.sql', donau_0002],
+ ['donau-0003.sql', ['donau-0003.sql']],
]
diff --git a/src/donaudb/test_donaudb.c b/src/donaudb/test_donaudb.c
@@ -474,6 +474,42 @@ run (void *cls)
DONAUDB_commit (ctx));
}
+ /* D-5: `history' is documented as holding the *yearly* total per
+ charity, so it must be able to hold more than one year per charity,
+ and a repeated write for the same year must be distinguishable from
+ a successful one. */
+ {
+ uint64_t hist_charity_id;
+ struct TALER_Amount y1;
+ struct TALER_Amount y2;
+
+ FAILIF (GNUNET_OK !=
+ make_charity (CURRENCY ":1000",
+ &hist_charity_id));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":90",
+ &y1));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":75",
+ &y2));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ DONAUDB_insert_history_entry (ctx,
+ hist_charity_id,
+ &y1,
+ 2026));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ DONAUDB_insert_history_entry (ctx,
+ hist_charity_id,
+ &y2,
+ 2027));
+ /* Re-recording the same year must report "nothing written". */
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ DONAUDB_insert_history_entry (ctx,
+ hist_charity_id,
+ &y2,
+ 2027));
+ }
+
result = 0;
drop:
diff --git a/src/include/donau-database/insert_history_entry.h b/src/include/donau-database/insert_history_entry.h
@@ -31,7 +31,10 @@
* @param charity_id charity id
* @param final_amount final donation amount at the end of the donation year
* @param donation_year year of the donations
- * @return transaction status code
+ * @return transaction status code;
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the entry was written,
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if an entry for that
+ * charity and year already existed (nothing was written)
*/
enum GNUNET_DB_QueryStatus
DONAUDB_insert_history_entry (