merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit c9722efbdf7cec7c3c28aff9abe082db56a14fcf
parent 775973575eb5237db6b4eccefc2dea0809e1845d
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Mon, 24 Mar 2025 12:36:36 +0100

Merge branch 'master' into dev/bohdan-potuzhnyi/donau-integration

Diffstat:
Adebian/taler-merchant.taler-merchant-dbinit-gc.service | 6++++++
Adebian/taler-merchant.taler-merchant-dbinit-gc.timer | 11+++++++++++
Msrc/backend/taler-merchant-depositcheck.c | 23+++++++++++++++++++----
Msrc/backend/taler-merchant-httpd.c | 2++
Msrc/backend/taler-merchant-httpd_post-orders-ID-abort.c | 68++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/backend/taler-merchant-httpd_post-orders-ID-paid.c | 9++++++++-
Msrc/backend/taler-merchant-httpd_post-orders-ID-pay.c | 9+++++----
Msrc/backend/taler-merchant-httpd_private-get-orders-ID.c | 27+++++++++++----------------
Msrc/backend/taler-merchant-httpd_private-get-transfers.c | 13++++++++++++-
Msrc/backend/taler-merchant-httpd_private-post-orders.c | 6+++---
Msrc/backenddb/Makefile.am | 2++
Asrc/backenddb/example-statistics-0001.sql | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backenddb/merchant-0001.sql | 50++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/backenddb/merchant-0006.sql | 4++++
Msrc/backenddb/merchant-0013.sql | 16+++++++++++-----
Asrc/backenddb/merchant-0014.sql | 589+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_gc.c | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_gc.h | 37+++++++++++++++++++++++++++++++++++++
Msrc/backenddb/pg_mark_contract_paid.c | 7++++++-
Msrc/backenddb/pg_mark_contract_paid.h | 4+++-
Asrc/backenddb/pg_statistics_examples.sql | 200+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_statistics_helpers.sql | 1072+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backenddb/pg_template.c | 2+-
Msrc/backenddb/pg_template.h | 2+-
Msrc/backenddb/plugin_merchantdb_postgres.c | 5+++--
Msrc/backenddb/procedures.sql.in | 17++++++++++++++++-
Msrc/backenddb/test_merchantdb.c | 3++-
Msrc/include/taler_merchantdb_plugin.h | 752++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/merchant-tools/taler-merchant-dbinit.c | 32+++++++++++++++++++++++++++-----
29 files changed, 2723 insertions(+), 452 deletions(-)

diff --git a/debian/taler-merchant.taler-merchant-dbinit-gc.service b/debian/taler-merchant.taler-merchant-dbinit-gc.service @@ -0,0 +1,6 @@ +[Unit] +Description=Job to remove stale data from the taler-merchant database (run as a timer) + +[Service] +Type=simple +ExecStart=taler-merchant-dbinit -c /etc/taler-merchant/taler-merchant.conf -g diff --git a/debian/taler-merchant.taler-merchant-dbinit-gc.timer b/debian/taler-merchant.taler-merchant-dbinit-gc.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Run taler-merchant-dbinit-gc daily + +[Timer] +# First time 1h after boot +OnBootSec=60min +# Run daily +OnUnitActiveSec=24h + +[Install] +WantedBy=timers.target diff --git a/src/backend/taler-merchant-depositcheck.c b/src/backend/taler-merchant-depositcheck.c @@ -608,8 +608,7 @@ pending_deposits_cb ( const struct TALER_Amount *deposit_fee, const struct TALER_CoinSpendPublicKeyP *coin_pub) { - struct ExchangeInteraction *w - = GNUNET_new (struct ExchangeInteraction); + struct ExchangeInteraction *w; (void) cls; if (GNUNET_TIME_absolute_is_future (wire_deadline)) @@ -620,6 +619,7 @@ pending_deposits_cb ( run_at (wire_deadline); return; } + w = GNUNET_new (struct ExchangeInteraction); w->deposit_serial = deposit_serial; w->wire_deadline = wire_deadline; w->retry_backoff = GNUNET_TIME_STD_BACKOFF (retry_backoff); @@ -769,7 +769,21 @@ start_worker (const char *base_url) GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Launching worker for exchange `%s' using `%s`\n", base_url, - cfg_filename); + NULL == cfg_filename + ? "<default>" + : cfg_filename); + if (NULL == cfg_filename) + return GNUNET_OS_start_process ( + GNUNET_OS_INHERIT_STD_ALL, + NULL, + NULL, + NULL, + "taler-merchant-depositcheck", + "taler-merchant-depositcheck", + "-e", base_url, + "-L", "INFO", + test_mode ? "-t" : NULL, + NULL); return GNUNET_OS_start_process ( GNUNET_OS_INHERIT_STD_ALL, NULL, @@ -992,7 +1006,8 @@ run (void *cls, (void) args; cfg = c; - cfg_filename = GNUNET_strdup (cfgfile); + if (NULL != cfgfile) + cfg_filename = GNUNET_strdup (cfgfile); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Running with configuration %s\n", cfgfile); diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c @@ -1648,12 +1648,14 @@ url_handler (void *cls, { GNUNET_break (0); MHD_destroy_response (response); + GNUNET_free (instance_id); return MHD_NO; } ret = MHD_queue_response (connection, MHD_HTTP_PERMANENT_REDIRECT, response); MHD_destroy_response (response); + GNUNET_free (instance_id); return ret; } hc->instance = TMH_lookup_instance (instance_id); diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c @@ -383,35 +383,36 @@ generate_success_response (struct AbortContext *ac) GNUNET_JSON_pack_string ("type", "undeposited")); } - else if (MHD_HTTP_OK != rdi->http_status) + else { - detail = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("type", - "failure"), - GNUNET_JSON_pack_uint64 ("exchange_status", - rdi->http_status), - GNUNET_JSON_pack_uint64 ("exchange_code", - (NULL != rdi->exchange_reply) - ? - TALER_JSON_get_error_code ( - rdi->exchange_reply) - : - TALER_EC_GENERIC_INVALID_RESPONSE), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_incref ("exchange_reply", - rdi->exchange_reply))); - } - else if (rdi->found_deposit) - { - detail = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("type", - "success"), - GNUNET_JSON_pack_uint64 ("exchange_status", - rdi->http_status), - GNUNET_JSON_pack_data_auto ("exchange_sig", - &rdi->exchange_sig), - GNUNET_JSON_pack_data_auto ("exchange_pub", - &rdi->exchange_pub)); + if (MHD_HTTP_OK != rdi->http_status) + { + detail = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "failure"), + GNUNET_JSON_pack_uint64 ("exchange_status", + rdi->http_status), + GNUNET_JSON_pack_uint64 ("exchange_code", + (NULL != rdi->exchange_reply) + ? TALER_JSON_get_error_code ( + rdi->exchange_reply) + : TALER_EC_GENERIC_INVALID_RESPONSE), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("exchange_reply", + rdi->exchange_reply))); + } + else + { + detail = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "success"), + GNUNET_JSON_pack_uint64 ("exchange_status", + rdi->http_status), + GNUNET_JSON_pack_data_auto ("exchange_sig", + &rdi->exchange_sig), + GNUNET_JSON_pack_data_auto ("exchange_pub", + &rdi->exchange_pub)); + } } GNUNET_assert (0 == json_array_append_new (refunds, @@ -893,10 +894,13 @@ parse_abort (struct MHD_Connection *connection, if (0 == ac->coins_cnt) { GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_COINS_ARRAY_EMPTY, - "coins"); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_COINS_ARRAY_EMPTY, + "coins")) + ? GNUNET_NO + : GNUNET_SYSERR; } /* note: 1 coin = 1 deposit confirmation expected */ ac->pending = ac->coins_cnt; diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-paid.c b/src/backend/taler-merchant-httpd_post-orders-ID-paid.c @@ -91,6 +91,7 @@ TMH_post_orders_ID_paid (const struct TMH_RequestHandler *rh, const char *order_id = hc->infix; struct TALER_MerchantSignatureP merchant_sig; const char *session_id; + int16_t choice_index; struct TALER_PrivateContractHashP hct; json_t *contract_terms; const char *fulfillment_url; @@ -104,10 +105,15 @@ TMH_post_orders_ID_paid (const struct TMH_RequestHandler *rh, &hct), GNUNET_JSON_spec_string ("session_id", &session_id), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_int16 ("choice_index", + &choice_index), + NULL), GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; + choice_index = -1; res = TALER_MHD_parse_json_data (connection, hc->request_body, spec); @@ -202,7 +208,8 @@ TMH_post_orders_ID_paid (const struct TMH_RequestHandler *rh, qs = TMH_db->mark_contract_paid (TMH_db->cls, hc->instance->settings.id, &hct, - session_id); + session_id, + choice_index); /* If the order was paid already, we get qs == 0. */ if (0 > qs) { diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -509,7 +509,7 @@ struct PayContext /** * Index of selected choice in the @e contract_terms choices array. */ - int64_t choice_index; + int16_t choice_index; /** * Number of token envelopes passed to this request. @@ -2389,7 +2389,8 @@ phase_execute_pay_transaction (struct PayContext *pc) qs = TMH_db->mark_contract_paid (TMH_db->cls, instance_id, &pc->check_contract.h_contract_terms, - pc->parse_pay.session_id); + pc->parse_pay.session_id, + pc->parse_wallet_data.choice_index); if (qs < 0) { TMH_db->rollback (TMH_db->cls); @@ -3353,7 +3354,7 @@ phase_check_contract (struct PayContext *pc) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Order `%s' has choices array with %u elements but " - "request has 'choice_index' field with value %ld\n", + "request has 'choice_index' field with value %d\n", pc->order_id, pc->check_contract.contract_terms->details.v1.choices_len, pc->parse_wallet_data.choice_index); @@ -3452,7 +3453,7 @@ phase_parse_wallet_data (struct PayContext *pc) struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_int64 ("choice_index", + GNUNET_JSON_spec_int16 ("choice_index", &pc->parse_wallet_data.choice_index), NULL), GNUNET_JSON_spec_mark_optional ( diff --git a/src/backend/taler-merchant-httpd_private-get-orders-ID.c b/src/backend/taler-merchant-httpd_private-get-orders-ID.c @@ -745,6 +745,13 @@ phase_parse_contract (struct GetOrderRequestContext *gorc) gorc->contract_amount = gorc->contract_terms->details.v1.choices[gorc->choice_index].amount; } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "choice index %i for order %s is invalid or not yet available", + gorc->choice_index, + gorc->contract_terms->order_id); + } break; default: { @@ -1426,7 +1433,6 @@ phase_reply_result (struct GetOrderRequestContext *gorc) struct TMH_HandlerContext *hc = gorc->hc; MHD_RESULT ret; char *order_status_url; - json_t *choice_index; GNUNET_assert (gorc->paid); GNUNET_assert (! gorc->order_only); @@ -1457,17 +1463,6 @@ phase_reply_result (struct GetOrderRequestContext *gorc) TALER_amount_is_zero (&gorc->contract_amount)); gorc->last_payment = gorc->contract_terms->timestamp; } - if (-1 != gorc->choice_index) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Choice index is %d\n", - gorc->choice_index); - choice_index = json_integer ((json_int_t) gorc->choice_index); - } - else - { - choice_index = NULL; - } ret = TALER_MHD_REPLY_JSON_PACK ( gorc->sc.con, MHD_HTTP_OK, @@ -1507,10 +1502,10 @@ phase_reply_result (struct GetOrderRequestContext *gorc) gorc->refund_details), GNUNET_JSON_pack_string ("order_status_url", order_status_url), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_steal ( - "choice_index", - choice_index))); + (gorc->choice_index >= 0) + ? GNUNET_JSON_pack_int64 ("choice_index", + gorc->choice_index) + : GNUNET_JSON_pack_end_ ()); GNUNET_free (order_status_url); gorc->wire_details = NULL; gorc->refund_details = NULL; diff --git a/src/backend/taler-merchant-httpd_private-get-transfers.c b/src/backend/taler-merchant-httpd_private-get-transfers.c @@ -129,10 +129,14 @@ TMH_private_get_transfers (const struct TMH_RequestHandler *rh, (GNUNET_OK != GNUNET_STRINGS_fancy_time_to_timestamp (before_s, &before)) ) + { + GNUNET_break_op (0); + GNUNET_free (payto_uri.full_payto); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, "before"); + } } { const char *after_s; @@ -144,10 +148,14 @@ TMH_private_get_transfers (const struct TMH_RequestHandler *rh, (GNUNET_OK != GNUNET_STRINGS_fancy_time_to_timestamp (after_s, &after)) ) + { + GNUNET_break_op (0); + GNUNET_free (payto_uri.full_payto); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, "after"); + } } TALER_MHD_parse_request_snumber (connection, "limit", @@ -163,11 +171,14 @@ TMH_private_get_transfers (const struct TMH_RequestHandler *rh, "verified", TALER_EXCHANGE_YNA_ALL, &verified)) ) + { + GNUNET_break_op (0); + GNUNET_free (payto_uri.full_payto); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, "verified"); - + } TMH_db->preflight (TMH_db->cls); { json_t *ja; diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c @@ -78,7 +78,7 @@ * refuses a forced download. */ #define MAX_KEYS_WAIT \ - GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 2500) + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 2500) /** * Generate the base URL for the given merchant instance. @@ -1534,8 +1534,8 @@ add_input_token_family (struct OrderContext *oc, struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); struct GNUNET_TIME_Timestamp end = oc->parse_order.pay_deadline; enum GNUNET_DB_QueryStatus qs; - enum TALER_ErrorCode ec; - unsigned int http_status; + enum TALER_ErrorCode ec = TALER_EC_INVALID; /* make compiler happy */ + unsigned int http_status = 0; /* make compiler happy */ qs = TMH_db->lookup_token_family_keys (TMH_db->cls, oc->hc->instance->settings.id, diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am @@ -31,6 +31,7 @@ sql_DATA = \ merchant-0012.sql \ merchant-0013.sql \ merchant-0014.sql \ + merchant-0015.sql \ drop.sql BUILT_SOURCES = \ @@ -94,6 +95,7 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \ pg_delete_transfer.h pg_delete_transfer.c \ pg_delete_webhook.h pg_delete_webhook.c \ pg_expire_locks.h pg_expire_locks.c \ + pg_gc.h pg_gc.c \ pg_get_kyc_limits.h pg_get_kyc_limits.c \ pg_get_kyc_status.h pg_get_kyc_status.c \ pg_helper.h pg_helper.c \ diff --git a/src/backenddb/example-statistics-0001.sql b/src/backenddb/example-statistics-0001.sql @@ -0,0 +1,140 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2025 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 example-statistics-0001.sql +-- @brief examples for how to add statistics to the merchant backend +-- @author Christian Grothoff + +-- Everything in one big transaction +BEGIN; + +-- Check patch versioning is in place. +SELECT _v.register_patch('example-statistics-0001', NULL, NULL); + +CREATE SCHEMA example_statistics; + +SET search_path TO example_statistics; + + +-- This is for now just an example for how to use the API. --- BEGIN EXAMPLE + +-- Setup statistic: what do we want to track for 'deposits'? +-- (Note: this is basically the one "manual" step we might not keep hard-coded) +INSERT INTO merchant.merchant_statistic_bucket_meta + (slug + ,description + ,stype + ,ranges + ,ages) +VALUES + ('deposits' + ,'sales (before refunds)' + ,'amount' + ,ARRAY['second'::statistic_range, 'minute' 'day', 'month', 'quarter', 'year'] + ,ARRAY[120, 120, 95, 36, 40, 100] -- track last 120 s, 120 minutes, 95 days, 36 months, 40 quarters & 100 years + ); + +INSERT INTO merchant.merchant_statistic_interval_meta + (slug + ,description + ,stype + ,ranges + ,precisions) +VALUES + ('deposits' + ,'sales (before refunds)' + ,'amount' + ,ARRAY[1,60, 24 * 60 * 60, 30 * 24 * 60 * 60, 365 * 24 * 60 * 60] -- second, minute, day, month, year + ,ARRAY[1,1, 60, 60 * 60, 24 * 60 * 60] -- second, second, minute, hour, day + ); + +CREATE FUNCTION merchant_deposits_statistics_trigger() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +DECLARE + my_instance INT8; +BEGIN + SELECT mct.merchant_serial + INTO my_instance + FROM merchant_contract_terms mct + JOIN merchant_deposit_confirmations mdc + USING (order_serial) + WHERE mdc.deposit_confirmation_serial = NEW.deposit_confirmation_serial; + CALL merchant_do_bump_amount_stat + ('deposits' + ,my_instance + ,NEW.amount_with_fee); + RETURN NEW; +END $$; +COMMENT ON FUNCTION merchant_deposits_statistics_trigger + IS 'adds the deposited amount to the deposit statistics'; + +-- Whenever a deposit is made, call our trigger to bump statistics +CREATE TRIGGER merchant_deposits_on_insert + AFTER INSERT + ON merchant.merchant_deposits + FOR EACH ROW EXECUTE FUNCTION merchant_deposits_statistics_trigger(); + +-- This is for now just an example for how to use the API. --- END EXAMPLE + + + +-- This is just another example for how to use the API. -- BEGIN EXAMPLE + +-- Setup statistic +-- (Note: this is basically the one "manual" step we might not keep hard-coded) +INSERT INTO merchant.merchant_statistic_bucket_meta + (slug + ,description + ,stype + ,ranges + ,ages) +VALUES + ('products-sold' + ,'products sold (only those tracked in inventory)' + ,'number' + ,ARRAY['second'::statistic_range, 'minute' 'day', 'week', 'month', 'quarter', 'year'] + ,ARRAY[120, 120, 60, 12, 24, 8, 10] -- track last 120s, 120 minutes, 60 days, 12 weeks, 24 months, 8 quarters and 10 years + ); + +CREATE FUNCTION merchant_products_sold_statistics_trigger() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +DECLARE + my_sold INT8; +BEGIN + my_sold = NEW.total_sold - OLD.total_sold; + IF (0 < my_sold) + THEN + CALL merchant_do_bump_number_stat + ('products-sold' + ,NEW.merchant_serial + ,my_sold); + END IF; + RETURN NEW; +END $$; + +-- Whenever inventory changes, call our trigger to bump statistics +CREATE TRIGGER merchant_products_on_sold + AFTER UPDATE + ON merchant.merchant_inventory + FOR EACH ROW EXECUTE FUNCTION merchant_products_sold_statistics_trigger(); + +-- This is for now just an example for how to use the API. --- END EXAMPLE + +COMMIT; diff --git a/src/backenddb/merchant-0001.sql b/src/backenddb/merchant-0001.sql @@ -182,6 +182,10 @@ COMMENT ON COLUMN merchant_accounts.credit_facade_credentials IS 'JSON with credentials needed to access the credit facade'; COMMENT ON COLUMN merchant_accounts.last_bank_serial IS 'Serial number of the bank of the last transaction we successfully imported'; +COMMENT ON COLUMN merchant_accounts.account_serial + IS 'Unique identifier for this account, used to reference this account from other tables'; +COMMENT ON COLUMN merchant_accounts.merchant_serial + IS 'Merchant instance that controls this bank account'; -------------------------- Inventory --------------------------- @@ -404,6 +408,14 @@ COMMENT ON TABLE merchant_deposit_confirmations IS 'Table with the deposit confirmations for each coin we deposited at the exchange'; COMMENT ON COLUMN merchant_deposit_confirmations.signkey_serial IS 'Online signing key of the exchange on the deposit confirmation'; +COMMENT ON COLUMN merchant_deposit_confirmations.account_serial + IS 'Identifies the bank account of the merchant that will receive the payment'; +COMMENT ON COLUMN merchant_deposit_confirmations.exchange_url + IS 'Which exchange received the deposit; a single contract may be paid by multiple (batch) deposits to multiple exchanges'; +COMMENT ON COLUMN merchant_deposit_confirmations.order_serial + IS 'Identifies the entry in the contract_terms table that was paid by this deposit'; +COMMENT ON COLUMN merchant_deposit_confirmations.total_without_fee + IS 'Total amount that was (batch) deposited, excluding deposit fees (irrespective of who had to pay the fees); so this is the amount the merchant will be credited for this deposit (minus applicable wire fees after aggregation)'; COMMENT ON COLUMN merchant_deposit_confirmations.deposit_timestamp IS 'Time when the exchange generated the deposit confirmation'; COMMENT ON COLUMN merchant_deposit_confirmations.exchange_sig @@ -451,7 +463,7 @@ CREATE TABLE IF NOT EXISTS merchant_refunds ,refund_amount taler_amount_currency NOT NULL ,UNIQUE (order_serial, coin_pub, rtransaction_id) ); -COMMENT ON TABLE merchant_deposits +COMMENT ON TABLE merchant_refunds IS 'Refunds approved by the merchant (backoffice) logic, excludes abort refunds'; COMMENT ON COLUMN merchant_refunds.rtransaction_id IS 'Needed for uniqueness in case a refund is increased for the same order'; @@ -489,6 +501,16 @@ CREATE TABLE IF NOT EXISTS merchant_transfers ); COMMENT ON TABLE merchant_transfers IS 'table represents the information provided by the (trusted) merchant about incoming wire transfers'; +COMMENT ON COLUMN merchant_transfers.credit_serial + IS 'Unique identifier for this wire transfer in this backend'; +COMMENT ON COLUMN merchant_transfers.account_serial + IS 'Merchant bank account that received this wire transfer; also implies the merchant instance implicated by the wire transfer'; +COMMENT ON COLUMN merchant_transfers.ready_time + IS 'Time when we should next inquire at the exchange about this wire transfer; used by taler-merchant-reconciliation to limit retries with the exchange in case of failures'; +COMMENT ON COLUMN merchant_transfers.exchange_url + IS 'Base URL of the exchange that originated the wire transfer as extracted from the wire transfer subject'; +COMMENT ON COLUMN merchant_transfers.wtid + IS 'Unique wire transfer identifier (or at least, should be unique by protocol) as selected by the exchange and extracted from the wire transfer subject'; COMMENT ON COLUMN merchant_transfers.verified IS 'true once we got an acceptable response from the exchange for this transfer'; COMMENT ON COLUMN merchant_transfers.confirmed @@ -520,6 +542,14 @@ CREATE TABLE IF NOT EXISTS merchant_transfer_signatures ); COMMENT ON TABLE merchant_transfer_signatures IS 'table represents the main information returned from the /transfer request to the exchange.'; +COMMENT ON COLUMN merchant_transfer_signatures.credit_serial + IS 'wire transfer this signature is about'; +COMMENT ON COLUMN merchant_transfer_signatures.signkey_serial + IS 'Online signing key by the exchange that was used for the exchange_sig signature'; +COMMENT ON COLUMN merchant_transfer_signatures.wire_fee + IS 'wire fee charged by the exchange for this transfer'; +COMMENT ON COLUMN merchant_transfer_signatures.exchange_sig + IS 'signature by the exchange of purpose TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT'; COMMENT ON COLUMN merchant_transfer_signatures.execution_time IS 'Execution time as claimed by the exchange, roughly matches time seen by merchant'; COMMENT ON COLUMN merchant_transfer_signatures.credit_amount @@ -540,6 +570,12 @@ CREATE INDEX IF NOT EXISTS merchant_transfers_by_credit (credit_serial); COMMENT ON TABLE merchant_transfer_to_coin IS 'Mapping of (credit) transfers to (deposited) coins'; +COMMENT ON COLUMN merchant_transfer_to_coin.deposit_serial + IS 'Identifies the deposited coin that the wire transfer presumably settles'; +COMMENT ON COLUMN merchant_transfer_to_coin.credit_serial + IS 'Identifies the wire transfer that settles the given deposited coin'; +COMMENT ON COLUMN merchant_transfer_to_coin.offset_in_exchange_list + IS 'The exchange settlement data includes an array of the settled coins; this is the index of the coin in that list, useful to reconstruct the correct sequence of coins as needed to check the exchange signature'; COMMENT ON COLUMN merchant_transfer_to_coin.exchange_deposit_value IS 'Deposit value as claimed by the exchange, should match our values in merchant_deposits minus refunds'; COMMENT ON COLUMN merchant_transfer_to_coin.exchange_deposit_fee @@ -558,9 +594,19 @@ CREATE TABLE IF NOT EXISTS merchant_deposit_to_transfer ,UNIQUE(deposit_serial,credit_serial) ); COMMENT ON TABLE merchant_deposit_to_transfer - IS 'Mapping of deposits to (possibly unconfirmed) wire transfers; NOTE: not used yet'; + IS 'Mapping of deposits to (possibly unconfirmed) wire transfers; note: currently taler-merchant-depositcheck only INSERTs into this table, but we never use it'; +COMMENT ON COLUMN merchant_deposit_to_transfer.deposit_serial + IS 'identifies the deposited coin for which we inquired about the wire transfer status at the exchange'; +COMMENT ON COLUMN merchant_deposit_to_transfer.coin_contribution_value + IS 'Contribution of this coin to the overall wire transfer made by the exchange'; +COMMENT ON COLUMN merchant_deposit_to_transfer.credit_serial + IS 'Identifies the wire transfer from the exchange to the merchant that settled the deposit of coin'; COMMENT ON COLUMN merchant_deposit_to_transfer.execution_time IS 'Execution time as claimed by the exchange, roughly matches time seen by merchant'; +COMMENT ON COLUMN merchant_deposit_to_transfer.signkey_serial + IS 'Identifies the online signing key of the exchange used to make the exchange_sig'; +COMMENT ON COLUMN merchant_deposit_to_transfer.exchange_sig + IS 'Exchange signature of purpose TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE'; -------------------------- Rewards --------------------------- diff --git a/src/backenddb/merchant-0006.sql b/src/backenddb/merchant-0006.sql @@ -37,6 +37,8 @@ CREATE TABLE IF NOT EXISTS merchant_categories ,category_name_i18n BYTEA NOT NULL ); +COMMENT ON TABLE merchant_categories + IS 'product categories (with translations) to group products from inventory (primarily for the point-of-sale app)'; COMMENT ON COLUMN merchant_categories.category_name IS 'name of the category'; COMMENT ON COLUMN merchant_categories.category_name_i18n @@ -52,6 +54,8 @@ CREATE INDEX merchant_categories_by_category CREATE INDEX merchant_categories_by_product ON merchant_product_categories (product_serial); +COMMENT ON TABLE merchant_product_categories + IS 'N:M map from products to categories (a product can be in any number of categories, including zero)'; COMMENT ON COLUMN merchant_product_categories.category_serial IS 'Reference to a category the product is part of'; COMMENT ON COLUMN merchant_product_categories.product_serial diff --git a/src/backenddb/merchant-0013.sql b/src/backenddb/merchant-0013.sql @@ -69,7 +69,7 @@ COMMENT ON COLUMN merchant_token_family_keys.private_key_created_at IS 'Specifies when the private key was created. Not terribly useful, mostly for debugging.'; -- Function to replace placeholders in a string with a given value -CREATE OR REPLACE FUNCTION replace_placeholder( +CREATE FUNCTION replace_placeholder( template TEXT, key TEXT, value TEXT @@ -84,8 +84,7 @@ BEGIN END; $$ LANGUAGE plpgsql; --- Trigger function to handle pending webhooks for category changes -CREATE OR REPLACE FUNCTION handle_category_changes() +CREATE FUNCTION handle_category_changes() RETURNS TRIGGER AS $$ DECLARE my_merchant_serial BIGINT; @@ -230,6 +229,10 @@ BEGIN END; $$ LANGUAGE plpgsql; +COMMENT ON FUNCTION handle_category_changes + IS 'Trigger function to handle pending webhooks for category changes'; + + -- Trigger to invoke the trigger function CREATE TRIGGER trigger_category_changes AFTER INSERT OR UPDATE OR DELETE @@ -237,8 +240,7 @@ ON merchant_categories FOR EACH ROW EXECUTE FUNCTION handle_category_changes(); --- Function to handle inventory changes and notify webhooks -CREATE OR REPLACE FUNCTION handle_inventory_changes() +CREATE FUNCTION handle_inventory_changes() RETURNS TRIGGER AS $$ DECLARE my_merchant_serial BIGINT; @@ -525,6 +527,10 @@ BEGIN END; $$ LANGUAGE plpgsql; +COMMENT ON FUNCTION handle_inventory_changes + IS 'Function to handle inventory changes and notify webhooks'; + + -- Trigger to invoke the trigger function CREATE TRIGGER trigger_inventory_changes AFTER INSERT OR UPDATE OR DELETE diff --git a/src/backenddb/merchant-0014.sql b/src/backenddb/merchant-0014.sql @@ -0,0 +1,589 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2025 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 merchant-0014.sql +-- @brief Tables for statistics +-- @author Christian Grothoff + + +BEGIN; + +-- Check patch versioning is in place. +SELECT _v.register_patch('merchant-0014', NULL, NULL); + +SET search_path TO merchant; + +-- Ranges given here must be supported by the date_trunc function of Postgresql! +CREATE TYPE statistic_range AS + ENUM('century', 'decade', 'year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second'); + +CREATE TYPE statistic_type AS + ENUM('amount', 'number'); + +-- -------------- Bucket statistics --------------------- + +CREATE TABLE merchant_statistic_bucket_meta + (bmeta_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY + ,slug TEXT NOT NULL + ,description TEXT NOT NULL + ,stype statistic_type NOT NULL + ,ranges statistic_range[] NOT NULL + ,ages INT4[] NOT NULL + ,UNIQUE(slug,stype) + ,CONSTRAINT equal_array_length + CHECK (array_length(ranges,1) = + array_length(ages,1)) + ); +COMMENT ON TABLE merchant_statistic_bucket_meta + IS 'meta data about a statistic with events falling into buckets we are tracking'; +COMMENT ON COLUMN merchant_statistic_bucket_meta.bmeta_serial_id + IS 'unique identifier for this type of bucket statistic we are tracking'; +COMMENT ON COLUMN merchant_statistic_bucket_meta.slug + IS 'keyword (or name) of the statistic; identifies what the statistic is about; should be a slug suitable for a URI path'; +COMMENT ON COLUMN merchant_statistic_bucket_meta.description + IS 'description of the statistic being tracked'; +COMMENT ON COLUMN merchant_statistic_bucket_meta.stype + IS 'statistic type, what kind of data is being tracked, amount or number'; +COMMENT ON COLUMN merchant_statistic_bucket_meta.ranges + IS 'size of the buckets that are being kept for this statistic'; +COMMENT ON COLUMN merchant_statistic_bucket_meta.ages + IS 'determines how long into the past we keep buckets for the range at the given index around (in generations)'; + + +CREATE TABLE merchant_statistic_bucket_counter + (bmeta_serial_id INT8 NOT NULL + REFERENCES merchant_statistic_bucket_meta (bmeta_serial_id) ON DELETE CASCADE + ,merchant_serial BIGINT NOT NULL + REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE + ,bucket_start INT8 NOT NULL + ,bucket_range statistic_range NOT NULL + ,cumulative_number INT8 NOT NULL + ,UNIQUE (bmeta_serial_id,merchant_serial,bucket_start,bucket_range) + ); +COMMENT ON TABLE merchant_statistic_bucket_counter + IS 'various numeric statistics (cumulative counters) being tracked by bucket into which they fall'; +COMMENT ON COLUMN merchant_statistic_bucket_counter.bmeta_serial_id + IS 'identifies what the statistic is about'; +COMMENT ON COLUMN merchant_statistic_bucket_counter.merchant_serial + IS 'identifies the instance for which the statistic is kept'; +COMMENT ON COLUMN merchant_statistic_bucket_counter.bucket_start + IS 'start date for the bucket in seconds since the epoch'; +COMMENT ON COLUMN merchant_statistic_bucket_counter.bucket_range + IS 'range of the bucket'; +COMMENT ON COLUMN merchant_statistic_bucket_counter.cumulative_number + IS 'aggregate (sum) of tracked by the statistic; what exactly is tracked is determined by the keyword'; + + +CREATE TABLE merchant_statistic_bucket_amount + (bmeta_serial_id INT8 NOT NULL + REFERENCES merchant_statistic_bucket_meta (bmeta_serial_id) ON DELETE CASCADE + ,merchant_serial BIGINT NOT NULL + REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE + ,bucket_start INT8 NOT NULL + ,bucket_range statistic_range NOT NULL + ,curr VARCHAR(12) NOT NULL + ,cumulative_value INT8 NOT NULL + ,cumulative_frac INT4 NOT NULL + ,UNIQUE (bmeta_serial_id,merchant_serial,curr,bucket_start,bucket_range) + ); +COMMENT ON TABLE merchant_statistic_bucket_amount + IS 'various amount statistics (in various currencies) being tracked'; +COMMENT ON COLUMN merchant_statistic_bucket_amount.bmeta_serial_id + IS 'identifies what the statistic is about'; +COMMENT ON COLUMN merchant_statistic_bucket_amount.merchant_serial + IS 'identifies the instance for which the statistic is kept'; +COMMENT ON COLUMN merchant_statistic_bucket_amount.bucket_start + IS 'start date for the bucket in seconds since the epoch'; +COMMENT ON COLUMN merchant_statistic_bucket_amount.bucket_range + IS 'range of the bucket'; +COMMENT ON COLUMN merchant_statistic_bucket_amount.curr + IS 'currency which this statistic is tracking the amount for'; +COMMENT ON COLUMN merchant_statistic_bucket_amount.cumulative_value + IS 'amount in the respective currency, non-fractional amount value'; +COMMENT ON COLUMN merchant_statistic_bucket_amount.cumulative_frac + IS 'amount in the respective currency, fraction in units of 1/100000000 of the base value'; + + +-- -------------- Interval statistics --------------------- + + +CREATE TABLE merchant_statistic_interval_meta + (imeta_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY + ,slug TEXT NOT NULL + ,description TEXT NOT NULL + ,stype statistic_type NOT NULL + ,ranges INT8[] NOT NULL CHECK (array_length(ranges,1) > 0) + ,precisions INT8[] NOT NULL CHECK (array_length(precisions,1) > 0) + ,UNIQUE(slug,stype) + ,CONSTRAINT equal_array_length + CHECK (array_length(ranges,1) = + array_length(precisions,1)) + ); +COMMENT ON TABLE merchant_statistic_interval_meta + IS 'meta data about an interval statistic we are tracking'; +COMMENT ON COLUMN merchant_statistic_interval_meta.imeta_serial_id + IS 'unique identifier for this type of interval statistic we are tracking'; +COMMENT ON COLUMN merchant_statistic_interval_meta.slug + IS 'keyword (or name) of the statistic; identifies what the statistic is about; should be a slug suitable for a URI path'; +COMMENT ON COLUMN merchant_statistic_interval_meta.description + IS 'description of the statistic being tracked'; +COMMENT ON COLUMN merchant_statistic_interval_meta.stype + IS 'statistic type, what kind of data is being tracked, amount or number'; +COMMENT ON COLUMN merchant_statistic_interval_meta.ranges + IS 'range of values that is being kept for this statistic, in seconds, must be monotonically increasing'; +COMMENT ON COLUMN merchant_statistic_interval_meta.precisions + IS 'determines how precisely we track which events fall into the range at the same index (allowing us to coalesce events with timestamps in proximity close to the given precision), in seconds, 0 is not allowed'; + +CREATE TABLE merchant_statistic_counter_event + (nevent_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY + ,imeta_serial_id INT8 + REFERENCES merchant_statistic_interval_meta (imeta_serial_id) ON DELETE CASCADE + ,merchant_serial BIGINT NOT NULL + REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE + ,slot INT8 NOT NULL + ,delta INT8 NOT NULL + ,UNIQUE (imeta_serial_id, merchant_serial, slot) + ); +COMMENT ON TABLE merchant_statistic_counter_event + IS 'number to decrement an interval statistic by when a certain time value is reached'; +COMMENT ON COLUMN merchant_statistic_counter_event.nevent_serial_id + IS 'unique identifier for this number event'; +COMMENT ON COLUMN merchant_statistic_counter_event.imeta_serial_id + IS 'identifies what the statistic is about; must be of stype number'; +COMMENT ON COLUMN merchant_statistic_counter_event.merchant_serial + IS 'identifies which merchant instance the event is about'; +COMMENT ON COLUMN merchant_statistic_counter_event.slot + IS 'identifies the time slot at which the given event(s) happened, rounded down by the respective precisions value'; +COMMENT ON COLUMN merchant_statistic_counter_event.delta + IS 'total cumulative number that was added at the time identified by slot'; + +CREATE TABLE merchant_statistic_interval_counter + (imeta_serial_id INT8 NOT NULL + REFERENCES merchant_statistic_interval_meta (imeta_serial_id) ON DELETE CASCADE + ,merchant_serial BIGINT NOT NULL + REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE + ,range INT8 NOT NULL + ,event_delimiter INT8 NOT NULL + REFERENCES merchant_statistic_counter_event (nevent_serial_id) ON DELETE RESTRICT + ,cumulative_number INT8 NOT NULL + ,UNIQUE (imeta_serial_id,merchant_serial,range) + ); +COMMENT ON TABLE merchant_statistic_interval_counter + IS 'various numeric statistics (cumulative counters) being tracked'; +COMMENT ON COLUMN merchant_statistic_interval_counter.imeta_serial_id + IS 'identifies what the statistic is about'; +COMMENT ON COLUMN merchant_statistic_interval_counter.merchant_serial + IS 'identifies the instance for which the statistic is kept'; +COMMENT ON COLUMN merchant_statistic_interval_counter.range + IS 'for which range is this the counter; note that the cumulative_number excludes the values already stored in smaller ranges'; +COMMENT ON COLUMN merchant_statistic_interval_counter.event_delimiter + IS 'determines the last event currently included in the interval'; +COMMENT ON COLUMN merchant_statistic_interval_counter.cumulative_number + IS 'aggregate (sum) of tracked by the statistic; what exactly is tracked is determined by the keyword'; + + +CREATE TABLE merchant_statistic_amount_event + (aevent_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY + ,imeta_serial_id INT8 + REFERENCES merchant_statistic_interval_meta (imeta_serial_id) ON DELETE CASCADE + ,merchant_serial BIGINT NOT NULL + REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE + ,slot INT8 NOT NULL + ,delta_curr VARCHAR(12) NOT NULL + ,delta_value INT8 NOT NULL + ,delta_frac INT4 NOT NULL + ,CONSTRAINT event_key UNIQUE (imeta_serial_id, merchant_serial, delta_curr, slot) + ); +COMMENT ON TABLE merchant_statistic_amount_event + IS 'amount to decrement an interval statistic by when a certain time value is reached'; +COMMENT ON COLUMN merchant_statistic_amount_event.aevent_serial_id + IS 'unique identifier for this amount event'; +COMMENT ON COLUMN merchant_statistic_amount_event.imeta_serial_id + IS 'identifies what the statistic is about; must be of clazz interval and of stype amount'; +COMMENT ON COLUMN merchant_statistic_amount_event.merchant_serial + IS 'identifies which merchant instance the event is about'; +COMMENT ON COLUMN merchant_statistic_amount_event.slot + IS 'identifies the time slot at which the given event(s) happened'; +COMMENT ON COLUMN merchant_statistic_amount_event.delta_curr + IS 'currency of the total cumulative amount that was added at the time identified by slot'; +COMMENT ON COLUMN merchant_statistic_amount_event.delta_value + IS 'total cumulative amount (value) that was added at the time identified by slot'; +COMMENT ON COLUMN merchant_statistic_amount_event.delta_frac + IS 'total cumulative amount (fraction) that was added at the time identified by slot'; + + +CREATE TABLE merchant_statistic_interval_amount + (imeta_serial_id INT8 NOT NULL + REFERENCES merchant_statistic_interval_meta (imeta_serial_id) ON DELETE CASCADE + ,merchant_serial BIGINT NOT NULL + REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE + ,event_delimiter INT8 NOT NULL + REFERENCES merchant_statistic_amount_event (aevent_serial_id) ON DELETE RESTRICT + ,range INT8 NOT NULL + ,curr VARCHAR(12) NOT NULL + ,cumulative_value INT8 NOT NULL + ,cumulative_frac INT4 NOT NULL + ,UNIQUE (imeta_serial_id,merchant_serial,curr,range) + ); +COMMENT ON TABLE merchant_statistic_interval_amount + IS 'various amount statistics (in various currencies) being tracked'; +COMMENT ON COLUMN merchant_statistic_interval_amount.imeta_serial_id + IS 'identifies what the statistic is about'; +COMMENT ON COLUMN merchant_statistic_interval_amount.merchant_serial + IS 'identifies the instance for which the statistic is kept'; +COMMENT ON COLUMN merchant_statistic_interval_amount.range + IS 'for which range is this the counter; note that the cumulative_number excludes the values already stored in smaller ranges'; +COMMENT ON COLUMN merchant_statistic_interval_amount.curr + IS 'currency which this statistic is tracking the amount for'; +COMMENT ON COLUMN merchant_statistic_interval_amount.cumulative_value + IS 'amount in the respective currency, non-fractional amount value'; +COMMENT ON COLUMN merchant_statistic_interval_amount.cumulative_frac + IS 'amount in the respective currency, fraction in units of 1/100000000 of the base value'; + +CREATE TYPE merchant_statistic_interval_number_get_return_value + AS + (range INT8 + ,rvalue INT8 + ); +COMMENT ON TYPE merchant_statistic_interval_number_get_return_value + IS 'Return type for merchant_statistic_interval_number_get stored procedure'; + +CREATE TYPE merchant_statistic_interval_amount_get_return_value + AS + (range INT8 + ,rvalue taler_amount_currency + ); +COMMENT ON TYPE merchant_statistic_interval_amount_get_return_value + IS 'Return type for merchant_statistic_interval_amount_get stored procedure'; + +-- ---------------- Actual statistics below --------------------- + + +CREATE FUNCTION merchant_orders_insert_statistics_trigger() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +BEGIN + CALL merchant_do_bump_number_stat + ('orders-created' + ,NEW.merchant_serial + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,1); + RETURN NEW; +END $$; + +-- Whenever an order is created, call our trigger to bump statistics +CREATE TRIGGER merchant_orders_on_insert_statistic + AFTER INSERT + ON merchant_orders + FOR EACH ROW EXECUTE FUNCTION merchant_orders_insert_statistics_trigger(); + + +CREATE FUNCTION merchant_contract_terms_insert_statistics_trigger() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +BEGIN + CALL merchant_do_bump_number_stat + ('orders-claimed' + ,NEW.merchant_serial + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,1); + RETURN NEW; +END $$; + +-- Whenever an order is created, call our trigger to bump statistics +CREATE TRIGGER merchant_contract_terms_on_insert_statistic + AFTER INSERT + ON merchant_contract_terms + FOR EACH ROW EXECUTE FUNCTION merchant_contract_terms_insert_statistics_trigger(); + + +CREATE FUNCTION merchant_contract_terms_update_statistics_trigger() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +DECLARE + my_rec RECORD; +BEGIN + IF (NEW.wired AND NOT OLD.wired) + THEN + CALL merchant_do_bump_number_stat + ('orders-settled' + ,NEW.merchant_serial + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,1); + END IF; + IF (NEW.paid AND NOT OLD.paid) + THEN + CALL merchant_do_bump_number_stat + ('orders-paid' + ,NEW.merchant_serial + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,1); + FOR my_rec IN + SELECT total_without_fee + FROM merchant_deposit_confirmations + WHERE order_serial = NEW.order_serial + LOOP + CALL merchant_do_bump_amount_stat + ('payments-received-after-deposit-fee' + ,NEW.merchant_serial + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,my_rec.total_without_fee); + END LOOP; + FOR my_rec IN + SELECT deposit_fee + FROM merchant_deposits + WHERE deposit_confirmation_serial IN + (SELECT deposit_confirmation_serial + FROM merchant_deposit_confirmations + WHERE order_serial = NEW.order_serial) + LOOP + CALL merchant_do_bump_amount_stat + ('total-deposit-fees-paid' + ,NEW.merchant_serial + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,my_rec.deposit_fee); + END LOOP; + END IF; + RETURN NEW; +END $$; + +-- Whenever a contract is updated, call our trigger to bump statistics +CREATE TRIGGER merchant_contract_terms_on_update_statistic + AFTER UPDATE + ON merchant_contract_terms + FOR EACH ROW EXECUTE FUNCTION merchant_contract_terms_update_statistics_trigger(); + + +CREATE FUNCTION merchant_refunds_insert_statistics_trigger() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +DECLARE + my_merchant_serial INT8; +BEGIN + SELECT merchant_serial + INTO my_merchant_serial + FROM merchant_contract_terms + WHERE order_serial = NEW.order_serial; + CALL merchant_do_bump_amount_stat + ('refunds-granted' + ,my_merchant_serial + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,NEW.refund_amount); + RETURN NEW; +END $$; + +-- Whenever a refund is granted created, call our trigger to bump statistics +CREATE TRIGGER merchant_refunds_on_insert_statistic + AFTER INSERT + ON merchant_refunds + FOR EACH ROW EXECUTE FUNCTION merchant_refunds_insert_statistics_trigger(); + + +CREATE FUNCTION merchant_transfer_signatures_insert_statistics_trigger() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +DECLARE + my_merchant_serial INT8; +BEGIN + SELECT merchant_serial + INTO my_merchant_serial + FROM merchant_accounts + WHERE account_serial = + (SELECT account_serial + FROM merchant_transfers + WHERE credit_serial = NEW.credit_serial); + CALL merchant_do_bump_amount_stat + ('wire-fees-paid' + ,my_merchant_serial + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,NEW.wire_fee); + RETURN NEW; +END $$; + +-- Whenever a refund is granted created, call our trigger to bump statistics +CREATE TRIGGER merchant_transfer_signatures_on_insert_statistic + AFTER INSERT + ON merchant_transfer_signatures + FOR EACH ROW EXECUTE FUNCTION merchant_transfer_signatures_insert_statistics_trigger(); + + +CREATE FUNCTION merchant_issued_tokens_insert_statistics_trigger() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +DECLARE + my_merchant_serial INT8; +BEGIN + SELECT merchant_serial + INTO my_merchant_serial + FROM merchant_token_families + WHERE token_family_serial = + (SELECT token_family_serial + FROM merchant_token_family_keys + WHERE token_family_key_serial = NEW.token_family_key_serial); + CALL merchant_do_bump_number_stat + ('tokens-issued' + ,my_merchant_serial + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,1); + RETURN NEW; +END $$; + +-- Whenever a token is issued, call our trigger to bump statistics +CREATE TRIGGER merchant_issued_tokens_on_insert_statistic + AFTER INSERT + ON merchant_issued_tokens + FOR EACH ROW EXECUTE FUNCTION merchant_issued_tokens_insert_statistics_trigger(); + + +CREATE FUNCTION merchant_used_tokens_insert_statistics_trigger() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +DECLARE + my_merchant_serial INT8; +BEGIN + SELECT merchant_serial + INTO my_merchant_serial + FROM merchant_token_families + WHERE token_family_serial = + (SELECT token_family_serial + FROM merchant_token_family_keys + WHERE token_family_key_serial = NEW.token_family_key_serial); + CALL merchant_do_bump_number_stat + ('tokens-used' + ,my_merchant_serial + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,1); + RETURN NEW; +END $$; + +-- Whenever a token is used, call our trigger to bump statistics +CREATE TRIGGER merchant_used_tokens_on_insert_statistic + AFTER INSERT + ON merchant_used_tokens + FOR EACH ROW EXECUTE FUNCTION merchant_used_tokens_insert_statistics_trigger(); + +-- Enable interval statistics +INSERT INTO merchant_statistic_interval_meta + (slug + ,description + ,stype + ,ranges + ,precisions) +VALUES + ('orders-created' + ,'number of orders created (but not necessarily claimed by wallets)' + ,'number' + ,ARRAY(SELECT generate_series (1, 60*60, 60)) -- for last hour, per minute + || ARRAY(SELECT generate_series (60*60*2, 60*60*48, 60 * 60)) -- for last 2 days, per hour + ,array_fill (5, ARRAY[60]) -- precision: 5s + || array_fill (5 * 60, ARRAY[48-1]) -- precision: 5 minutes + ), + ('orders-claimed' + ,'number of orders claimed by a wallet (but not necessarily paid)' + ,'number' + ,ARRAY(SELECT generate_series (1, 60*60, 60)) -- for last hour, per minute + || ARRAY(SELECT generate_series (60*60*2, 60*60*48, 60 * 60)) -- for last 2 days, per hour + ,array_fill (5, ARRAY[60]) -- precision: 5s + || array_fill (5 * 60, ARRAY[48-1]) -- precision: 5 minutes + ), + ('orders-paid' + ,'number of orders paid (but not necessarily settled by the exchange)' + ,'number' + ,ARRAY(SELECT generate_series (1, 60*60, 60)) -- for last hour, per minute + || ARRAY(SELECT generate_series (60*60*2, 60*60*48, 60 * 60)) -- for last 2 days, per hour + ,array_fill (5, ARRAY[60]) -- precision: 5s + || array_fill (5 * 60, ARRAY[48-1]) -- precision: 5 minutes + ), + ('orders-settled' + ,'number of orders settled' + ,'number' + ,ARRAY(SELECT generate_series (1, 60*60, 60)) -- for last hour, per minute + || ARRAY(SELECT generate_series (60*60*2, 60*60*48, 60 * 60)) -- for last 2 days, per hour + ,array_fill (5, ARRAY[60]) -- precision: 5s + || array_fill (5 * 60, ARRAY[48-1]) -- precision: 5 minutes + ), + ('tokens-issued' + ,'number of tokens issued to customers' + ,'number' + ,ARRAY(SELECT generate_series (1, 60*60, 60)) -- for last hour, per minute + || ARRAY(SELECT generate_series (60*60*2, 60*60*48, 60 * 60)) -- for last 2 days, per hour + ,array_fill (5, ARRAY[60]) -- precision: 5s + || array_fill (5 * 60, ARRAY[48-1]) -- precision: 5 minutes + ), + ('tokens-used' + ,'number of tokens used by customers' + ,'number' + ,ARRAY(SELECT generate_series (1, 60*60, 60)) -- for last hour, per minute + || ARRAY(SELECT generate_series (60*60*2, 60*60*48, 60 * 60)) -- for last 2 days, per hour + ,array_fill (5, ARRAY[60]) -- precision: 5s + || array_fill (5 * 60, ARRAY[48-1]) -- precision: 5 minutes + ); + +-- Enable bucket statistics +INSERT INTO merchant_statistic_bucket_meta + (slug + ,description + ,stype + ,ranges + ,ages) +VALUES + ('payments-received-after-deposit-fee' + ,'amount customers paid to us (excluded deposit fees paid by us or customers, wire fees are still deducted by the exchange)' + ,'amount' + ,ARRAY['hour'::statistic_range, 'day', 'week', 'month', 'quarter', 'year'] + ,ARRAY[72, 14, 12, 24, 12, 10] + ), + ('total-deposit-fees-paid' + ,'deposit fees we or our customers paid to the exchange (includes those waived on refunds)' + ,'amount' + ,ARRAY['hour'::statistic_range, 'day', 'week', 'month', 'quarter', 'year'] + ,ARRAY[72, 14, 12, 24, 12, 10] + ), + ('total-wire-fees-paid' + ,'wire fees we paid to the exchange' + ,'amount' + ,ARRAY['hour'::statistic_range, 'day', 'week', 'month', 'quarter', 'year'] + ,ARRAY[72, 12, 12, 24, 12, 10] + ), + ('refunds-granted' + ,'refunds granted by us to our customers' + ,'amount' + ,ARRAY['hour'::statistic_range, 'day', 'week', 'month', 'quarter', 'year'] + ,ARRAY[72, 14, 12, 24, 12, 10] + ), + ('tokens-issued' + ,'number of tokens issued to customers' + ,'number' + ,ARRAY['hour'::statistic_range, 'day', 'week', 'month', 'quarter', 'year'] + ,ARRAY[72, 14, 12, 24, 12, 10] + ), + ('tokens-used' + ,'number of tokens used by customers' + ,'number' + ,ARRAY['hour'::statistic_range, 'day', 'week', 'month', 'quarter', 'year'] + ,ARRAY[72, 14, 12, 24, 12, 10] + ); + + + +COMMIT; diff --git a/src/backenddb/pg_gc.c b/src/backenddb/pg_gc.c @@ -0,0 +1,67 @@ +/* + This file is part of TALER + Copyright (C) 2025 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 backenddb/pg_gc.c + * @brief Implementation of the gc function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_gc.h" +#include "pg_helper.h" + + +enum GNUNET_GenericReturnValue +TMH_PG_gc (void *cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_Context *conn; + enum GNUNET_GenericReturnValue ret; + + { + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_try_execute ("SET search_path TO merchant;"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + struct GNUNET_PQ_PreparedStatement ps[] = { + GNUNET_PQ_make_prepare ("run_gc", + "CALL merchant_do_gc ();"), + GNUNET_PQ_PREPARED_STATEMENT_END + }; + + conn = GNUNET_PQ_connect_with_cfg (pg->cfg, + "merchantdb-postgres", + NULL, + es, + ps); + } + if (NULL == conn) + return GNUNET_SYSERR; + + if (0 > GNUNET_PQ_eval_prepared_non_select (conn, + "run_gc", + params)) + ret = GNUNET_SYSERR; + else + ret = GNUNET_OK; + GNUNET_PQ_disconnect (conn); + return ret; +} diff --git a/src/backenddb/pg_gc.h b/src/backenddb/pg_gc.h @@ -0,0 +1,37 @@ +/* + 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/> + */ +/** + * @file backenddb/pg_gc.h + * @brief implementation of the gc function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_GC_H +#define PG_GC_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Garbage collect database. Removes unnecessary data. + * + * @param cls closure + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +enum GNUNET_GenericReturnValue +TMH_PG_gc (void *cls); + +#endif diff --git a/src/backenddb/pg_mark_contract_paid.c b/src/backenddb/pg_mark_contract_paid.c @@ -30,13 +30,17 @@ TMH_PG_mark_contract_paid ( void *cls, const char *instance_id, const struct TALER_PrivateContractHashP *h_contract_terms, - const char *session_id) + const char *session_id, + int16_t choice_index) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_string (session_id), + (choice_index >= 0) + ? GNUNET_PQ_query_param_int16 (&choice_index) + : GNUNET_PQ_query_param_null (), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_QueryParam uparams[] = { @@ -60,6 +64,7 @@ TMH_PG_mark_contract_paid ( "UPDATE merchant_contract_terms SET" " paid=TRUE" ",session_id=$3" + ",choice_index=$4" " WHERE h_contract_terms=$2" " AND merchant_serial=" " (SELECT merchant_serial" diff --git a/src/backenddb/pg_mark_contract_paid.h b/src/backenddb/pg_mark_contract_paid.h @@ -34,6 +34,7 @@ * @param instance_id instance to mark contract as paid for * @param h_contract_terms hash of the contract that is now paid * @param session_id the session that paid the contract + * @param choice_index the contract v1 choice index to select, -1 for v0 * @return transaction status */ enum GNUNET_DB_QueryStatus @@ -41,6 +42,7 @@ TMH_PG_mark_contract_paid (void *cls, const char *instance_id, const struct TALER_PrivateContractHashP *h_contract_terms, - const char *session_id); + const char *session_id, + int16_t choice_index); #endif diff --git a/src/backenddb/pg_statistics_examples.sql b/src/backenddb/pg_statistics_examples.sql @@ -0,0 +1,200 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2025 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 pg_statistics_example.sql +-- @brief example/test case for how to add statistics to the merchant backend, NOT for production! +-- @author Christian Grothoff + +-- Everything in one big transaction +BEGIN; + +-- Check patch versioning is in place. +-- SELECT _v.register_patch('example-statistics-0001', NULL, NULL); + +CREATE SCHEMA IF NOT EXISTS example_statistics; + +SET search_path TO example_statistics,merchant; + + +-- Setup statistic: what do we want to track for 'deposits'? +-- (Note: this is basically the one "manual" step we might not keep hard-coded) +INSERT INTO merchant_statistic_bucket_meta + (slug + ,description + ,stype + ,ranges + ,ages) +VALUES + ('deposits' + ,'sales (before refunds)' + ,'amount' + ,ARRAY['second'::statistic_range, 'minute', 'day', 'month', 'quarter', 'year'] + ,ARRAY[120, 120, 95, 36, 40, 100] -- track last 120 s, 120 minutes, 95 days, 36 months, 40 quarters & 100 years + ) +ON CONFLICT DO NOTHING; + +INSERT INTO merchant_statistic_interval_meta + (slug + ,description + ,stype + ,ranges + ,precisions) +VALUES + ('deposits' + ,'sales (before refunds)' + ,'amount' + ,generate_series (1, 10, 1) || generate_series (60, 180, 60), + ,array_fill (1, 10) || array_fill (5, 3)] + ), + ('products-sold' + ,'number of products sold' + ,'number' + ,generate_series (1, 10, 1) || [60], + ,array_fill (1, 10) || [10] + ) +ON CONFLICT DO NOTHING; + +DROP FUNCTION IF EXISTS merchant_deposits_statistics_trigger CASCADE; +CREATE FUNCTION merchant_deposits_statistics_trigger() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +DECLARE + my_instance INT8; +BEGIN +-- SET search_path TO merchant; + SELECT mct.merchant_serial + INTO my_instance + FROM merchant_contract_terms mct + JOIN merchant_deposit_confirmations mdc + USING (order_serial) + WHERE mdc.deposit_confirmation_serial = NEW.deposit_confirmation_serial; + CALL merchant_do_bump_amount_stat + ('deposits' + ,my_instance + ,CURRENT_TIMESTAMP(0) + ,NEW.amount_with_fee); + RETURN NEW; +END $$; +COMMENT ON FUNCTION merchant_deposits_statistics_trigger + IS 'adds the deposited amount to the deposit statistics'; + +DO $$ +DECLARE + rec RECORD; +BEGIN +FOR rec IN + SELECT 'deposits' AS in_slug + ,mct.merchant_serial AS in_merchant_serial + ,TO_TIMESTAMP (mdc.deposit_timestamp / 1000.0 / 1000.0)::TIMESTAMP AS in_timestamp + ,mdc.total_without_fee AS in_delta + FROM merchant_deposit_confirmations mdc + JOIN merchant_contract_terms mct + USING (order_serial) + WHERE mdc.deposit_timestamp > (EXTRACT(EPOCH FROM CURRENT_TIMESTAMP(0)) - 365*24*60*60) * 1000000 +LOOP + CALL merchant_do_bump_amount_stat (rec.in_slug, rec.in_merchant_serial, rec.in_timestamp, rec.in_delta); +END LOOP; +END $$; + +-- Whenever a deposit is made, call our trigger to bump statistics +CREATE TRIGGER merchant_deposits_on_insert + AFTER INSERT + ON merchant_deposits + FOR EACH ROW EXECUTE FUNCTION merchant_deposits_statistics_trigger(); + + + + +-- Setup statistic +-- (Note: this is basically the one "manual" step we might not keep hard-coded) +INSERT INTO merchant_statistic_bucket_meta + (slug + ,description + ,stype + ,ranges + ,ages) +VALUES + ('products-sold' + ,'products sold (only those tracked in inventory)' + ,'number' + ,ARRAY['second'::statistic_range, 'minute', 'day', 'week', 'month', 'quarter', 'year'] + ,ARRAY[120, 120, 60, 12, 24, 8, 10] -- track last 120s, 120 minutes, 60 days, 12 weeks, 24 months, 8 quarters and 10 years + ) +ON CONFLICT DO NOTHING; + +DROP FUNCTION IF EXISTS merchant_products_sold_statistics_trigger CASCADE; +CREATE FUNCTION merchant_products_sold_statistics_trigger() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +DECLARE + my_sold INT8; +BEGIN +-- SET search_path TO merchant; + my_sold = NEW.total_sold - OLD.total_sold; + IF (0 < my_sold) + THEN + CALL merchant_do_bump_number_stat + ('products-sold' + ,NEW.merchant_serial + ,CURRENT_TIMESTAMP(0) + ,my_sold); + END IF; + RETURN NEW; +END $$; + +-- Whenever inventory changes, call our trigger to bump statistics +CREATE TRIGGER merchant_products_on_sold + AFTER UPDATE + ON merchant_inventory + FOR EACH ROW EXECUTE FUNCTION merchant_products_sold_statistics_trigger(); + +delete from merchant.merchant_statistic_bucket_counter ; +delete from merchant.merchant_statistic_bucket_amount ; +delete from merchant.merchant_statistic_interval_counter; +delete from merchant.merchant_statistic_interval_amount; +delete from merchant.merchant_statistic_amount_event; +delete from merchant.merchant_statistic_counter_event; + + +call merchant_do_bump_number_stat ('products-sold'::text, 6, CURRENT_TIMESTAMP(0)::TIMESTAMP-INTERVAL '2 minutes', 1); +call merchant_do_bump_amount_stat ('deposits'::text, 6, CURRENT_TIMESTAMP(0)::TIMESTAMP-INTERVAL '2 minutes', (1,1,'EUR')::taler_amount_currency); +call merchant_do_bump_number_stat ('products-sold'::text, 6, CURRENT_TIMESTAMP(0)::TIMESTAMP-INTERVAL '2 seconds', 2); +call merchant_do_bump_amount_stat ('deposits'::text, 6, CURRENT_TIMESTAMP(0)::TIMESTAMP-INTERVAL '1 minute', (2,2,'EUR')::taler_amount_currency); +call merchant_do_bump_amount_stat ('deposits'::text, 6, CURRENT_TIMESTAMP(0)::TIMESTAMP-INTERVAL '2 seconds', (4,4,'EUR')::taler_amount_currency); +call merchant_do_bump_amount_stat ('deposits'::text, 6, CURRENT_TIMESTAMP(0)::TIMESTAMP-INTERVAL '1 second', (8,8,'EUR')::taler_amount_currency); +call merchant_do_bump_number_stat ('products-sold'::text, 6, CURRENT_TIMESTAMP(0)::TIMESTAMP, 4); +call merchant_do_bump_amount_stat ('deposits'::text, 6, CURRENT_TIMESTAMP(0)::TIMESTAMP, (16,16,'EUR')::taler_amount_currency); + +select * from merchant_statistic_interval_number_get ('products-sold', 'default'); + +select * from merchant_statistic_interval_amount_get ('deposits', 'default'); + +select * from merchant.merchant_statistic_amount_event; + +select * from merchant.merchant_statistic_counter_event; + +select * from merchant.merchant_statistic_interval_counter; + +select * from merchant.merchant_statistic_interval_amount; + +select * from merchant.merchant_statistic_bucket_counter ; + +select * from merchant.merchant_statistic_bucket_amount ; + +-- ROLLBACK; +COMMIT; diff --git a/src/backenddb/pg_statistics_helpers.sql b/src/backenddb/pg_statistics_helpers.sql @@ -0,0 +1,1072 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2025 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/> +-- + +SET search_path TO merchant; +DROP FUNCTION IF EXISTS interval_to_start; +CREATE OR REPLACE FUNCTION interval_to_start ( + IN in_timestamp TIMESTAMP, + IN in_range statistic_range, + OUT out_bucket_start INT8 +) +LANGUAGE plpgsql +AS $$ +BEGIN + out_bucket_start = EXTRACT(EPOCH FROM DATE_TRUNC(in_range::text, in_timestamp)); +END $$; +COMMENT ON FUNCTION interval_to_start + IS 'computes the start time of the bucket for an event at the current time given the desired bucket range'; + + +DROP PROCEDURE IF EXISTS merchant_do_bump_number_bucket_stat; +CREATE OR REPLACE PROCEDURE merchant_do_bump_number_bucket_stat( + in_slug TEXT, + in_merchant_serial BIGINT, + in_timestamp TIMESTAMP, + in_delta INT8 +) +LANGUAGE plpgsql +AS $$ +DECLARE + my_meta INT8; + my_range statistic_range; + my_bucket_start INT8; + my_curs CURSOR (arg_slug TEXT) + FOR SELECT UNNEST(ranges) + FROM merchant_statistic_bucket_meta + WHERE slug=arg_slug; +BEGIN + SELECT bmeta_serial_id + INTO my_meta + FROM merchant_statistic_bucket_meta + WHERE slug=in_slug + AND stype='number'; + IF NOT FOUND + THEN + RETURN; + END IF; + OPEN my_curs (arg_slug:=in_slug); + LOOP + FETCH NEXT + FROM my_curs + INTO my_range; + EXIT WHEN NOT FOUND; + SELECT * + INTO my_bucket_start + FROM interval_to_start (in_timestamp, my_range); + + UPDATE merchant_statistic_bucket_counter + SET cumulative_number = cumulative_number + in_delta + WHERE bmeta_serial_id=my_meta + AND merchant_serial=in_merchant_serial + AND bucket_start=my_bucket_start + AND bucket_range=my_range; + IF NOT FOUND + THEN + INSERT INTO merchant_statistic_bucket_counter + (bmeta_serial_id + ,merchant_serial + ,bucket_start + ,bucket_range + ,cumulative_number + ) VALUES ( + my_meta + ,in_merchant_serial + ,my_bucket_start + ,my_range + ,in_delta); + END IF; + END LOOP; + CLOSE my_curs; +END $$; + + +DROP PROCEDURE IF EXISTS merchant_do_bump_amount_bucket_stat; +CREATE OR REPLACE PROCEDURE merchant_do_bump_amount_bucket_stat( + in_slug TEXT, + in_merchant_serial BIGINT, + in_timestamp TIMESTAMP, + in_delta taler_amount_currency +) +LANGUAGE plpgsql +AS $$ +DECLARE + my_meta INT8; + my_range statistic_range; + my_bucket_start INT8; + my_curs CURSOR (arg_slug TEXT) + FOR SELECT UNNEST(ranges) + FROM merchant_statistic_bucket_meta + WHERE slug=arg_slug; +BEGIN + SELECT bmeta_serial_id + INTO my_meta + FROM merchant_statistic_bucket_meta + WHERE slug=in_slug + AND stype='amount'; + IF NOT FOUND + THEN + RETURN; + END IF; + OPEN my_curs (arg_slug:=in_slug); + LOOP + FETCH NEXT + FROM my_curs + INTO my_range; + EXIT WHEN NOT FOUND; + SELECT * + INTO my_bucket_start + FROM interval_to_start (in_timestamp, my_range); + + UPDATE merchant_statistic_bucket_amount + SET + cumulative_value = cumulative_value + (in_delta).val + + CASE + WHEN (in_delta).frac + cumulative_frac >= 100000000 + THEN 1 + ELSE 0 + END, + cumulative_frac = cumulative_frac + (in_delta).frac + - CASE + WHEN (in_delta).frac + cumulative_frac >= 100000000 + THEN 100000000 + ELSE 0 + END + WHERE bmeta_serial_id=my_meta + AND merchant_serial=in_merchant_serial + AND curr=(in_delta).curr + AND bucket_start=my_bucket_start + AND bucket_range=my_range; + IF NOT FOUND + THEN + INSERT INTO merchant_statistic_bucket_amount + (bmeta_serial_id + ,merchant_serial + ,bucket_start + ,bucket_range + ,curr + ,cumulative_value + ,cumulative_frac + ) VALUES ( + my_meta + ,in_merchant_serial + ,my_bucket_start + ,my_range + ,(in_delta).curr + ,(in_delta).val + ,(in_delta).frac); + END IF; + END LOOP; + CLOSE my_curs; +END $$; + +COMMENT ON PROCEDURE merchant_do_bump_amount_bucket_stat + IS 'Updates an amount statistic tracked over buckets'; + + +DROP PROCEDURE IF EXISTS merchant_do_bump_number_interval_stat; +CREATE OR REPLACE PROCEDURE merchant_do_bump_number_interval_stat( + in_slug TEXT, + in_merchant_serial BIGINT, + in_timestamp TIMESTAMP, + in_delta INT8 +) +LANGUAGE plpgsql +AS $$ +DECLARE + my_now INT8; + my_record RECORD; + my_meta INT8; + my_ranges INT8[]; + my_precisions INT8[]; + my_rangex INT8; + my_precisionx INT8; + my_start INT8; + my_event INT8; +BEGIN + my_now = ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000; + SELECT imeta_serial_id + ,ranges AS ranges + ,precisions AS precisions + INTO my_record + FROM merchant_statistic_interval_meta + WHERE slug=in_slug + AND stype='number'; + IF NOT FOUND + THEN + RETURN; + END IF; + + my_start = ROUND(EXTRACT(epoch FROM in_timestamp) * 1000000)::INT8 / 1000 / 1000; -- convert to seconds + my_precisions = my_record.precisions; + my_ranges = my_record.ranges; + my_rangex = NULL; + FOR my_x IN 1..COALESCE(array_length(my_ranges,1),0) + LOOP + IF my_now - my_ranges[my_x] < my_start + THEN + my_rangex = my_ranges[my_x]; + my_precisionx = my_precisions[my_x]; + EXIT; + END IF; + END LOOP; + IF my_rangex IS NULL + THEN + -- event is beyond the ranges we care about + RETURN; + END IF; + + my_meta = my_record.imeta_serial_id; + my_start = my_start - my_start % my_precisionx; -- round down + + INSERT INTO merchant_statistic_counter_event AS msce + (imeta_serial_id + ,merchant_serial + ,slot + ,delta) + VALUES + (my_meta + ,in_merchant_serial + ,my_start + ,in_delta) + ON CONFLICT (imeta_serial_id, merchant_serial, slot) + DO UPDATE SET + delta = msce.delta + in_delta + RETURNING nevent_serial_id + INTO my_event; + + UPDATE merchant_statistic_interval_counter + SET cumulative_number = cumulative_number + in_delta + WHERE imeta_serial_id = my_meta + AND merchant_serial = in_merchant_serial + AND range=my_rangex; + IF NOT FOUND + THEN + INSERT INTO merchant_statistic_interval_counter + (imeta_serial_id + ,merchant_serial + ,range + ,event_delimiter + ,cumulative_number + ) VALUES ( + my_meta + ,in_merchant_serial + ,my_rangex + ,my_event + ,in_delta); + END IF; +END $$; + +COMMENT ON PROCEDURE merchant_do_bump_number_interval_stat + IS 'Updates a numeric statistic tracked over an interval'; + + +DROP PROCEDURE IF EXISTS merchant_do_bump_amount_interval_stat; +CREATE OR REPLACE PROCEDURE merchant_do_bump_amount_interval_stat( + in_slug TEXT, + in_merchant_serial BIGINT, + in_timestamp TIMESTAMP, + in_delta taler_amount_currency -- new amount in table that we should add to the tracker +) +LANGUAGE plpgsql +AS $$ +DECLARE + my_now INT8; + my_record RECORD; + my_meta INT8; + my_ranges INT8[]; + my_precisions INT8[]; + my_x INT; + my_rangex INT8; + my_precisionx INT8; + my_start INT8; + my_event INT8; +BEGIN + my_now = ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000; + SELECT imeta_serial_id + ,ranges + ,precisions + INTO my_record + FROM merchant_statistic_interval_meta + WHERE slug=in_slug + AND stype='amount'; + IF NOT FOUND + THEN + RETURN; + END IF; + + my_start = ROUND(EXTRACT(epoch FROM in_timestamp) * 1000000)::INT8 / 1000 / 1000; -- convert to seconds since epoch + my_precisions = my_record.precisions; + my_ranges = my_record.ranges; + my_rangex = NULL; + FOR my_x IN 1..COALESCE(array_length(my_ranges,1),0) + LOOP + IF my_now - my_ranges[my_x] < my_start + THEN + my_rangex = my_ranges[my_x]; + my_precisionx = my_precisions[my_x]; + EXIT; + END IF; + END LOOP; + IF my_rangex IS NULL + THEN + -- event is beyond the ranges we care about + RETURN; + END IF; + my_start = my_start - my_start % my_precisionx; -- round down + my_meta = my_record.imeta_serial_id; + + INSERT INTO merchant_statistic_amount_event AS msae + (imeta_serial_id + ,merchant_serial + ,slot + ,delta_curr + ,delta_value + ,delta_frac + ) VALUES ( + my_meta + ,in_merchant_serial + ,my_start + ,(in_delta).curr + ,(in_delta).val + ,(in_delta).frac + ) + ON CONFLICT (imeta_serial_id, merchant_serial, slot, delta_curr) + DO UPDATE SET + delta_value = msae.delta_value + (in_delta).val + + CASE + WHEN (in_delta).frac + msae.delta_frac >= 100000000 + THEN 1 + ELSE 0 + END, + delta_frac = msae.delta_frac + (in_delta).frac + - CASE + WHEN (in_delta).frac + msae.delta_frac >= 100000000 + THEN 100000000 + ELSE 0 + END + RETURNING aevent_serial_id + INTO my_event; + + UPDATE merchant_statistic_interval_amount + SET + cumulative_value = cumulative_value + (in_delta).val + + CASE + WHEN (in_delta).frac + cumulative_frac >= 100000000 + THEN 1 + ELSE 0 + END, + cumulative_frac = cumulative_frac + (in_delta).frac + - CASE + WHEN (in_delta).frac + cumulative_frac >= 100000000 + THEN 100000000 + ELSE 0 + END + WHERE imeta_serial_id=my_meta + AND merchant_serial=in_merchant_serial + AND range=my_rangex + AND curr=(in_delta).curr; + IF NOT FOUND + THEN + INSERT INTO merchant_statistic_interval_amount + (imeta_serial_id + ,merchant_serial + ,range + ,event_delimiter + ,curr + ,cumulative_value + ,cumulative_frac + ) VALUES ( + my_meta + ,in_merchant_serial + ,my_rangex + ,my_event + ,(in_delta).curr + ,(in_delta).val + ,(in_delta).frac); + END IF; +END $$; +COMMENT ON PROCEDURE merchant_do_bump_amount_interval_stat + IS 'Updates an amount statistic tracked over an interval'; + + +DROP PROCEDURE IF EXISTS merchant_do_bump_number_stat; +CREATE OR REPLACE PROCEDURE merchant_do_bump_number_stat( + in_slug TEXT, + in_merchant_serial BIGINT, + in_timestamp TIMESTAMP, + in_delta INT8 +) +LANGUAGE plpgsql +AS $$ +BEGIN + CALL merchant_do_bump_number_bucket_stat (in_slug, in_merchant_serial, in_timestamp, in_delta); + CALL merchant_do_bump_number_interval_stat (in_slug, in_merchant_serial, in_timestamp, in_delta); +END $$; +COMMENT ON PROCEDURE merchant_do_bump_number_stat + IS 'Updates a numeric statistic (bucket or interval)'; + + +DROP PROCEDURE IF EXISTS merchant_do_bump_amount_stat; +CREATE OR REPLACE PROCEDURE merchant_do_bump_amount_stat( + in_slug TEXT, + in_merchant_serial BIGINT, + in_timestamp TIMESTAMP, + in_delta taler_amount_currency +) +LANGUAGE plpgsql +AS $$ +BEGIN + CALL merchant_do_bump_amount_bucket_stat (in_slug, in_merchant_serial, in_timestamp, in_delta); + CALL merchant_do_bump_amount_interval_stat (in_slug, in_merchant_serial, in_timestamp, in_delta); +END $$; +COMMENT ON PROCEDURE merchant_do_bump_amount_stat + IS 'Updates an amount statistic (bucket or interval)'; + + +DROP FUNCTION IF EXISTS merchant_statistic_interval_number_get; +CREATE OR REPLACE FUNCTION merchant_statistic_interval_number_get ( + IN in_slug TEXT, + IN in_instance_id TEXT +) +RETURNS SETOF merchant_statistic_interval_number_get_return_value +LANGUAGE plpgsql +AS $$ +DECLARE + my_time INT8 DEFAULT ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000; + my_ranges INT8[]; + my_range INT8; + my_delta INT8; + my_meta INT8; + my_next_max_serial INT8; + my_instance_id INT8; + my_rec RECORD; + my_irec RECORD; + my_i INT; + my_min_serial INT8 DEFAULT NULL; + my_rval merchant_statistic_interval_number_get_return_value; +BEGIN + SELECT merchant_serial + INTO my_instance_id + FROM merchant_instances + WHERE merchant_id=in_instance_id; + IF NOT FOUND + THEN + RETURN; + END IF; + + SELECT imeta_serial_id + ,ranges + ,precisions + INTO my_rec + FROM merchant_statistic_interval_meta + WHERE slug=in_slug; + IF NOT FOUND + THEN + RETURN; + END IF; + my_rval.rvalue = 0; + my_ranges = my_rec.ranges; + my_meta = my_rec.imeta_serial_id; + + FOR my_i IN 1..COALESCE(array_length(my_ranges,1),0) + LOOP + my_range = my_ranges[my_i]; + SELECT event_delimiter + ,cumulative_number + INTO my_irec + FROM merchant_statistic_interval_counter + WHERE imeta_serial_id = my_meta + AND range = my_range + AND merchant_serial = my_instance_id; + IF FOUND + THEN + my_min_serial = my_irec.event_delimiter; + my_rval.rvalue = my_rval.rvalue + my_irec.cumulative_number; + + -- Check if we have events that left the applicable range + SELECT SUM(delta) AS delta_sum + INTO my_irec + FROM merchant_statistic_counter_event + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance_id + AND slot < my_time - my_range + AND nevent_serial_id >= my_min_serial; + + IF FOUND AND my_irec.delta_sum IS NOT NULL + THEN + my_delta = my_irec.delta_sum; + my_rval.rvalue = my_rval.rvalue - my_delta; + + -- First find out the next event delimiter value + SELECT nevent_serial_id + INTO my_next_max_serial + FROM merchant_statistic_counter_event + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance_id + AND slot >= my_time - my_range + AND nevent_serial_id >= my_min_serial + ORDER BY slot ASC + LIMIT 1; + + IF FOUND + THEN + -- remove expired events from the sum of the current slot + + UPDATE merchant_statistic_interval_counter + SET cumulative_number = cumulative_number - my_delta, + event_delimiter = my_next_max_serial + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance_id + AND range = my_range; + ELSE + -- actually, slot is now empty, remove it entirely + DELETE FROM merchant_statistic_interval_counter + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance_id + AND range = my_range; + END IF; + IF (my_i < array_length(my_ranges,1)) + THEN + -- carry over all events into the next slot + UPDATE merchant_statistic_interval_counter AS usic SET + cumulative_number = cumulative_number + my_delta, + event_delimiter = LEAST(usic.event_delimiter,my_min_serial) + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance_id + AND range=my_ranges[my_i+1]; + IF NOT FOUND + THEN + INSERT INTO merchant_statistic_interval_counter + (imeta_serial_id + ,merchant_serial + ,range + ,event_delimiter + ,cumulative_number + ) VALUES ( + my_meta + ,my_instance_id + ,my_ranges[my_i+1] + ,my_min_serial + ,my_delta); + END IF; + ELSE + -- events are obsolete, delete them + DELETE FROM merchant_statistic_counter_event + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance_id + AND slot < my_time - my_range; + END IF; + END IF; + + my_rval.range = my_range; + RETURN NEXT my_rval; + END IF; + END LOOP; +END $$; + +COMMENT ON FUNCTION merchant_statistic_interval_number_get + IS 'Returns deposit statistic tracking deposited amounts over certain time intervals; we first trim the stored data to only track what is still in-range, and then return the remaining value for each range'; + + +DROP FUNCTION IF EXISTS merchant_statistic_interval_amount_get; +CREATE OR REPLACE FUNCTION merchant_statistic_interval_amount_get ( + IN in_slug TEXT, + IN in_instance_id TEXT +) +RETURNS SETOF merchant_statistic_interval_amount_get_return_value +LANGUAGE plpgsql +AS $$ +DECLARE + my_time INT8 DEFAULT ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000; + my_ranges INT8[]; + my_range INT8; + my_delta_value INT8; + my_delta_frac INT8; + my_meta INT8; + my_instance_id INT8; + my_next_max_serial INT8; + my_currency TEXT; + my_rec RECORD; + my_irec RECORD; + my_jrec RECORD; + my_i INT; + my_min_serial INT8 DEFAULT NULL; + my_rval merchant_statistic_interval_amount_get_return_value; +BEGIN + SELECT merchant_serial + INTO my_instance_id + FROM merchant_instances + WHERE merchant_id=in_instance_id; + IF NOT FOUND + THEN + RETURN; + END IF; + + SELECT imeta_serial_id + ,ranges + ,precisions + INTO my_rec + FROM merchant_statistic_interval_meta + WHERE slug=in_slug; + IF NOT FOUND + THEN + RETURN; + END IF; + + my_meta = my_rec.imeta_serial_id; + my_ranges = my_rec.ranges; + + FOR my_currency IN + SELECT DISTINCT delta_curr + FROM merchant_statistic_amount_event + WHERE imeta_serial_id = my_meta + LOOP + + my_rval.rvalue.val = 0; + my_rval.rvalue.frac = 0; + my_rval.rvalue.curr = my_currency; + + FOR my_i IN 1..COALESCE(array_length(my_ranges,1),0) + LOOP + my_range = my_ranges[my_i]; + SELECT event_delimiter + ,cumulative_value + ,cumulative_frac + INTO my_irec + FROM merchant_statistic_interval_amount + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance_id + AND curr = my_currency + AND range = my_range; + + IF FOUND + THEN + my_min_serial = my_irec.event_delimiter; + my_rval.rvalue.val = (my_rval.rvalue).val + my_irec.cumulative_value + my_irec.cumulative_frac / 100000000; + my_rval.rvalue.frac = (my_rval.rvalue).frac + my_irec.cumulative_frac % 100000000; + IF (my_rval.rvalue).frac > 100000000 + THEN + my_rval.rvalue.frac = (my_rval.rvalue).frac - 100000000; + my_rval.rvalue.val = (my_rval.rvalue).val + 1; + END IF; + + -- Check if we have events that left the applicable range + SELECT SUM(delta_value) AS value_sum + ,SUM(delta_frac) AS frac_sum + INTO my_jrec + FROM merchant_statistic_amount_event + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance_id + AND delta_curr = my_currency + AND slot < my_time - my_range + AND aevent_serial_id >= my_min_serial; + + IF FOUND AND my_jrec.value_sum IS NOT NULL + THEN + -- Normalize sum + my_delta_value = my_jrec.value_sum + my_jrec.frac_sum / 100000000; + my_delta_frac = my_jrec.frac_sum % 100000000; + my_rval.rvalue.val = (my_rval.rvalue).val - my_delta_value; + IF ((my_rval.rvalue).frac >= my_delta_frac) + THEN + my_rval.rvalue.frac = (my_rval.rvalue).frac - my_delta_frac; + ELSE + my_rval.rvalue.frac = 100000000 + (my_rval.rvalue).frac - my_delta_frac; + my_rval.rvalue.val = (my_rval.rvalue).val - 1; + END IF; + + -- First find out the next event delimiter value + SELECT aevent_serial_id + INTO my_next_max_serial + FROM merchant_statistic_amount_event + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance_id + AND delta_curr = my_currency + AND slot >= my_time - my_range + AND aevent_serial_id >= my_min_serial + ORDER BY slot ASC + LIMIT 1; + IF FOUND + THEN + -- remove expired events from the sum of the current slot + UPDATE merchant_statistic_interval_amount SET + cumulative_value = cumulative_value - my_delta_value + - CASE + WHEN cumulative_frac < my_delta_frac + THEN 1 + ELSE 0 + END, + cumulative_frac = cumulative_frac - my_delta_frac + + CASE + WHEN cumulative_frac < my_delta_frac + THEN 100000000 + ELSE 0 + END, + event_delimiter = my_next_max_serial + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance_id + AND curr = my_currency + AND range = my_range; + ELSE + -- actually, slot is now empty, remove it entirely + DELETE FROM merchant_statistic_interval_amount + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance_id + AND curr = my_currency + AND range = my_range; + END IF; + IF (my_i < array_length(my_ranges,1)) + THEN + -- carry over all events into the next (larger) slot + UPDATE merchant_statistic_interval_amount AS msia SET + cumulative_value = cumulative_value + my_delta_value + + CASE + WHEN cumulative_frac + my_delta_frac > 100000000 + THEN 1 + ELSE 0 + END, + cumulative_frac = cumulative_frac + my_delta_value + - CASE + WHEN cumulative_frac + my_delta_frac > 100000000 + THEN 100000000 + ELSE 0 + END, + event_delimiter = LEAST (msia.event_delimiter,my_min_serial) + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance_id + AND range=my_ranges[my_i+1]; + IF NOT FOUND + THEN + INSERT INTO merchant_statistic_interval_amount + (imeta_serial_id + ,merchant_serial + ,event_delimiter + ,range + ,curr + ,cumulative_value + ,cumulative_frac + ) VALUES ( + my_meta + ,my_instance_id + ,my_min_serial + ,my_ranges[my_i+1] + ,my_currency + ,my_delta_value + ,my_delta_frac); + END IF; + ELSE + -- events are obsolete, delete them + DELETE FROM merchant_statistic_amount_event + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance_id + AND slot < my_time - my_range; + END IF; + END IF; + + my_rval.range = my_range; + RETURN NEXT my_rval; + END IF; + END LOOP; -- over my_ranges + END LOOP; -- over my_currency +END $$; + +COMMENT ON FUNCTION merchant_statistic_interval_amount_get + IS 'Returns deposit statistic tracking deposited amounts over certain time intervals; we first trim the stored data to only track what is still in-range, and then return the remaining value; multiple values are returned, one per currency and range'; + + + + + +DROP PROCEDURE IF EXISTS merchant_statistic_counter_gc; +CREATE OR REPLACE PROCEDURE merchant_statistic_counter_gc () +LANGUAGE plpgsql +AS $$ +DECLARE + my_time INT8 DEFAULT ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000; + my_instance INT8; + my_instance_name TEXT; + my_rec RECORD; + my_sum RECORD; + my_meta INT8; + my_ranges INT8[]; + my_precisions INT8[]; + my_precision INT4; + my_i INT4; + min_slot INT8; + max_slot INT8; + end_slot INT8; + my_total INT8; +BEGIN + -- GC for all instances + FOR my_instance IN + SELECT DISTINCT merchant_serial + FROM merchant_statistic_counter_event + LOOP + -- Do combination work for all numeric statistic events + FOR my_rec IN + SELECT imeta_serial_id + ,ranges + ,precisions + ,slug + FROM merchant_statistic_interval_meta + LOOP + -- First, we query the current interval statistic to update its counters + SELECT merchant_id + INTO my_instance_name + FROM merchant_instances + WHERE merchant_serial = my_instance; + PERFORM FROM merchant_statistic_interval_number_get (my_rec.slug, my_instance_name); + + my_meta = my_rec.imeta_serial_id; + my_ranges = my_rec.ranges; + my_precisions = my_rec.precisions; + + FOR my_i IN 1..COALESCE(array_length(my_ranges,1),0) + LOOP + my_precision = my_precisions[my_i]; + IF 1 >= my_precision + THEN + -- Cannot coarsen in this case + CONTINUE; + END IF; + + IF 1 = my_i + THEN + min_slot = 0; + ELSE + min_slot = my_ranges[my_i - 1]; + END IF; + end_slot = my_ranges[my_i]; + RAISE NOTICE 'Coarsening from [%,%) at %', my_time - end_slot, my_time - min_slot, my_precision; + + LOOP + EXIT WHEN min_slot >= end_slot; + max_slot = min_slot + my_precision; + SELECT SUM(delta) AS total, + COUNT(*) AS matches, + MIN(nevent_serial_id) AS rep_serial_id + INTO my_sum + FROM merchant_statistic_counter_event + WHERE merchant_serial=my_instance + AND imeta_serial_id=my_meta + AND slot >= my_time - max_slot + AND slot < my_time - min_slot; + + RAISE NOTICE 'Found % entries between [%,%)', my_sum.matches, my_time - max_slot, my_time - min_slot; + -- we only proceed if we had more then one match (optimization) + IF FOUND AND my_sum.matches > 1 + THEN + my_total = my_sum.total; + + RAISE NOTICE 'combining % entries to representative % for slots [%-%)', my_sum.matches, my_sum.rep_serial_id, my_time - max_slot, my_time - min_slot; + + -- combine entries + DELETE FROM merchant_statistic_counter_event + WHERE merchant_serial=my_instance + AND imeta_serial_id=my_meta + AND slot >= my_time - max_slot + AND slot < my_time - min_slot + AND nevent_serial_id > my_sum.rep_serial_id; + -- Now update the representative to the sum + UPDATE merchant_statistic_counter_event SET + delta = my_total + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance + AND nevent_serial_id = my_sum.rep_serial_id; + END IF; + min_slot = min_slot + my_precision; + END LOOP; -- min_slot to end_slot by precision loop + END LOOP; -- my_i loop + -- Finally, delete all events beyond the range we care about + + RAISE NOTICE 'deleting entries of %/% before % - % = %', my_instance, my_meta, my_time, my_ranges[array_length(my_ranges,1)], my_time - my_ranges[array_length(my_ranges,1)]; + DELETE FROM merchant_statistic_counter_event + WHERE merchant_serial=my_instance + AND imeta_serial_id=my_meta + AND slot < my_time - my_ranges[array_length(my_ranges,1)]; + END LOOP; -- my_rec loop + END LOOP; -- my_instance loop +END $$; +COMMENT ON PROCEDURE merchant_statistic_counter_gc + IS 'Performs garbage collection and compaction of the merchant_statistic_counter_event table'; + + + +DROP PROCEDURE IF EXISTS merchant_statistic_amount_gc; +CREATE OR REPLACE PROCEDURE merchant_statistic_amount_gc () +LANGUAGE plpgsql +AS $$ +DECLARE + my_time INT8 DEFAULT ROUND(EXTRACT(epoch FROM CURRENT_TIMESTAMP(0)::TIMESTAMP) * 1000000)::INT8 / 1000 / 1000; + my_instance INT8; + my_instance_name TEXT; + my_rec RECORD; + my_sum RECORD; + my_meta INT8; + my_ranges INT8[]; + my_precisions INT8[]; + my_precision INT4; + my_currency TEXT; + my_i INT4; + min_slot INT8; + max_slot INT8; + end_slot INT8; + my_total_val INT8; + my_total_frac INT8; +BEGIN + -- GC for all instances + FOR my_instance IN + SELECT DISTINCT merchant_serial + FROM merchant_statistic_counter_event + LOOP + -- Do combination work for all numeric statistic events + FOR my_rec IN + SELECT imeta_serial_id + ,ranges + ,precisions + ,slug + FROM merchant_statistic_interval_meta + LOOP + + -- First, we query the current interval statistic to update its counters + SELECT merchant_id + INTO my_instance_name + FROM merchant_instances + WHERE merchant_serial = my_instance; + PERFORM FROM merchant_statistic_interval_amount_get (my_rec.slug, my_instance_name); + + my_meta = my_rec.imeta_serial_id; + my_ranges = my_rec.ranges; + my_precisions = my_rec.precisions; + FOR my_currency IN + SELECT DISTINCT delta_curr + FROM merchant_statistic_amount_event + WHERE imeta_serial_id = my_meta + LOOP + + FOR my_i IN 1..COALESCE(array_length(my_ranges,1),0) + LOOP + my_precision = my_precisions[my_i]; + IF 1 >= my_precision + THEN + -- Cannot coarsen in this case + CONTINUE; + END IF; + + IF 1 = my_i + THEN + min_slot = 0; + ELSE + min_slot = my_ranges[my_i - 1]; + END IF; + end_slot = my_ranges[my_i]; + + RAISE NOTICE 'Coarsening from [%,%) at %', my_time - end_slot, my_time - min_slot, my_precision; + LOOP + EXIT WHEN min_slot >= end_slot; + max_slot = min_slot + my_precision; + SELECT SUM(delta_value) AS total_val, + SUM(delta_frac) AS total_frac, + COUNT(*) AS matches, + MIN(aevent_serial_id) AS rep_serial_id + INTO my_sum + FROM merchant_statistic_amount_event + WHERE imeta_serial_id=my_meta + AND merchant_serial=my_instance + AND delta_curr = my_currency + AND slot >= my_time - max_slot + AND slot < my_time - max_slot; + -- we only proceed if we had more then one match (optimization) + IF FOUND AND my_sum.matches > 1 + THEN + -- normalize new total + my_total_frac = my_sum.total_frac % 100000000; + my_total_val = my_sum.total_val + my_sum.total_frac / 100000000; + + -- combine entries + DELETE FROM merchant_statistic_amount_event + WHERE imeta_serial_id=my_meta + AND merchant_serial=my_instance + AND delta_curr = my_currency + AND slot >= my_time - max_slot + AND slot < my_time - max_slot + AND aevent_serial_id > my_sum.rep_serial_id; + -- Now update the representative to the sum + UPDATE merchant_statistic_amount_event SET + delta_value = my_total_value + ,delta_frac = my_total_frac + WHERE imeta_serial_id = my_meta + AND merchant_serial = my_instance + AND delta_curr = my_currency + AND aevent_serial_id = my_sum.rep_serial_id; + END IF; + min_slot = min_slot + my_precision; + END LOOP; -- min_slot to end_slot by precision loop + END LOOP; -- my_i loop + END LOOP; -- my_currency loop + -- Finally, delete all events beyond the range we care about + + RAISE NOTICE 'deleting entries of %/% before % - % = %', my_instance, my_meta, my_time, my_ranges[array_length(my_ranges,1)], my_time - my_ranges[array_length(my_ranges,1)]; + DELETE FROM merchant_statistic_amount_event + WHERE merchant_serial=my_instance + AND imeta_serial_id=my_meta + AND slot < my_time - my_ranges[array_length(my_ranges,1)]; + END LOOP; -- my_rec loop + END LOOP; -- my_instance loop +END $$; +COMMENT ON PROCEDURE merchant_statistic_amount_gc + IS 'Performs garbage collection and compaction of the merchant_statistic_amount_event table'; + + + +DROP PROCEDURE IF EXISTS merchant_statistic_bucket_gc; +CREATE OR REPLACE PROCEDURE merchant_statistic_bucket_gc () +LANGUAGE plpgsql +AS $$ +DECLARE + my_rec RECORD; + my_range TEXT; + my_now INT8; + my_end INT8; +BEGIN + my_now = EXTRACT(EPOCH FROM CURRENT_TIMESTAMP(0)::TIMESTAMP); -- seconds since epoch + FOR my_rec IN + SELECT bmeta_serial_id + ,stype + ,ranges[array_length(ranges,1)] AS range + ,ages[array_length(ages,1)] AS age + FROM merchant_statistic_bucket_meta + LOOP + my_range = '1 ' || my_rec.range::TEXT; + my_end = my_now - my_rec.age * EXTRACT(SECONDS FROM (SELECT my_range::INTERVAL)); -- age is given in multiples of the range (in seconds) + IF my_rec.stype = 'amount' + THEN + DELETE + FROM merchant_statistic_bucket_amount + WHERE bmeta_serial_id = my_rec.bmeta_serial_id + AND bucket_start >= my_end; + ELSE + DELETE + FROM merchant_statistic_bucket_counter + WHERE bmeta_serial_id = my_rec.bmeta_serial_id + AND bucket_start >= my_end; + END IF; + END LOOP; +END $$; +COMMENT ON PROCEDURE merchant_statistic_bucket_gc + IS 'Performs garbage collection of the merchant_statistic_bucket_counter and merchant_statistic_bucket_amount tables'; + + diff --git a/src/backenddb/pg_template.c b/src/backenddb/pg_template.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2024 Taler Systems SA + Copyright (C) 2025 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 diff --git a/src/backenddb/pg_template.h b/src/backenddb/pg_template.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2024 Taler Systems SA + Copyright (C) 2025 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 diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c @@ -31,6 +31,7 @@ #include <taler/taler_mhd_lib.h> #include "taler_merchantdb_plugin.h" #include "pg_helper.h" +#include "pg_gc.h" #include "pg_insert_otp.h" #include "pg_get_kyc_status.h" #include "pg_get_kyc_limits.h" @@ -638,7 +639,8 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) = &TMH_PG_insert_issued_token; plugin->lookup_spent_tokens_by_order = &TMH_PG_lookup_spent_tokens_by_order; - + plugin->gc + = &TMH_PG_gc; #ifdef HAVE_DONAU_DONAU_SERVICE_H plugin->insert_donau_instance = &TMH_PG_insert_donau_instance; @@ -651,7 +653,6 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) plugin->upsert_donau_keys = &TMH_PG_upsert_donau_keys; #endif - return plugin; } diff --git a/src/backenddb/procedures.sql.in b/src/backenddb/procedures.sql.in @@ -1,6 +1,6 @@ -- -- This file is part of TALER --- Copyright (C) 2024 Taler Systems SA +-- Copyright (C) 2024-2025 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 @@ -24,5 +24,20 @@ SET search_path TO merchant; #include "pg_update_product.sql" #include "pg_account_kyc_set_status.sql" #include "pg_account_kyc_set_failed.sql" +#include "pg_statistics_helpers.sql" + + +DROP PROCEDURE IF EXISTS merchant_do_gc; +CREATE PROCEDURE merchant_do_gc() +LANGUAGE plpgsql +AS $$ +BEGIN + CALL merchant_statistic_amount_gc (); + CALL merchant_statistic_bucket_gc (); + CALL merchant_statistic_counter_gc (); +END $$; +COMMENT ON PROCEDURE merchant_do_gc + IS 'calls all other garbage collection subroutines'; + COMMIT; diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c @@ -1898,7 +1898,8 @@ test_mark_contract_paid (const struct InstanceData *instance, plugin->mark_contract_paid (plugin->cls, instance->instance.id, &h_contract_terms, - "test_orders_session"), + "test_orders_session", + -1), "Mark contract paid failed\n"); return 0; } diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -1363,7 +1363,7 @@ struct TALER_MERCHANTDB_Plugin * @param cls closure */ enum GNUNET_GenericReturnValue - (*connect)(void *cls); + (*connect)(void *cls); /** * Drop merchant tables. Used for testcases and to reset the DB. @@ -1372,7 +1372,16 @@ struct TALER_MERCHANTDB_Plugin * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ enum GNUNET_GenericReturnValue - (*drop_tables)(void *cls); + (*drop_tables)(void *cls); + + /** + * Garbage collect database. Removes unnecessary data. + * + * @param cls closure + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ + enum GNUNET_GenericReturnValue + (*gc)(void *cls); /** * Initialize merchant tables @@ -1381,7 +1390,7 @@ struct TALER_MERCHANTDB_Plugin * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ enum GNUNET_GenericReturnValue - (*create_tables)(void *cls); + (*create_tables)(void *cls); /** * Register callback to be invoked on events of type @a es. @@ -1444,8 +1453,8 @@ struct TALER_MERCHANTDB_Plugin * @return #GNUNET_OK on success */ enum GNUNET_GenericReturnValue - (*start)(void *cls, - const char *name); + (*start)(void *cls, + const char *name); /** * Start a transaction with isolation level 'read committed'. @@ -1456,8 +1465,8 @@ struct TALER_MERCHANTDB_Plugin * @return #GNUNET_OK on success */ enum GNUNET_GenericReturnValue - (*start_read_committed)(void *cls, - const char *name); + (*start_read_committed)(void *cls, + const char *name); /** * Roll back the current transaction of a database connection. @@ -1474,7 +1483,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*commit)(void *cls); + (*commit)(void *cls); /** * Lookup all of the instances this backend has configured. @@ -1485,10 +1494,10 @@ struct TALER_MERCHANTDB_Plugin * @param cb_cls closure for @a cb */ enum GNUNET_DB_QueryStatus - (*lookup_instances)(void *cls, - bool active_only, - TALER_MERCHANTDB_InstanceCallback cb, - void *cb_cls); + (*lookup_instances)(void *cls, + bool active_only, + TALER_MERCHANTDB_InstanceCallback cb, + void *cb_cls); /** * Lookup one of the instances this backend has configured. @@ -1500,11 +1509,11 @@ struct TALER_MERCHANTDB_Plugin * @param cb_cls closure for @a cb */ enum GNUNET_DB_QueryStatus - (*lookup_instance)(void *cls, - const char *id, - bool active_only, - TALER_MERCHANTDB_InstanceCallback cb, - void *cb_cls); + (*lookup_instance)(void *cls, + const char *id, + bool active_only, + TALER_MERCHANTDB_InstanceCallback cb, + void *cb_cls); /** * Lookup authentication data of an instance. @@ -1514,9 +1523,9 @@ struct TALER_MERCHANTDB_Plugin * @param[out] ias where to store the auth data */ enum GNUNET_DB_QueryStatus - (*lookup_instance_auth)(void *cls, - const char *instance_id, - struct TALER_MERCHANTDB_InstanceAuthSettings *ias); + (*lookup_instance_auth)(void *cls, + const char *instance_id, + struct TALER_MERCHANTDB_InstanceAuthSettings *ias); /** @@ -1529,11 +1538,11 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*insert_instance)(void *cls, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct TALER_MerchantPrivateKeyP *merchant_priv, - const struct TALER_MERCHANTDB_InstanceSettings *is, - const struct TALER_MERCHANTDB_InstanceAuthSettings *ias); + (*insert_instance)(void *cls, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_MerchantPrivateKeyP *merchant_priv, + const struct TALER_MERCHANTDB_InstanceSettings *is, + const struct TALER_MERCHANTDB_InstanceAuthSettings *ias); /** * Insert information about an instance's account into our database. @@ -1543,7 +1552,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*insert_account)( + (*insert_account)( void *cls, const struct TALER_MERCHANTDB_AccountDetails *account_details); @@ -1560,7 +1569,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*insert_login_token)( + (*insert_login_token)( void *cls, const char *id, const struct TALER_MERCHANTDB_LoginTokenP *token, @@ -1580,7 +1589,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*select_login_token)( + (*select_login_token)( void *cls, const char *id, const struct TALER_MERCHANTDB_LoginTokenP *token, @@ -1597,7 +1606,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*delete_login_token)( + (*delete_login_token)( void *cls, const char *id, const struct TALER_MERCHANTDB_LoginTokenP *token); @@ -1614,7 +1623,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*update_account)( + (*update_account)( void *cls, const char *id, const struct TALER_MerchantWireHashP *h_wire, @@ -1632,7 +1641,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*select_accounts)( + (*select_accounts)( void *cls, const char *id, TALER_MERCHANTDB_AccountCallback cb, @@ -1649,7 +1658,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*select_account)( + (*select_account)( void *cls, const char *id, const struct TALER_MerchantWireHashP *h_wire, @@ -1666,7 +1675,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*select_account_by_uri)( + (*select_account_by_uri)( void *cls, const char *id, struct TALER_FullPayto payto_uri, @@ -1681,7 +1690,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*delete_instance_private_key)( + (*delete_instance_private_key)( void *cls, const char *merchant_id); @@ -1694,8 +1703,8 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*purge_instance)(void *cls, - const char *merchant_id); + (*purge_instance)(void *cls, + const char *merchant_id); /** * Update information about an instance into our database. @@ -1705,8 +1714,8 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*update_instance)(void *cls, - const struct TALER_MERCHANTDB_InstanceSettings *is); + (*update_instance)(void *cls, + const struct TALER_MERCHANTDB_InstanceSettings *is); /** * Update information about an instance's authentication settings @@ -1718,7 +1727,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*update_instance_auth)( + (*update_instance_auth)( void *cls, const char *merchant_id, const struct TALER_MERCHANTDB_InstanceAuthSettings *ias); @@ -1732,9 +1741,9 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*inactivate_account)(void *cls, - const char *merchant_id, - const struct TALER_MerchantWireHashP *h_wire); + (*inactivate_account)(void *cls, + const char *merchant_id, + const struct TALER_MerchantWireHashP *h_wire); /** @@ -1746,9 +1755,9 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*activate_account)(void *cls, - const char *merchant_id, - const struct TALER_MerchantWireHashP *h_wire); + (*activate_account)(void *cls, + const char *merchant_id, + const struct TALER_MerchantWireHashP *h_wire); /** @@ -1765,7 +1774,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*account_kyc_get_status)( + (*account_kyc_get_status)( void *cls, const char *merchant_id, const struct TALER_MerchantWireHashP *h_wire, @@ -1791,7 +1800,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*get_kyc_status)( + (*get_kyc_status)( void *cls, struct TALER_FullPayto merchant_account_uri, const char *instance_id, @@ -1819,7 +1828,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*get_kyc_limits)( + (*get_kyc_limits)( void *cls, struct TALER_FullPayto merchant_account_uri, const char *instance_id, @@ -1846,7 +1855,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*account_kyc_set_status)( + (*account_kyc_set_status)( void *cls, const char *merchant_id, const struct TALER_MerchantWireHashP *h_wire, @@ -1873,7 +1882,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*account_kyc_set_failed) ( + (*account_kyc_set_failed)( void *cls, const char *merchant_id, const struct TALER_MerchantWireHashP *h_wire, @@ -1896,12 +1905,12 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*lookup_products)(void *cls, - const char *instance_id, - uint64_t offset, - int64_t limit, - TALER_MERCHANTDB_ProductsCallback cb, - void *cb_cls); + (*lookup_products)(void *cls, + const char *instance_id, + uint64_t offset, + int64_t limit, + TALER_MERCHANTDB_ProductsCallback cb, + void *cb_cls); /** @@ -1914,10 +1923,10 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*lookup_all_products)(void *cls, - const char *instance_id, - TALER_MERCHANTDB_ProductCallback cb, - void *cb_cls); + (*lookup_all_products)(void *cls, + const char *instance_id, + TALER_MERCHANTDB_ProductCallback cb, + void *cb_cls); /** * Lookup details about a particular product. @@ -1933,12 +1942,12 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*lookup_product)(void *cls, - const char *instance_id, - const char *product_id, - struct TALER_MERCHANTDB_ProductDetails *pd, - size_t *num_categories, - uint64_t **categories); + (*lookup_product)(void *cls, + const char *instance_id, + const char *product_id, + struct TALER_MERCHANTDB_ProductDetails *pd, + size_t *num_categories, + uint64_t **categories); /** * Delete information about a product. Note that the transaction must @@ -1951,9 +1960,9 @@ struct TALER_MERCHANTDB_Plugin * if locks prevent deletion OR product unknown */ enum GNUNET_DB_QueryStatus - (*delete_product)(void *cls, - const char *instance_id, - const char *product_id); + (*delete_product)(void *cls, + const char *instance_id, + const char *product_id); /** * Insert details about a particular product. @@ -1971,15 +1980,15 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*insert_product)(void *cls, - const char *instance_id, - const char *product_id, - const struct TALER_MERCHANTDB_ProductDetails *pd, - size_t num_cats, - const uint64_t *cats, - bool *no_instance, - bool *conflict, - ssize_t *no_cat); + (*insert_product)(void *cls, + const char *instance_id, + const char *product_id, + const struct TALER_MERCHANTDB_ProductDetails *pd, + size_t num_cats, + const uint64_t *cats, + bool *no_instance, + bool *conflict, + ssize_t *no_cat); /** * Update details about a particular product. Note that the @@ -2003,18 +2012,18 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*update_product)(void *cls, - const char *instance_id, - const char *product_id, - const struct TALER_MERCHANTDB_ProductDetails *pd, - size_t num_cats, - const uint64_t *cats, - bool *no_instance, - ssize_t *no_cat, - bool *no_product, - bool *lost_reduced, - bool *sold_reduced, - bool *stocked_reduced); + (*update_product)(void *cls, + const char *instance_id, + const char *product_id, + const struct TALER_MERCHANTDB_ProductDetails *pd, + size_t num_cats, + const uint64_t *cats, + bool *no_instance, + ssize_t *no_cat, + bool *no_product, + bool *lost_reduced, + bool *sold_reduced, + bool *stocked_reduced); /** * Lock stocks of a particular product. Note that the transaction must @@ -2030,12 +2039,12 @@ struct TALER_MERCHANTDB_Plugin * product is unknown OR if there insufficient stocks remaining */ enum GNUNET_DB_QueryStatus - (*lock_product)(void *cls, - const char *instance_id, - const char *product_id, - const struct GNUNET_Uuid *uuid, - uint64_t quantity, - struct GNUNET_TIME_Timestamp expiration_time); + (*lock_product)(void *cls, + const char *instance_id, + const char *product_id, + const struct GNUNET_Uuid *uuid, + uint64_t quantity, + struct GNUNET_TIME_Timestamp expiration_time); /** @@ -2047,7 +2056,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*expire_locks)(void *cls); + (*expire_locks)(void *cls); /** @@ -2062,10 +2071,10 @@ struct TALER_MERCHANTDB_Plugin * if locks prevent deletion OR order unknown */ enum GNUNET_DB_QueryStatus - (*delete_order)(void *cls, - const char *instance_id, - const char *order_id, - bool force); + (*delete_order)(void *cls, + const char *instance_id, + const char *order_id, + bool force); /** @@ -2082,12 +2091,12 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_order)(void *cls, - const char *instance_id, - const char *order_id, - struct TALER_ClaimTokenP *claim_token, - struct TALER_MerchantPostDataHashP *h_post_data, - json_t **contract_terms); + (*lookup_order)(void *cls, + const char *instance_id, + const char *order_id, + struct TALER_ClaimTokenP *claim_token, + struct TALER_MerchantPostDataHashP *h_post_data, + json_t **contract_terms); /** @@ -2101,11 +2110,11 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_order_summary)(void *cls, - const char *instance_id, - const char *order_id, - struct GNUNET_TIME_Timestamp *timestamp, - uint64_t *order_serial); + (*lookup_order_summary)(void *cls, + const char *instance_id, + const char *order_id, + struct GNUNET_TIME_Timestamp *timestamp, + uint64_t *order_serial); /** @@ -2119,11 +2128,11 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_orders)(void *cls, - const char *instance_id, - const struct TALER_MERCHANTDB_OrderFilter *of, - TALER_MERCHANTDB_OrdersCallback cb, - void *cb_cls); + (*lookup_orders)(void *cls, + const char *instance_id, + const struct TALER_MERCHANTDB_OrderFilter *of, + TALER_MERCHANTDB_OrdersCallback cb, + void *cb_cls); /** @@ -2142,16 +2151,16 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*insert_order)(void *cls, - const char *instance_id, - const char *order_id, - const char *session_id, - const struct TALER_MerchantPostDataHashP *h_post_data, - struct GNUNET_TIME_Timestamp pay_deadline, - const struct TALER_ClaimTokenP *claim_token, - const json_t *contract_terms, - const char *pos_key, - enum TALER_MerchantConfirmationAlgorithm pos_algorithm); + (*insert_order)(void *cls, + const char *instance_id, + const char *order_id, + const char *session_id, + const struct TALER_MerchantPostDataHashP *h_post_data, + struct GNUNET_TIME_Timestamp pay_deadline, + const struct TALER_ClaimTokenP *claim_token, + const json_t *contract_terms, + const char *pos_key, + enum TALER_MerchantConfirmationAlgorithm pos_algorithm); /** @@ -2165,8 +2174,8 @@ struct TALER_MERCHANTDB_Plugin * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success */ enum GNUNET_DB_QueryStatus - (*unlock_inventory)(void *cls, - const struct GNUNET_Uuid *uuid); + (*unlock_inventory)(void *cls, + const struct GNUNET_Uuid *uuid); /** @@ -2182,11 +2191,11 @@ struct TALER_MERCHANTDB_Plugin * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success */ enum GNUNET_DB_QueryStatus - (*insert_order_lock)(void *cls, - const char *instance_id, - const char *order_id, - const char *product_id, - uint64_t quantity); + (*insert_order_lock)(void *cls, + const char *instance_id, + const char *order_id, + const char *product_id, + uint64_t quantity); /** @@ -2204,7 +2213,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_contract_terms2)( + (*lookup_contract_terms2)( void *cls, const char *instance_id, const char *order_id, @@ -2233,7 +2242,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_contract_terms3)( + (*lookup_contract_terms3)( void *cls, const char *instance_id, const char *order_id, @@ -2259,7 +2268,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_contract_terms)( + (*lookup_contract_terms)( void *cls, const char *instance_id, const char *order_id, @@ -2285,7 +2294,7 @@ struct TALER_MERCHANTDB_Plugin * is malformed */ enum GNUNET_DB_QueryStatus - (*insert_contract_terms)( + (*insert_contract_terms)( void *cls, const char *instance_id, const char *order_id, @@ -2309,10 +2318,10 @@ struct TALER_MERCHANTDB_Plugin * is malformed */ enum GNUNET_DB_QueryStatus - (*update_contract_terms)(void *cls, - const char *instance_id, - const char *order_id, - json_t *contract_terms); + (*update_contract_terms)(void *cls, + const char *instance_id, + const char *order_id, + json_t *contract_terms); /** @@ -2329,10 +2338,10 @@ struct TALER_MERCHANTDB_Plugin * if locks prevent deletion OR order unknown */ enum GNUNET_DB_QueryStatus - (*delete_contract_terms)(void *cls, - const char *instance_id, - const char *order_id, - struct GNUNET_TIME_Relative legal_expiration); + (*delete_contract_terms)(void *cls, + const char *instance_id, + const char *order_id, + struct GNUNET_TIME_Relative legal_expiration); /** @@ -2347,12 +2356,12 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_deposits)(void *cls, - const char *instance_id, - const struct TALER_PrivateContractHashP *h_contract_terms - , - TALER_MERCHANTDB_DepositsCallback cb, - void *cb_cls); + (*lookup_deposits)(void *cls, + const char *instance_id, + const struct TALER_PrivateContractHashP *h_contract_terms + , + TALER_MERCHANTDB_DepositsCallback cb, + void *cb_cls); /** @@ -2367,7 +2376,7 @@ struct TALER_MERCHANTDB_Plugin * @param master_sig signature of @a master_pub over the @a exchange_pub and the dates */ enum GNUNET_DB_QueryStatus - (*insert_exchange_signkey)( + (*insert_exchange_signkey)( void *cls, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_ExchangePublicKeyP *exchange_pub, @@ -2395,7 +2404,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*insert_deposit_confirmation)( + (*insert_deposit_confirmation)( void *cls, const char *instance_id, struct GNUNET_TIME_Timestamp deposit_timestamp, @@ -2425,7 +2434,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*insert_deposit)( + (*insert_deposit)( void *cls, uint32_t offset, uint64_t deposit_confirmation_serial_id, @@ -2447,11 +2456,11 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_refunds)(void *cls, - const char *instance_id, - const struct TALER_PrivateContractHashP *h_contract_terms, - TALER_MERCHANTDB_RefundCallback rc, - void *rc_cls); + (*lookup_refunds)(void *cls, + const char *instance_id, + const struct TALER_PrivateContractHashP *h_contract_terms, + TALER_MERCHANTDB_RefundCallback rc, + void *rc_cls); /** @@ -2464,10 +2473,10 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_spent_tokens_by_order)(void *cls, - uint64_t order_serial, - TALER_MERCHANTDB_UsedTokensCallback cb, - void *cb_cls); + (*lookup_spent_tokens_by_order)(void *cls, + uint64_t order_serial, + TALER_MERCHANTDB_UsedTokensCallback cb, + void *cb_cls); /** @@ -2482,11 +2491,12 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*mark_contract_paid)( + (*mark_contract_paid)( void *cls, const char *instance_id, const struct TALER_PrivateContractHashP *h_contract_terms, - const char *session_id); + const char *session_id, + int16_t choice_index); /** @@ -2505,12 +2515,12 @@ struct TALER_MERCHANTDB_Plugin * regardless of whether it actually increased the refund */ enum GNUNET_DB_QueryStatus - (*refund_coin)(void *cls, - const char *instance_id, - const struct TALER_PrivateContractHashP *h_contract_terms, - struct GNUNET_TIME_Timestamp refund_timestamp, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const char *reason); + (*refund_coin)(void *cls, + const char *instance_id, + const struct TALER_PrivateContractHashP *h_contract_terms, + struct GNUNET_TIME_Timestamp refund_timestamp, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const char *reason); /** @@ -2524,11 +2534,11 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_order_status)(void *cls, - const char *instance_id, - const char *order_id, - struct TALER_PrivateContractHashP *h_contract_terms, - bool *paid); + (*lookup_order_status)(void *cls, + const char *instance_id, + const char *order_id, + struct TALER_PrivateContractHashP *h_contract_terms, + bool *paid); /** * Retrieve contract terms given its @a order_serial @@ -2542,13 +2552,13 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_order_status_by_serial)(void *cls, - const char *instance_id, - uint64_t order_serial, - char **order_id, - struct TALER_PrivateContractHashP * - h_contract_terms, - bool *paid); + (*lookup_order_status_by_serial)(void *cls, + const char *instance_id, + uint64_t order_serial, + char **order_id, + struct TALER_PrivateContractHashP * + h_contract_terms, + bool *paid); /** @@ -2561,10 +2571,10 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_deposits_by_order)(void *cls, - uint64_t order_serial, - TALER_MERCHANTDB_DepositedCoinsCallback cb, - void *cb_cls); + (*lookup_deposits_by_order)(void *cls, + uint64_t order_serial, + TALER_MERCHANTDB_DepositedCoinsCallback cb, + void *cb_cls); /** @@ -2578,7 +2588,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_transfer_details_by_order)( + (*lookup_transfer_details_by_order)( void *cls, uint64_t order_serial, TALER_MERCHANTDB_OrderTransferDetailsCallback cb, @@ -2598,7 +2608,7 @@ struct TALER_MERCHANTDB_Plugin * @return database transaction status */ enum GNUNET_DB_QueryStatus - (*update_transfer_status)( + (*update_transfer_status)( void *cls, const char *exchange_url, const struct TALER_WireTransferIdentifierRawP *wtid, @@ -2619,7 +2629,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*select_open_transfers)( + (*select_open_transfers)( void *cls, uint64_t limit, TALER_MERCHANTDB_OpenTransferCallback cb, @@ -2636,10 +2646,10 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*insert_deposit_to_transfer)(void *cls, - uint64_t deposit_serial, - const struct TALER_EXCHANGE_DepositData *dd, - bool *wpc); + (*insert_deposit_to_transfer)(void *cls, + uint64_t deposit_serial, + const struct TALER_EXCHANGE_DepositData *dd, + bool *wpc); /** @@ -2650,8 +2660,8 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*mark_order_wired)(void *cls, - uint64_t order_serial); + (*mark_order_wired)(void *cls, + uint64_t order_serial); /** @@ -2677,7 +2687,7 @@ struct TALER_MERCHANTDB_Plugin * what was already refunded (idempotency!) */ enum TALER_MERCHANTDB_RefundStatus - (*increase_refund)( + (*increase_refund)( void *cls, const char *instance_id, const char *order_id, @@ -2698,7 +2708,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_refunds_detailed)( + (*lookup_refunds_detailed)( void *cls, const char *instance_id, const struct TALER_PrivateContractHashP *h_contract_terms, @@ -2715,7 +2725,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*insert_refund_proof)( + (*insert_refund_proof)( void *cls, uint64_t refund_serial, const struct TALER_ExchangeSignatureP *exchange_sig, @@ -2734,7 +2744,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*insert_spent_token)( + (*insert_spent_token)( void *cls, const struct TALER_PrivateContractHashP * h_contract_terms, @@ -2754,7 +2764,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*insert_issued_token)( + (*insert_issued_token)( void *cls, const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_TokenIssuePublicKeyHashP *h_issue_pub, @@ -2771,7 +2781,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_refund_proof)( + (*lookup_refund_proof)( void *cls, uint64_t refund_serial, struct TALER_ExchangeSignatureP *exchange_sig, @@ -2792,7 +2802,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_order_by_fulfillment)( + (*lookup_order_by_fulfillment)( void *cls, const char *instance_id, const char *fulfillment_url, @@ -2810,7 +2820,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*update_wirewatch_progress)( + (*update_wirewatch_progress)( void *cls, const char *instance, struct TALER_FullPayto payto_uri, @@ -2826,7 +2836,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*select_wirewatch_accounts)( + (*select_wirewatch_accounts)( void *cls, TALER_MERCHANTDB_WirewatchWorkCallback cb, void *cb_cls); @@ -2846,7 +2856,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*insert_transfer)( + (*insert_transfer)( void *cls, const char *instance_id, const char *exchange_url, @@ -2867,9 +2877,9 @@ struct TALER_MERCHANTDB_Plugin * if deletion is prohibited OR transfer is unknown */ enum GNUNET_DB_QueryStatus - (*delete_transfer)(void *cls, - const char *instance_id, - uint64_t transfer_serial_id); + (*delete_transfer)(void *cls, + const char *instance_id, + uint64_t transfer_serial_id); /** @@ -2883,9 +2893,9 @@ struct TALER_MERCHANTDB_Plugin * if the transfer record exists */ enum GNUNET_DB_QueryStatus - (*check_transfer_exists)(void *cls, - const char *instance_id, - uint64_t transfer_serial_id); + (*check_transfer_exists)(void *cls, + const char *instance_id, + uint64_t transfer_serial_id); /** @@ -2898,10 +2908,10 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_account)(void *cls, - const char *instance_id, - struct TALER_FullPayto payto_uri, - uint64_t *account_serial); + (*lookup_account)(void *cls, + const char *instance_id, + struct TALER_FullPayto payto_uri, + uint64_t *account_serial); /** @@ -2918,7 +2928,7 @@ struct TALER_MERCHANTDB_Plugin * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT on success */ enum GNUNET_DB_QueryStatus - (*insert_transfer_details)( + (*insert_transfer_details)( void *cls, const char *instance_id, const char *exchange_url, @@ -2942,7 +2952,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status code */ enum GNUNET_DB_QueryStatus - (*lookup_wire_fee)( + (*lookup_wire_fee)( void *cls, const struct TALER_MasterPublicKeyP *master_pub, const char *wire_method, @@ -2966,7 +2976,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_deposits_by_contract_and_coin)( + (*lookup_deposits_by_contract_and_coin)( void *cls, const char *instance_id, const struct TALER_PrivateContractHashP *h_contract_terms, @@ -2993,7 +3003,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_transfer)( + (*lookup_transfer)( void *cls, const char *instance_id, const char *exchange_url, @@ -3017,7 +3027,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*set_transfer_status_to_confirmed)( + (*set_transfer_status_to_confirmed)( void *cls, const char *instance_id, const char *exchange_url, @@ -3036,7 +3046,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_transfer_summary)( + (*lookup_transfer_summary)( void *cls, const char *exchange_url, const struct TALER_WireTransferIdentifierRawP *wtid, @@ -3055,7 +3065,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_transfer_details)( + (*lookup_transfer_details)( void *cls, const char *exchange_url, const struct TALER_WireTransferIdentifierRawP *wtid, @@ -3080,7 +3090,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_transfers)( + (*lookup_transfers)( void *cls, const char *instance_id, struct TALER_FullPayto payto_uri, @@ -3107,7 +3117,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status code */ enum GNUNET_DB_QueryStatus - (*store_wire_fee_by_exchange)( + (*store_wire_fee_by_exchange)( void *cls, const struct TALER_MasterPublicKeyP *master_pub, const struct GNUNET_HashCode *h_wire_method, @@ -3125,7 +3135,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status code */ enum GNUNET_DB_QueryStatus - (*delete_exchange_accounts)( + (*delete_exchange_accounts)( void *cls, const struct TALER_MasterPublicKeyP *master_pub); @@ -3140,7 +3150,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status code */ enum GNUNET_DB_QueryStatus - (*select_accounts_by_exchange)( + (*select_accounts_by_exchange)( void *cls, const struct TALER_MasterPublicKeyP *master_pub, TALER_MERCHANTDB_ExchangeAccountCallback cb, @@ -3160,7 +3170,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status code */ enum GNUNET_DB_QueryStatus - (*insert_exchange_account)( + (*insert_exchange_account)( void *cls, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_FullPayto payto_uri, @@ -3180,10 +3190,10 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*lookup_templates)(void *cls, - const char *instance_id, - TALER_MERCHANTDB_TemplatesCallback cb, - void *cb_cls); + (*lookup_templates)(void *cls, + const char *instance_id, + TALER_MERCHANTDB_TemplatesCallback cb, + void *cb_cls); /** @@ -3197,10 +3207,10 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*lookup_template)(void *cls, - const char *instance_id, - const char *template_id, - struct TALER_MERCHANTDB_TemplateDetails *td); + (*lookup_template)(void *cls, + const char *instance_id, + const char *template_id, + struct TALER_MERCHANTDB_TemplateDetails *td); /** * Delete information about a template. @@ -3212,9 +3222,9 @@ struct TALER_MERCHANTDB_Plugin * if template unknown. */ enum GNUNET_DB_QueryStatus - (*delete_template)(void *cls, - const char *instance_id, - const char *template_id); + (*delete_template)(void *cls, + const char *instance_id, + const char *template_id); /** @@ -3228,11 +3238,11 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*insert_template)(void *cls, - const char *instance_id, - const char *template_id, - uint64_t otp_serial_id, - const struct TALER_MERCHANTDB_TemplateDetails *td); + (*insert_template)(void *cls, + const char *instance_id, + const char *template_id, + uint64_t otp_serial_id, + const struct TALER_MERCHANTDB_TemplateDetails *td); /** @@ -3247,10 +3257,10 @@ struct TALER_MERCHANTDB_Plugin * does not yet exist. */ enum GNUNET_DB_QueryStatus - (*update_template)(void *cls, - const char *instance_id, - const char *template_id, - const struct TALER_MERCHANTDB_TemplateDetails *td); + (*update_template)(void *cls, + const char *instance_id, + const char *template_id, + const struct TALER_MERCHANTDB_TemplateDetails *td); /** @@ -3263,9 +3273,9 @@ struct TALER_MERCHANTDB_Plugin * if template unknown. */ enum GNUNET_DB_QueryStatus - (*delete_otp)(void *cls, - const char *instance_id, - const char *otp_id); + (*delete_otp)(void *cls, + const char *instance_id, + const char *otp_id); /** * Insert details about a particular OTP device. @@ -3277,10 +3287,10 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*insert_otp)(void *cls, - const char *instance_id, - const char *otp_id, - const struct TALER_MERCHANTDB_OtpDeviceDetails *td); + (*insert_otp)(void *cls, + const char *instance_id, + const char *otp_id, + const struct TALER_MERCHANTDB_OtpDeviceDetails *td); /** @@ -3295,10 +3305,10 @@ struct TALER_MERCHANTDB_Plugin * does not yet exist. */ enum GNUNET_DB_QueryStatus - (*update_otp)(void *cls, - const char *instance_id, - const char *otp_id, - const struct TALER_MERCHANTDB_OtpDeviceDetails *td); + (*update_otp)(void *cls, + const char *instance_id, + const char *otp_id, + const struct TALER_MERCHANTDB_OtpDeviceDetails *td); /** * Lookup all of the OTP devices the given instance has configured. @@ -3310,10 +3320,10 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*lookup_otp_devices)(void *cls, - const char *instance_id, - TALER_MERCHANTDB_OtpDeviceCallback cb, - void *cb_cls); + (*lookup_otp_devices)(void *cls, + const char *instance_id, + TALER_MERCHANTDB_OtpDeviceCallback cb, + void *cb_cls); /** @@ -3327,10 +3337,10 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*select_otp)(void *cls, - const char *instance_id, - const char *otp_id, - struct TALER_MERCHANTDB_OtpDeviceDetails *td); + (*select_otp)(void *cls, + const char *instance_id, + const char *otp_id, + struct TALER_MERCHANTDB_OtpDeviceDetails *td); /** @@ -3342,10 +3352,10 @@ struct TALER_MERCHANTDB_Plugin * @param[out] serial set to the OTP device serial number * @return database result code */ enum GNUNET_DB_QueryStatus - (*select_otp_serial)(void *cls, - const char *instance_id, - const char *otp_id, - uint64_t *serial); + (*select_otp_serial)(void *cls, + const char *instance_id, + const char *otp_id, + uint64_t *serial); /** @@ -3358,9 +3368,9 @@ struct TALER_MERCHANTDB_Plugin * if template unknown. */ enum GNUNET_DB_QueryStatus - (*delete_category)(void *cls, - const char *instance_id, - uint64_t category_id); + (*delete_category)(void *cls, + const char *instance_id, + uint64_t category_id); /** * Insert new product category. @@ -3373,11 +3383,11 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*insert_category)(void *cls, - const char *instance_id, - const char *category_name, - const json_t *category_name_i18n, - uint64_t *category_id); + (*insert_category)(void *cls, + const char *instance_id, + const char *category_name, + const json_t *category_name_i18n, + uint64_t *category_id); /** @@ -3392,11 +3402,11 @@ struct TALER_MERCHANTDB_Plugin * does not yet exist. */ enum GNUNET_DB_QueryStatus - (*update_category)(void *cls, - const char *instance_id, - uint64_t category_id, - const char *category_name, - const json_t *category_name_i18n); + (*update_category)(void *cls, + const char *instance_id, + uint64_t category_id, + const char *category_name, + const json_t *category_name_i18n); /** * Lookup all of the product categories the given instance has configured. @@ -3408,10 +3418,10 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*lookup_categories)(void *cls, - const char *instance_id, - TALER_MERCHANTDB_CategoriesCallback cb, - void *cb_cls); + (*lookup_categories)(void *cls, + const char *instance_id, + TALER_MERCHANTDB_CategoriesCallback cb, + void *cb_cls); /** @@ -3427,12 +3437,12 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*select_category)(void *cls, - const char *instance_id, - uint64_t category_id, - struct TALER_MERCHANTDB_CategoryDetails *cd, - size_t *num_products, - char **products); + (*select_category)(void *cls, + const char *instance_id, + uint64_t category_id, + struct TALER_MERCHANTDB_CategoryDetails *cd, + size_t *num_products, + char **products); /** @@ -3446,11 +3456,11 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*select_category_by_name)(void *cls, - const char *instance_id, - const char *category_name, - json_t **name_i18n, - uint64_t *category_id); + (*select_category_by_name)(void *cls, + const char *instance_id, + const char *category_name, + json_t **name_i18n, + uint64_t *category_id); /** @@ -3463,10 +3473,10 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*lookup_webhooks)(void *cls, - const char *instance_id, - TALER_MERCHANTDB_WebhooksCallback cb, - void *cb_cls); + (*lookup_webhooks)(void *cls, + const char *instance_id, + TALER_MERCHANTDB_WebhooksCallback cb, + void *cb_cls); /** @@ -3480,10 +3490,10 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*lookup_webhook)(void *cls, - const char *instance_id, - const char *webhook_id, - struct TALER_MERCHANTDB_WebhookDetails *wb); + (*lookup_webhook)(void *cls, + const char *instance_id, + const char *webhook_id, + struct TALER_MERCHANTDB_WebhookDetails *wb); /** * Delete information about a webhook. @@ -3495,9 +3505,9 @@ struct TALER_MERCHANTDB_Plugin * if webhook unknown. */ enum GNUNET_DB_QueryStatus - (*delete_webhook)(void *cls, - const char *instance_id, - const char *webhook_id); + (*delete_webhook)(void *cls, + const char *instance_id, + const char *webhook_id); /** @@ -3510,10 +3520,10 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*insert_webhook)(void *cls, - const char *instance_id, - const char *webhook_id, - const struct TALER_MERCHANTDB_WebhookDetails *wb); + (*insert_webhook)(void *cls, + const char *instance_id, + const char *webhook_id, + const struct TALER_MERCHANTDB_WebhookDetails *wb); /** @@ -3528,10 +3538,10 @@ struct TALER_MERCHANTDB_Plugin * does not yet exist. */ enum GNUNET_DB_QueryStatus - (*update_webhook)(void *cls, - const char *instance_id, - const char *webhook_id, - const struct TALER_MERCHANTDB_WebhookDetails *wb); + (*update_webhook)(void *cls, + const char *instance_id, + const char *webhook_id, + const struct TALER_MERCHANTDB_WebhookDetails *wb); /** * Lookup webhook by event @@ -3544,11 +3554,11 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*lookup_webhook_by_event)(void *cls, - const char *instance_id, - const char *event_type, - TALER_MERCHANTDB_WebhookDetailCallback cb, - void *cb_cls); + (*lookup_webhook_by_event)(void *cls, + const char *instance_id, + const char *event_type, + TALER_MERCHANTDB_WebhookDetailCallback cb, + void *cb_cls); /** * Insert webhook in the pending webhook. @@ -3563,13 +3573,13 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*insert_pending_webhook)(void *cls, - const char *instance_id, - uint64_t webhook_serial, - const char *url, - const char *http_method, - const char *header, - const char *body); + (*insert_pending_webhook)(void *cls, + const char *instance_id, + uint64_t webhook_serial, + const char *url, + const char *http_method, + const char *header, + const char *body); /** * Lookup the webhook that need to be send in priority. These webhooks are not successfully * send. @@ -3580,9 +3590,9 @@ struct TALER_MERCHANTDB_Plugin */ // WHERE next_attempt <= now ORDER BY next_attempt ASC enum GNUNET_DB_QueryStatus - (*lookup_pending_webhooks)(void *cls, - TALER_MERCHANTDB_PendingWebhooksCallback cb, - void *cb_cls); + (*lookup_pending_webhooks)(void *cls, + TALER_MERCHANTDB_PendingWebhooksCallback cb, + void *cb_cls); /** * Lookup future webhook in the pending webhook that need to be send. @@ -3594,9 +3604,9 @@ struct TALER_MERCHANTDB_Plugin */ // ORDER BY next_attempt ASC LIMIT 1 enum GNUNET_DB_QueryStatus - (*lookup_future_webhook)(void *cls, - TALER_MERCHANTDB_PendingWebhooksCallback cb, - void *cb_cls); + (*lookup_future_webhook)(void *cls, + TALER_MERCHANTDB_PendingWebhooksCallback cb, + void *cb_cls); /** * Lookup all the webhooks in the pending webhook. @@ -3611,12 +3621,12 @@ struct TALER_MERCHANTDB_Plugin */ // WHERE webhook_pending_serial > min_row ORDER BY webhook_pending_serial ASC LIMIT max_results enum GNUNET_DB_QueryStatus - (*lookup_all_webhooks)(void *cls, - const char *instance_id, - uint64_t min_row, - uint32_t max_results, - TALER_MERCHANTDB_PendingWebhooksCallback cb, - void *cb_cls); + (*lookup_all_webhooks)(void *cls, + const char *instance_id, + uint64_t min_row, + uint32_t max_results, + TALER_MERCHANTDB_PendingWebhooksCallback cb, + void *cb_cls); /** @@ -3628,9 +3638,9 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*update_pending_webhook)(void *cls, - uint64_t webhook_pending_serial, - struct GNUNET_TIME_Absolute next_attempt); + (*update_pending_webhook)(void *cls, + uint64_t webhook_pending_serial, + struct GNUNET_TIME_Absolute next_attempt); // maybe add: http status of failure? @@ -3643,8 +3653,8 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*delete_pending_webhook)(void *cls, - uint64_t webhook_pending_serial); + (*delete_pending_webhook)(void *cls, + uint64_t webhook_pending_serial); /** @@ -3656,9 +3666,9 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*select_exchange_keys)(void *cls, - const char *exchange_url, - struct TALER_EXCHANGE_Keys **keys); + (*select_exchange_keys)(void *cls, + const char *exchange_url, + struct TALER_EXCHANGE_Keys **keys); /** @@ -3669,8 +3679,8 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*insert_exchange_keys)(void *cls, - const struct TALER_EXCHANGE_Keys *keys); + (*insert_exchange_keys)(void *cls, + const struct TALER_EXCHANGE_Keys *keys); /** @@ -3683,10 +3693,10 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*lookup_token_families)(void *cls, - const char *instance_id, - TALER_MERCHANTDB_TokenFamiliesCallback cb, - void *cb_cls); + (*lookup_token_families)(void *cls, + const char *instance_id, + TALER_MERCHANTDB_TokenFamiliesCallback cb, + void *cb_cls); /** * Lookup details about a particular token family. @@ -3699,10 +3709,10 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*lookup_token_family)(void *cls, - const char *instance_id, - const char *token_family_slug, - struct TALER_MERCHANTDB_TokenFamilyDetails *details); + (*lookup_token_family)(void *cls, + const char *instance_id, + const char *token_family_slug, + struct TALER_MERCHANTDB_TokenFamilyDetails *details); /** * Delete information about a token family. @@ -3713,9 +3723,9 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*delete_token_family)(void *cls, - const char *instance_id, - const char *token_family_slug); + (*delete_token_family)(void *cls, + const char *instance_id, + const char *token_family_slug); /** * Update details about a particular token family. @@ -3728,7 +3738,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*update_token_family)( + (*update_token_family)( void *cls, const char *instance_id, const char *token_family_slug, @@ -3745,7 +3755,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*insert_token_family)( + (*insert_token_family)( void *cls, const char *instance_id, const char *token_family_slug, @@ -3765,7 +3775,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*lookup_token_family_key)( + (*lookup_token_family_key)( void *cls, const char *instance_id, const char *token_family_slug, @@ -3787,7 +3797,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*lookup_token_family_keys)( + (*lookup_token_family_keys)( void *cls, const char *instance_id, const char *token_family_slug, @@ -3812,7 +3822,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*insert_token_family_key)( + (*insert_token_family_key)( void *cls, const char *merchant_id, const char *token_family_slug, @@ -3834,7 +3844,7 @@ struct TALER_MERCHANTDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*lookup_pending_deposits)( + (*lookup_pending_deposits)( void *cls, const char *exchange_url, uint64_t limit, @@ -3856,7 +3866,7 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*update_deposit_confirmation_status)( + (*update_deposit_confirmation_status)( void *cls, uint64_t deposit_serial, bool wire_pending, diff --git a/src/merchant-tools/taler-merchant-dbinit.c b/src/merchant-tools/taler-merchant-dbinit.c @@ -37,6 +37,11 @@ static int global_ret; static int reset_db; /** + * -g option: garbage collect DB + */ +static int gc_db; + +/** * Main function that will be run. * * @param cls closure @@ -72,12 +77,25 @@ run (void *cls, "Failed to reset the database\n"); } } - if (GNUNET_OK != - plugin->create_tables (plugin->cls)) + if (gc_db) { - global_ret = 1; - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to initialize tables\n"); + if (GNUNET_OK != + plugin->gc (plugin->cls)) + { + global_ret = 1; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to garbage collect database\n"); + } + } + else + { + if (GNUNET_OK != + plugin->create_tables (plugin->cls)) + { + global_ret = 1; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to initialize tables\n"); + } } TALER_MERCHANTDB_plugin_unload (plugin); GNUNET_CONFIGURATION_destroy (cfg); @@ -97,6 +115,10 @@ main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_flag ('g', + "gc", + "garbage collect database", + &gc_db), GNUNET_GETOPT_option_flag ('r', "reset", "reset database (DANGEROUS: all existing data is lost!)",