merchant

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

commit e484532d9604491ddd5e8b37fb8cd1d7d6202945
parent 819981a623029949ba904a1025fb4a473f610c2f
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat,  9 May 2026 00:05:44 +0200

split DB by instance into schema

Diffstat:
Msrc/backenddb/account_kyc_get_outdated.c | 22++++++++--------------
Msrc/backenddb/account_kyc_get_status.c | 36++++++++++++++++++++----------------
Msrc/backenddb/account_kyc_set_failed.c | 25++++++++++++-------------
Msrc/backenddb/account_kyc_set_status.c | 27+++++++++++++--------------
Msrc/backenddb/activate_account.c | 26+++++++++++++++-----------
Msrc/backenddb/check_donau_instance.c | 23+++++++++++------------
Msrc/backenddb/check_money_pots.c | 31++++++++++++++++---------------
Msrc/backenddb/check_report.c | 15+++++----------
Msrc/backenddb/check_transfer_exists.c | 28++++++++++++----------------
Msrc/backenddb/create_mfa_challenge.c | 43++++++++++++++++++++++---------------------
Msrc/backenddb/delete_category.c | 22+++++++++++-----------
Msrc/backenddb/delete_contract_terms.c | 26+++++++++++++-------------
Msrc/backenddb/delete_donau_instance.c | 19++++++++++---------
Msrc/backenddb/delete_instance_private_key.c | 18+++++++++---------
Msrc/backenddb/delete_login_token.c | 42+++++++++++++++++++++---------------------
Msrc/backenddb/delete_money_pot.c | 22+++++++++++-----------
Msrc/backenddb/delete_order.c | 61++++++++++++++++++++++++++++---------------------------------
Msrc/backenddb/delete_otp.c | 22+++++++++++-----------
Msrc/backenddb/delete_pending_webhook.c | 16++++++++++------
Msrc/backenddb/delete_product.c | 30+++++++++++++++---------------
Msrc/backenddb/delete_product_group.c | 22+++++++++++-----------
Msrc/backenddb/delete_report.c | 22+++++++++++-----------
Msrc/backenddb/delete_template.c | 22+++++++++++-----------
Msrc/backenddb/delete_token_family.c | 22+++++++++++-----------
Msrc/backenddb/delete_transfer.c | 24++++++++++--------------
Msrc/backenddb/delete_unit.c | 24+++++++++++++-----------
Msrc/backenddb/delete_webhook.c | 22+++++++++++-----------
Msrc/backenddb/expire_locks.c | 59++++++++++++++++++-----------------------------------------
Msrc/backenddb/finalize_transfer_status.c | 48++++++++++++++++++++++++++----------------------
Msrc/backenddb/get_kyc_limits.c | 36++++++++++++++++++------------------
Msrc/backenddb/get_kyc_status.c | 50+++++++++++++++++++++++++-------------------------
Msrc/backenddb/helper.h | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backenddb/inactivate_account.c | 18+++++++++++-------
Msrc/backenddb/increase_refund.c | 110+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/backenddb/increment_money_pots.c | 20++++++++++++--------
Msrc/backenddb/insert_account.c | 47++++++++++++++++++++++++-----------------------
Msrc/backenddb/insert_category.c | 30+++++++++++++++---------------
Msrc/backenddb/insert_contract_terms.c | 73++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/backenddb/insert_deposit.c | 33++++++++++++++++++---------------
Msrc/backenddb/insert_deposit_confirmation.c | 39+++++++++++++++++----------------------
Msrc/backenddb/insert_deposit_to_transfer.c | 17++++++++++-------
Msrc/backenddb/insert_donau_instance.c | 35++++++++++++++++-------------------
Msrc/backenddb/insert_instance.c | 28++++++++++++++++++----------
Msrc/backenddb/insert_issued_token.c | 19+++++++++++--------
Msrc/backenddb/insert_login_token.c | 31++++++++++++++++---------------
Msrc/backenddb/insert_money_pot.c | 27++++++++++++++-------------
Msrc/backenddb/insert_order.c | 41+++++++++++++++++++++--------------------
Msrc/backenddb/insert_order_blinded_sigs.c | 28++++++++++++++++------------
Msrc/backenddb/insert_order_lock.c | 99+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/backenddb/insert_otp.c | 32++++++++++++++++----------------
Msrc/backenddb/insert_pending_webhook.c | 33+++++++++++++++++----------------
Msrc/backenddb/insert_product.c | 54++++++++++++++++++++++++++++--------------------------
Msrc/backenddb/insert_product_group.c | 27++++++++++++++-------------
Msrc/backenddb/insert_refund_proof.c | 28++++++++++++++++------------
Msrc/backenddb/insert_report.c | 41+++++++++++++++++++++--------------------
Msrc/backenddb/insert_spent_token.c | 19+++++++++++--------
Msrc/backenddb/insert_template.c | 33+++++++++++++++++----------------
Msrc/backenddb/insert_token_family.c | 84+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/backenddb/insert_token_family_key.c | 89+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/backenddb/insert_transfer.c | 26++++++++++++++------------
Msrc/backenddb/insert_transfer_details.c | 38+++++++++++++++++++-------------------
Msrc/backenddb/insert_unclaim_signature.c | 20++++++++++++--------
Msrc/backenddb/insert_unit.c | 27++++++++++++++-------------
Msrc/backenddb/insert_webhook.c | 34+++++++++++++++++-----------------
Msrc/backenddb/lock_product.c | 100++++++++++++++++++++++++++++++++++++++-----------------------------------------
Msrc/backenddb/lookup_account.c | 28++++++++++++++--------------
Msrc/backenddb/lookup_all_products.c | 81++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/backenddb/lookup_categories.c | 37+++++++++++++++++++------------------
Msrc/backenddb/lookup_categories_by_ids.c | 39++++++++++++++++++++-------------------
Msrc/backenddb/lookup_contract_terms.c | 28++++++++++++++--------------
Msrc/backenddb/lookup_contract_terms2.c | 34+++++++++++++++++-----------------
Msrc/backenddb/lookup_contract_terms3.c | 36++++++++++++++++++------------------
Msrc/backenddb/lookup_custom_units_by_names.c | 42+++++++++++++++++++++---------------------
Msrc/backenddb/lookup_deposits.c | 42+++++++++++++++++++++---------------------
Msrc/backenddb/lookup_deposits_by_contract_and_coin.c | 96+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/backenddb/lookup_deposits_by_order.c | 38+++++++++++++++++++++-----------------
Msrc/backenddb/lookup_expected_transfer.c | 49+++++++++++++++++++++++++------------------------
Msrc/backenddb/lookup_expected_transfers.c | 154++++++++++++++++++++++++++++++++++++++-----------------------------------------
Msrc/backenddb/lookup_instances.c | 193++++++++++++++++++++++++++++---------------------------------------------------
Msrc/backenddb/lookup_inventory_products.c | 123++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/backenddb/lookup_inventory_products_filtered.c | 147++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/backenddb/lookup_login_tokens.c | 74++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/backenddb/lookup_mfa_challenge.c | 39+++++++++++++++++++--------------------
Msrc/backenddb/lookup_order.c | 28++++++++++++++--------------
Msrc/backenddb/lookup_order_by_fulfillment.c | 50+++++++++++++++++++++++++-------------------------
Msrc/backenddb/lookup_order_charity.c | 40++++++++++++++++++++--------------------
Msrc/backenddb/lookup_order_status.c | 26+++++++++++++-------------
Msrc/backenddb/lookup_order_status_by_serial.c | 28++++++++++++++--------------
Msrc/backenddb/lookup_order_summary.c | 42+++++++++++++++++++-----------------------
Msrc/backenddb/lookup_orders.c | 261+++++++++++++++++++++++++++++++++++++------------------------------------------
Msrc/backenddb/lookup_otp_devices.c | 23++++++++++++-----------
Msrc/backenddb/lookup_pending_deposits.c | 60+++++++++++++++++++++---------------------------------------
Msrc/backenddb/lookup_pending_webhooks.c | 91+++++++++++++++++++++++++++++++++++++------------------------------------------
Msrc/backenddb/lookup_product.c | 83++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/backenddb/lookup_product_image.c | 15++++++++-------
Msrc/backenddb/lookup_products.c | 110+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/backenddb/lookup_reconciliation_details.c | 47++++++++++++++++++++++++-----------------------
Msrc/backenddb/lookup_refund_proof.c | 26+++++++++++++++-----------
Msrc/backenddb/lookup_refunds.c | 32++++++++++++++++----------------
Msrc/backenddb/lookup_refunds_detailed.c | 56++++++++++++++++++++++++++++----------------------------
Msrc/backenddb/lookup_reports_pending.c | 50+++++++++++++++++++++++---------------------------
Msrc/backenddb/lookup_spent_tokens_by_order.c | 36++++++++++++++++++++----------------
Msrc/backenddb/lookup_statistics_amount_by_bucket.c | 39++++++++++++++++++++-------------------
Msrc/backenddb/lookup_statistics_amount_by_bucket2.c | 43++++++++++++++++++++++---------------------
Msrc/backenddb/lookup_statistics_amount_by_interval.c | 30++++++++++++++++++------------
Msrc/backenddb/lookup_statistics_counter_by_bucket.c | 37+++++++++++++++++++------------------
Msrc/backenddb/lookup_statistics_counter_by_bucket2.c | 41+++++++++++++++++++++--------------------
Msrc/backenddb/lookup_statistics_counter_by_interval.c | 30++++++++++++++++++------------
Msrc/backenddb/lookup_template.c | 35++++++++++++++++++-----------------
Msrc/backenddb/lookup_templates.c | 23++++++++++++-----------
Msrc/backenddb/lookup_token_families.c | 33+++++++++++++++++----------------
Msrc/backenddb/lookup_token_family.c | 54+++++++++++++++++++++++++++---------------------------
Msrc/backenddb/lookup_token_family_key.c | 75++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/backenddb/lookup_token_family_keys.c | 67++++++++++++++++++++++++++++++++++---------------------------------
Msrc/backenddb/lookup_transfer_details.c | 44++++++++++++++++++++++++--------------------
Msrc/backenddb/lookup_transfer_details_by_order.c | 55+++++++++++++++++++++++++++++--------------------------
Msrc/backenddb/lookup_transfer_summary.c | 40++++++++++++++++++++++------------------
Msrc/backenddb/lookup_transfers.c | 142++++++++++++++++++++++++++++++++++++++-----------------------------------------
Msrc/backenddb/lookup_units.c | 70++++++++++++++++++++++++++++++++++------------------------------------
Msrc/backenddb/lookup_webhook.c | 33+++++++++++++++++----------------
Msrc/backenddb/lookup_webhook_by_event.c | 33+++++++++++++++++----------------
Msrc/backenddb/lookup_webhooks.c | 23++++++++++++-----------
Msrc/backenddb/mark_contract_paid.c | 98+++++++++++++++++++++++++++++++++++++------------------------------------------
Msrc/backenddb/mark_order_wired.c | 16++++++++++------
Msrc/backenddb/meson.build | 1+
Msrc/backenddb/pg.c | 1+
Asrc/backenddb/pg_account_kyc_get_outdated.sql | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_create_instance_schema.sql | 39+++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_create_instance_schema_procedures.sql.fragment | 2214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_create_instance_schema_tables.sql.fragment | 961+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_create_instance_schema_triggers.sql.fragment | 736+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_create_instance_trigger.sql | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_expire_locks.sql | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_instances.sql | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_pending_deposits.sql | 230+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_pending_webhooks.sql | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_select_accounts.sql | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_select_all_donau_instances.sql | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_select_open_transfers.sql | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backenddb/purge_instance.c | 17+++++++++++++++++
Msrc/backenddb/refund_coin.c | 63++++++++++++++++++++++++++++++++-------------------------------
Msrc/backenddb/select_account.c | 34+++++++++++++++++-----------------
Msrc/backenddb/select_account_by_uri.c | 32++++++++++++++++----------------
Msrc/backenddb/select_accounts.c | 48+++++++++++++++++++-----------------------------
Msrc/backenddb/select_all_donau_instances.c | 42+++++++++++++++++++-----------------------
Msrc/backenddb/select_category.c | 52++++++++++++++++++++++++++--------------------------
Msrc/backenddb/select_category_by_name.c | 25+++++++++++++------------
Msrc/backenddb/select_donau_instance_by_serial.c | 18+++++++++++-------
Msrc/backenddb/select_donau_instances.c | 44++++++++++++++++++++++++--------------------
Msrc/backenddb/select_donau_instances_filtered.c | 7+++----
Msrc/backenddb/select_login_token.c | 26+++++++++++++-------------
Msrc/backenddb/select_money_pot.c | 27++++++++++++++-------------
Msrc/backenddb/select_money_pots.c | 58+++++++++++++++++++++++++++++-----------------------------
Msrc/backenddb/select_open_transfers.c | 33+++++++++++++--------------------
Msrc/backenddb/select_order_blinded_sigs.c | 24++++++++++++++----------
Msrc/backenddb/select_otp.c | 34+++++++++++++++++-----------------
Msrc/backenddb/select_otp_serial.c | 23++++++++++++-----------
Msrc/backenddb/select_product_groups.c | 58+++++++++++++++++++++++++++++-----------------------------
Msrc/backenddb/select_report.c | 41+++++++++++++++++++++--------------------
Msrc/backenddb/select_reports.c | 62+++++++++++++++++++++++++++++++-------------------------------
Msrc/backenddb/select_unit.c | 103++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/backenddb/select_wirewatch_accounts.c | 26+++++++++++---------------
Asrc/backenddb/set_instance.c | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backenddb/solve_mfa_challenge.c | 19+++++++++++--------
Msrc/backenddb/sql-schema/gen-procedures.sh | 48++++++++++++++++++++++++++++++++++++++----------
Asrc/backenddb/sql-schema/merchant-0036-copy.sql.fragment | 504+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/sql-schema/merchant-0036-drop.sql.fragment | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/sql-schema/merchant-0036-setval.sql.fragment | 35+++++++++++++++++++++++++++++++++++
Asrc/backenddb/sql-schema/merchant-0036.sql.in | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backenddb/sql-schema/meson.build | 1+
Msrc/backenddb/unlock_inventory.c | 16++++++++++------
Msrc/backenddb/update_account.c | 28++++++++++++++--------------
Msrc/backenddb/update_category.c | 24++++++++++++------------
Msrc/backenddb/update_contract_session.c | 34+++++++++++++++++-----------------
Msrc/backenddb/update_contract_terms.c | 31++++++++++++++++---------------
Msrc/backenddb/update_deposit_confirmation_status.c | 24++++++++++++++----------
Msrc/backenddb/update_donau_instance.c | 28+++++++++++++---------------
Msrc/backenddb/update_donau_instance_receipts_amount.c | 16++++++++++------
Msrc/backenddb/update_mfa_challenge.c | 23+++++++++++++----------
Msrc/backenddb/update_money_pot.c | 24++++++++++++++----------
Msrc/backenddb/update_otp.c | 28++++++++++++++--------------
Msrc/backenddb/update_pending_webhook.c | 18+++++++++++-------
Msrc/backenddb/update_product.c | 64+++++++++++++++++++++++++++++++++-------------------------------
Msrc/backenddb/update_product_group.c | 22+++++++++++++---------
Msrc/backenddb/update_report.c | 36++++++++++++++++++------------------
Msrc/backenddb/update_report_status.c | 26+++++++++++++-------------
Msrc/backenddb/update_template.c | 44++++++++++++++++++++------------------------
Msrc/backenddb/update_token_family.c | 32++++++++++++++++----------------
Msrc/backenddb/update_transfer_status.c | 26+++++++++++++++-----------
Msrc/backenddb/update_unit.c | 26++++++++++++++------------
Msrc/backenddb/update_webhook.c | 30+++++++++++++++---------------
Msrc/backenddb/update_wirewatch_progress.c | 24++++++++++++------------
Asrc/include/merchant-database/set_instance.h | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
193 files changed, 9494 insertions(+), 3639 deletions(-)

diff --git a/src/backenddb/account_kyc_get_outdated.c b/src/backenddb/account_kyc_get_outdated.c @@ -76,11 +76,11 @@ kyc_status_cb (void *cls, char *exchange_url; char *instance_id; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("h_wire", + GNUNET_PQ_result_spec_auto_from_type ("out_h_wire", &h_wire), - GNUNET_PQ_result_spec_string ("exchange_url", + GNUNET_PQ_result_spec_string ("out_exchange_url", &exchange_url), - GNUNET_PQ_result_spec_string ("instance_id", + GNUNET_PQ_result_spec_string ("out_merchant_id", &instance_id), GNUNET_PQ_result_spec_end }; @@ -123,17 +123,11 @@ TALER_MERCHANTDB_account_kyc_get_outdated (struct TALER_MERCHANTDB_PostgresConte check_connection (pg); PREPARE (pg, "account_kyc_get_outdated", - "SELECT " - " mi.merchant_id" - " ,ma.h_wire" - " ,kyc.exchange_url" - " FROM merchant_kyc kyc" - " JOIN merchant_accounts ma" - " USING (account_serial)" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " WHERE kyc.next_kyc_poll < $1" - " ORDER BY kyc.next_kyc_poll ASC;"); + "SELECT" + " out_merchant_id" + " ,out_h_wire" + " ,out_exchange_url" + " FROM merchant.account_kyc_get_outdated($1)"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, "account_kyc_get_outdated", diff --git a/src/backenddb/account_kyc_get_status.c b/src/backenddb/account_kyc_get_status.c @@ -172,7 +172,6 @@ TALER_MERCHANTDB_account_kyc_get_status ( struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (merchant_id), GNUNET_PQ_query_param_absolute_time (&now), NULL == exchange_url ? GNUNET_PQ_query_param_null () @@ -183,25 +182,30 @@ TALER_MERCHANTDB_account_kyc_get_status ( GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (merchant_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "account_kyc_get_status", - "SELECT " - " out_h_wire AS h_wire" - " ,out_payto_uri AS payto_uri" - " ,out_exchange_url AS exchange_url" - " ,out_kyc_timestamp AS kyc_timestamp" - " ,out_kyc_ok AS kyc_ok" - " ,out_access_token AS access_token" - " ,out_exchange_http_status AS exchange_http_status" - " ,out_exchange_ec_code AS exchange_ec_code" - " ,out_aml_review AS aml_review" - " ,out_jaccount_limits::TEXT AS jaccount_limits" - " FROM merchant_do_account_kyc_get_status($1, $2, $3, $4);"); + PREPARE_INSTANCE (pg, + stmt, + "account_kyc_get_status", + "SELECT " + " out_h_wire AS h_wire" + " ,out_payto_uri AS payto_uri" + " ,out_exchange_url AS exchange_url" + " ,out_kyc_timestamp AS kyc_timestamp" + " ,out_kyc_ok AS kyc_ok" + " ,out_access_token AS access_token" + " ,out_exchange_http_status AS exchange_http_status" + " ,out_exchange_ec_code AS exchange_ec_code" + " ,out_aml_review AS aml_review" + " ,out_jaccount_limits::TEXT AS jaccount_limits" + " FROM merchant_do_account_kyc_get_status($1, $2, $3);"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "account_kyc_get_status", + stmt, params, &kyc_status_cb, &ksc); diff --git a/src/backenddb/account_kyc_set_failed.c b/src/backenddb/account_kyc_set_failed.c @@ -50,7 +50,6 @@ TALER_MERCHANTDB_account_kyc_set_failed (struct TALER_MERCHANTDB_PostgresContext = GNUNET_PQ_get_event_notify_channel (&hdr); uint32_t http_status32 = (uint32_t) exchange_http_status; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (merchant_id), GNUNET_PQ_query_param_auto_from_type (h_wire), GNUNET_PQ_query_param_string (exchange_url), GNUNET_PQ_query_param_timestamp (&timestamp), @@ -60,28 +59,29 @@ TALER_MERCHANTDB_account_kyc_set_failed (struct TALER_MERCHANTDB_PostgresContext GNUNET_PQ_query_param_string (notify2_s), GNUNET_PQ_query_param_end }; - bool no_instance; bool no_account; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_bool ("no_instance", - &no_instance), GNUNET_PQ_result_spec_bool ("no_account", &no_account), GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (merchant_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "account_kyc_set_failed", - "SELECT " - " out_no_instance AS no_instance" - " ,out_no_account AS no_account" - " FROM merchant_do_account_kyc_set_failed" - "($1, $2, $3, $4, $5, $6, $7, $8);"); + PREPARE_INSTANCE (pg, + stmt, + "account_kyc_set_failed", + "SELECT " + " out_no_account AS no_account" + " FROM merchant_do_account_kyc_set_failed" + "($1, $2, $3, $4, $5, $6, $7);"); qs = GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, - "account_kyc_set_failed", + stmt, params, rs); GNUNET_free (notify_s); @@ -93,7 +93,6 @@ TALER_MERCHANTDB_account_kyc_set_failed (struct TALER_MERCHANTDB_PostgresContext GNUNET_break (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs); return qs; } - GNUNET_break (! no_instance); GNUNET_break (! no_account); return qs; } diff --git a/src/backenddb/account_kyc_set_status.c b/src/backenddb/account_kyc_set_status.c @@ -59,7 +59,6 @@ TALER_MERCHANTDB_account_kyc_set_status (struct TALER_MERCHANTDB_PostgresContext uint32_t http_status32 = (uint32_t) exchange_http_status; uint32_t ec_code32 = (uint32_t) exchange_ec_code; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (merchant_id), GNUNET_PQ_query_param_auto_from_type (h_wire), GNUNET_PQ_query_param_string (exchange_url), GNUNET_PQ_query_param_timestamp (&timestamp), @@ -80,29 +79,30 @@ TALER_MERCHANTDB_account_kyc_set_status (struct TALER_MERCHANTDB_PostgresContext GNUNET_PQ_query_param_relative_time (&kyc_backoff), GNUNET_PQ_query_param_end }; - bool no_instance; bool no_account; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_bool ("no_instance", - &no_instance), GNUNET_PQ_result_spec_bool ("no_account", &no_account), GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (merchant_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "account_kyc_set_status", - "SELECT " - " out_no_instance AS no_instance" - " ,out_no_account AS no_account" - " FROM merchant_do_account_kyc_set_status" - "($1, $2, $3, $4, $5, $6, $7, $8::TEXT::JSONB" - ",$9, $10, $11, $12, $13, $14, $15);"); + PREPARE_INSTANCE (pg, + stmt, + "account_kyc_set_status", + "SELECT " + " out_no_account AS no_account" + " FROM merchant_do_account_kyc_set_status" + "($1, $2, $3, $4, $5, $6, $7::TEXT::JSONB" + ",$8, $9, $10, $11, $12, $13, $14);"); qs = GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, - "account_kyc_set_status", + stmt, params, rs); GNUNET_free (notify_s); @@ -114,7 +114,6 @@ TALER_MERCHANTDB_account_kyc_set_status (struct TALER_MERCHANTDB_PostgresContext GNUNET_break (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs); return qs; } - GNUNET_break (! no_instance); GNUNET_break (! no_account); return qs; } diff --git a/src/backenddb/activate_account.c b/src/backenddb/activate_account.c @@ -35,7 +35,6 @@ TALER_MERCHANTDB_activate_account (struct TALER_MERCHANTDB_PostgresContext *pg, bool *conflict) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (account_details->instance_id), GNUNET_PQ_query_param_auto_from_type (&account_details->h_wire), GNUNET_PQ_query_param_auto_from_type (&account_details->salt), GNUNET_PQ_query_param_string (account_details->payto_uri.full_payto), @@ -62,20 +61,25 @@ TALER_MERCHANTDB_activate_account (struct TALER_MERCHANTDB_PostgresContext *pg, salt), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; GNUNET_assert (account_details->active); + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (account_details->instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "activate_account", - "SELECT " - " out_h_wire AS h_wire" - " ,out_salt AS salt" - " ,out_conflict AS conflict" - " ,out_not_found AS not_found" - " FROM merchant_do_activate_account" - " ($1,$2,$3,$4,$5,$6,$7);"); + PREPARE_INSTANCE (pg, + stmt, + "activate_account", + "SELECT " + " out_h_wire AS h_wire" + " ,out_salt AS salt" + " ,out_conflict AS conflict" + " ,out_not_found AS not_found" + " FROM merchant_do_activate_account" + " ($1,$2,$3,$4,$5,$6);"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "activate_account", + stmt, params, rs); } diff --git a/src/backenddb/check_donau_instance.c b/src/backenddb/check_donau_instance.c @@ -36,26 +36,25 @@ TALER_MERCHANTDB_check_donau_instance (struct TALER_MERCHANTDB_PostgresContext * struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (donau_url), GNUNET_PQ_query_param_uint64 (&charity_id), - GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + (void) merchant_pub; + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "check_donau_instance", - "SELECT 1" - " FROM merchant_donau_instances" - " WHERE donau_url=$1" - " AND charity_id=$2 " - " AND merchant_instance_serial =" - " (SELECT merchant_serial" - " FROM merchant_instances mi" - " WHERE mi.merchant_pub = $3)"); + PREPARE_INSTANCE (pg, + stmt, + "check_donau_instance", + "SELECT 1" + " FROM merchant_donau_instances" + " WHERE donau_url=$1" + " AND charity_id=$2"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "check_donau_instance", + stmt, params, rs); diff --git a/src/backenddb/check_money_pots.c b/src/backenddb/check_money_pots.c @@ -34,7 +34,6 @@ TALER_MERCHANTDB_check_money_pots (struct TALER_MERCHANTDB_PostgresContext *pg, uint64_t *pot_missing) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_array_uint64 (pots_len, pots, pg->conn), @@ -46,24 +45,26 @@ TALER_MERCHANTDB_check_money_pots (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "check_money_pots", - "SELECT n AS out_missing" - " FROM UNNEST($2::INT8[]) AS n" - " WHERE NOT EXISTS (" - " SELECT 1" - " FROM merchant_money_pots mmp" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " WHERE mmp.money_pot_serial=n" - " AND mi.merchant_id=$1" - " )" - " LIMIT 1;"); + PREPARE_INSTANCE (pg, + stmt, + "check_money_pots", + "SELECT n AS out_missing" + " FROM UNNEST($1::INT8[]) AS n" + " WHERE NOT EXISTS (" + " SELECT 1" + " FROM merchant_money_pots mmp" + " WHERE mmp.money_pot_serial=n" + " )" + " LIMIT 1;"); qs = GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, - "check_money_pots", + stmt, params, rs); GNUNET_PQ_cleanup_query_params_closures (params); diff --git a/src/backenddb/check_report.c b/src/backenddb/check_report.c @@ -40,9 +40,9 @@ TALER_MERCHANTDB_check_report (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_string ("merchant_id", + GNUNET_PQ_result_spec_string ("out_merchant_id", instance_id), - GNUNET_PQ_result_spec_string ("data_source", + GNUNET_PQ_result_spec_string ("out_data_source", data_source), GNUNET_PQ_result_spec_end }; @@ -51,14 +51,9 @@ TALER_MERCHANTDB_check_report (struct TALER_MERCHANTDB_PostgresContext *pg, PREPARE (pg, "check_report", "SELECT" - " mi.merchant_id" - " ,mr.data_source" - " FROM merchant_reports mr" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " WHERE mr.report_serial=$1" - " AND mr.report_token=$2" - " AND mr.mime_type=$3;"); + " out_merchant_id" + " ,out_data_source" + " FROM merchant.check_report($1, $2, $3)"); return GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, "check_report", diff --git a/src/backenddb/check_transfer_exists.c b/src/backenddb/check_transfer_exists.c @@ -31,32 +31,28 @@ TALER_MERCHANTDB_check_transfer_exists (struct TALER_MERCHANTDB_PostgresContext uint64_t transfer_serial_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&transfer_serial_id), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "check_transfer_exists", - "SELECT" - " 1" - " FROM merchant_transfers" - " JOIN merchant_accounts" - " USING (account_serial)" - " WHERE" - " credit_serial=$2" - " AND" - " merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"); + PREPARE_INSTANCE (pg, + stmt, + "check_transfer_exists", + "SELECT" + " 1" + " FROM merchant_transfers" + " WHERE credit_serial=$1"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "check_transfer_exists", + stmt, params, rs); } diff --git a/src/backenddb/create_mfa_challenge.c b/src/backenddb/create_mfa_challenge.c @@ -55,7 +55,6 @@ TALER_MERCHANTDB_create_mfa_challenge ( GNUNET_PQ_query_param_absolute_time (&retransmission_date), GNUNET_PQ_query_param_string (channel_str), GNUNET_PQ_query_param_string (required_address), /* $9 */ - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { @@ -63,28 +62,30 @@ TALER_MERCHANTDB_create_mfa_challenge ( challenge_id), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; - PREPARE (pg, - "create_mfa_challenge", - "INSERT INTO tan_challenges" - " (h_body" - " ,salt" - " ,op" - " ,code" - " ,creation_date" - " ,expiration_date" - " ,retransmission_date" - " ,retry_counter" /* always set to 3 */ - " ,tan_channel" - " ,required_address" - " ,merchant_serial)" - " SELECT" - " $1, $2, $3, $4, $5, $6, $7, 3, $8, $9, merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$10" - " RETURNING challenge_id;"); + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); + PREPARE_INSTANCE (pg, + stmt, + "create_mfa_challenge", + "INSERT INTO tan_challenges" + " (h_body" + " ,salt" + " ,op" + " ,code" + " ,creation_date" + " ,expiration_date" + " ,retransmission_date" + " ,retry_counter" /* always set to 3 */ + " ,tan_channel" + " ,required_address)" + " VALUES" + " ($1, $2, $3, $4, $5, $6, $7, 3, $8, $9)" + " RETURNING challenge_id;"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "create_mfa_challenge", + stmt, params, rs); } diff --git a/src/backenddb/delete_category.c b/src/backenddb/delete_category.c @@ -32,22 +32,22 @@ TALER_MERCHANTDB_delete_category (struct TALER_MERCHANTDB_PostgresContext *pg, uint64_t category_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&category_id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_category", - "DELETE" - " FROM merchant_categories" - " WHERE merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND category_serial=$2"); + PREPARE_INSTANCE (pg, + stmt, + "delete_category", + "DELETE" + " FROM merchant_categories" + " WHERE category_serial=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_category", + stmt, params); } diff --git a/src/backenddb/delete_contract_terms.c b/src/backenddb/delete_contract_terms.c @@ -33,26 +33,26 @@ TALER_MERCHANTDB_delete_contract_terms (struct TALER_MERCHANTDB_PostgresContext { struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_relative_time (&legal_expiration), GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_contract_terms", - "DELETE FROM merchant_contract_terms" - " WHERE order_id=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND ( ( (pay_deadline < $4) AND" - " (NOT paid) ) OR" - " (creation_time + $3 < $4) )"); + PREPARE_INSTANCE (pg, + stmt, + "delete_contract_terms", + "DELETE FROM merchant_contract_terms" + " WHERE order_id=$1" + " AND ( ( (pay_deadline < $3) AND" + " (NOT paid) ) OR" + " (creation_time + $2 < $3) )"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_contract_terms", + stmt, params); } diff --git a/src/backenddb/delete_donau_instance.c b/src/backenddb/delete_donau_instance.c @@ -33,19 +33,20 @@ TALER_MERCHANTDB_delete_donau_instance (struct TALER_MERCHANTDB_PostgresContext { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&donau_serial_id), - GNUNET_PQ_query_param_string (id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_donau_instance", - "DELETE FROM merchant_donau_instances di" - " USING merchant_instances mi" - " WHERE di.merchant_instance_serial = mi.merchant_serial" - " AND di.donau_instances_serial = $1" - " AND mi.merchant_id = $2;"); + PREPARE_INSTANCE (pg, + stmt, + "delete_donau_instance", + "DELETE FROM merchant_donau_instances" + " WHERE donau_instances_serial = $1;"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_donau_instance", + stmt, params); } \ No newline at end of file diff --git a/src/backenddb/delete_instance_private_key.c b/src/backenddb/delete_instance_private_key.c @@ -30,19 +30,19 @@ TALER_MERCHANTDB_delete_instance_private_key (struct TALER_MERCHANTDB_PostgresCo const char *merchant_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (merchant_id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (merchant_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_key", - "DELETE FROM merchant_keys" - " USING merchant_instances" - " WHERE merchant_keys.merchant_serial" - " = merchant_instances.merchant_serial" - " AND merchant_instances.merchant_id = $1"); + PREPARE_INSTANCE (pg, + stmt, + "delete_key", + "DELETE FROM merchant_keys"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_key", + stmt, params); } diff --git a/src/backenddb/delete_login_token.c b/src/backenddb/delete_login_token.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023 Taler Systems SA + Copyright (C) 2023, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -31,22 +31,22 @@ TALER_MERCHANTDB_delete_login_token_serial (struct TALER_MERCHANTDB_PostgresCont uint64_t serial) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (id), GNUNET_PQ_query_param_uint64 (&serial), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_login_token_serial", - "DELETE FROM merchant_login_tokens" - " WHERE serial=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"); + PREPARE_INSTANCE (pg, + stmt, + "delete_login_token_serial", + "DELETE FROM merchant_login_tokens" + " WHERE serial=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_login_token_serial", + stmt, params); } @@ -57,21 +57,21 @@ TALER_MERCHANTDB_delete_login_token (struct TALER_MERCHANTDB_PostgresContext *pg const struct TALER_MERCHANTDB_LoginTokenP *token) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (id), GNUNET_PQ_query_param_auto_from_type (token), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_login_token", - "DELETE FROM merchant_login_tokens" - " WHERE token=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"); + PREPARE_INSTANCE (pg, + stmt, + "delete_login_token", + "DELETE FROM merchant_login_tokens" + " WHERE token=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_login_token", + stmt, params); } diff --git a/src/backenddb/delete_money_pot.c b/src/backenddb/delete_money_pot.c @@ -32,22 +32,22 @@ TALER_MERCHANTDB_delete_money_pot (struct TALER_MERCHANTDB_PostgresContext *pg, uint64_t money_pot_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&money_pot_id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_money_pot", - "DELETE" - " FROM merchant_money_pots" - " WHERE merchant_money_pots.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND merchant_money_pots.money_pot_serial=$2"); + PREPARE_INSTANCE (pg, + stmt, + "delete_money_pot", + "DELETE" + " FROM merchant_money_pots" + " WHERE money_pot_serial=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_money_pot", + stmt, params); } diff --git a/src/backenddb/delete_order.c b/src/backenddb/delete_order.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -33,56 +33,51 @@ TALER_MERCHANTDB_delete_order (struct TALER_MERCHANTDB_PostgresContext *pg, { struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_bool (force), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_QueryParam params2[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs2; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + char stmt2[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_order", - "WITH ms AS" - "(SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - ", mc AS" - "(SELECT paid" - " FROM merchant_contract_terms" - " JOIN ms USING (merchant_serial)" - " WHERE order_id=$2) " - "DELETE" - " FROM merchant_orders mo" - " WHERE order_id=$2" - " AND merchant_serial=(SELECT merchant_serial FROM ms)" - " AND ( (pay_deadline < $3)" - " OR (NOT EXISTS (SELECT paid FROM mc))" - " OR ($4 AND (FALSE=(SELECT paid FROM mc))) );"); + PREPARE_INSTANCE (pg, + stmt, + "delete_order", + "WITH mc AS" + "(SELECT paid" + " FROM merchant_contract_terms" + " WHERE order_id=$1) " + "DELETE" + " FROM merchant_orders mo" + " WHERE order_id=$1" + " AND ( (pay_deadline < $2)" + " OR (NOT EXISTS (SELECT paid FROM mc))" + " OR ($3 AND (FALSE=(SELECT paid FROM mc))) );"); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_order", + stmt, params); if ( (qs < 0) || (! force) ) return qs; - PREPARE (pg, - "delete_contract", - "DELETE" - " FROM merchant_contract_terms" - " WHERE order_id=$2 AND" - " merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND NOT paid;"); + PREPARE_INSTANCE (pg, + stmt2, + "delete_contract", + "DELETE" + " FROM merchant_contract_terms" + " WHERE order_id=$1" + " AND NOT paid;"); qs2 = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_contract", + stmt2, params2); if (qs2 < 0) return qs2; diff --git a/src/backenddb/delete_otp.c b/src/backenddb/delete_otp.c @@ -32,22 +32,22 @@ TALER_MERCHANTDB_delete_otp (struct TALER_MERCHANTDB_PostgresContext *pg, const char *otp_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (otp_id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_otp", - "DELETE" - " FROM merchant_otp_devices" - " WHERE merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND otp_id=$2"); + PREPARE_INSTANCE (pg, + stmt, + "delete_otp", + "DELETE" + " FROM merchant_otp_devices" + " WHERE otp_id=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_otp", + stmt, params); } diff --git a/src/backenddb/delete_pending_webhook.c b/src/backenddb/delete_pending_webhook.c @@ -34,14 +34,18 @@ TALER_MERCHANTDB_delete_pending_webhook (struct TALER_MERCHANTDB_PostgresContext GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "delete_pending_webhook", - "DELETE" - " FROM merchant_pending_webhooks" - " WHERE webhook_pending_serial=$1"); + PREPARE_INSTANCE (pg, + stmt, + "delete_pending_webhook", + "DELETE" + " FROM merchant_pending_webhooks" + " WHERE webhook_pending_serial=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_pending_webhook", + stmt, params); } diff --git a/src/backenddb/delete_product.c b/src/backenddb/delete_product.c @@ -31,26 +31,26 @@ TALER_MERCHANTDB_delete_product (struct TALER_MERCHANTDB_PostgresContext *pg, const char *product_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (product_id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_product", - "DELETE" - " FROM merchant_inventory" - " WHERE merchant_inventory.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND merchant_inventory.product_id=$2" - " AND product_serial NOT IN " - " (SELECT product_serial FROM merchant_order_locks)" - " AND product_serial NOT IN " - " (SELECT product_serial FROM merchant_inventory_locks)"); + PREPARE_INSTANCE (pg, + stmt, + "delete_product", + "DELETE" + " FROM merchant_inventory" + " WHERE product_id=$1" + " AND product_serial NOT IN " + " (SELECT product_serial FROM merchant_order_locks)" + " AND product_serial NOT IN " + " (SELECT product_serial FROM merchant_inventory_locks)"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_product", + stmt, params); } diff --git a/src/backenddb/delete_product_group.c b/src/backenddb/delete_product_group.c @@ -31,22 +31,22 @@ TALER_MERCHANTDB_delete_product_group (struct TALER_MERCHANTDB_PostgresContext * uint64_t product_group_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&product_group_id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_product_group", - "DELETE" - " FROM merchant_product_groups" - " WHERE merchant_product_groups.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND merchant_product_groups.product_group_serial=$2"); + PREPARE_INSTANCE (pg, + stmt, + "delete_product_group", + "DELETE" + " FROM merchant_product_groups" + " WHERE product_group_serial=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_product_group", + stmt, params); } diff --git a/src/backenddb/delete_report.c b/src/backenddb/delete_report.c @@ -32,22 +32,22 @@ TALER_MERCHANTDB_delete_report (struct TALER_MERCHANTDB_PostgresContext *pg, uint64_t report_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&report_id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_report", - "DELETE" - " FROM merchant_reports" - " WHERE merchant_reports.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND merchant_reports.report_serial=$2"); + PREPARE_INSTANCE (pg, + stmt, + "delete_report", + "DELETE" + " FROM merchant_reports" + " WHERE report_serial=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_report", + stmt, params); } diff --git a/src/backenddb/delete_template.c b/src/backenddb/delete_template.c @@ -32,22 +32,22 @@ TALER_MERCHANTDB_delete_template (struct TALER_MERCHANTDB_PostgresContext *pg, const char *template_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (template_id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_template", - "DELETE" - " FROM merchant_template" - " WHERE merchant_template.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND merchant_template.template_id=$2"); + PREPARE_INSTANCE (pg, + stmt, + "delete_template", + "DELETE" + " FROM merchant_template" + " WHERE template_id=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_template", + stmt, params); } diff --git a/src/backenddb/delete_token_family.c b/src/backenddb/delete_token_family.c @@ -31,22 +31,22 @@ TALER_MERCHANTDB_delete_token_family (struct TALER_MERCHANTDB_PostgresContext *p const char *token_family_slug) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (token_family_slug), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_token_family", - "DELETE" - " FROM merchant_token_families" - " WHERE merchant_token_families.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND slug=$2"); + PREPARE_INSTANCE (pg, + stmt, + "delete_token_family", + "DELETE" + " FROM merchant_token_families" + " WHERE slug=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_token_family", + stmt, params); } \ No newline at end of file diff --git a/src/backenddb/delete_transfer.c b/src/backenddb/delete_transfer.c @@ -32,26 +32,22 @@ TALER_MERCHANTDB_delete_transfer (struct TALER_MERCHANTDB_PostgresContext *pg, uint64_t transfer_serial_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&transfer_serial_id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_transfer", - "DELETE FROM merchant_transfers" - " WHERE" - " credit_serial=$2" - " AND account_serial IN " - " (SELECT account_serial " - " FROM merchant_accounts" - " WHERE merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1))"); + PREPARE_INSTANCE (pg, + stmt, + "delete_transfer", + "DELETE FROM merchant_transfers" + " WHERE credit_serial=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_transfer", + stmt, params); } diff --git a/src/backenddb/delete_unit.c b/src/backenddb/delete_unit.c @@ -35,13 +35,10 @@ TALER_MERCHANTDB_delete_unit (struct TALER_MERCHANTDB_PostgresContext *pg, bool *builtin_conflict) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (unit_id), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_bool ("out_no_instance", - no_instance), GNUNET_PQ_result_spec_bool ("out_no_unit", no_unit), GNUNET_PQ_result_spec_bool ("out_builtin_conflict", @@ -49,17 +46,22 @@ TALER_MERCHANTDB_delete_unit (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + *no_instance = false; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_unit", - "SELECT" - " out_no_instance" - " ,out_no_unit" - " ,out_builtin_conflict" - " FROM merchant_do_delete_unit($1,$2);"); + PREPARE_INSTANCE (pg, + stmt, + "delete_unit", + "SELECT" + " out_no_unit" + " ,out_builtin_conflict" + " FROM merchant_do_delete_unit($1);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "delete_unit", + stmt, params, rs); GNUNET_PQ_cleanup_query_params_closures (params); diff --git a/src/backenddb/delete_webhook.c b/src/backenddb/delete_webhook.c @@ -31,23 +31,23 @@ TALER_MERCHANTDB_delete_webhook (struct TALER_MERCHANTDB_PostgresContext *pg, const char *webhook_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (webhook_id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "delete_webhook", - "DELETE" - " FROM merchant_webhook" - " WHERE merchant_webhook.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND merchant_webhook.webhook_id=$2"); + PREPARE_INSTANCE (pg, + stmt, + "delete_webhook", + "DELETE" + " FROM merchant_webhook" + " WHERE webhook_id=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_webhook", + stmt, params); } diff --git a/src/backenddb/expire_locks.c b/src/backenddb/expire_locks.c @@ -34,52 +34,29 @@ TALER_MERCHANTDB_expire_locks (struct TALER_MERCHANTDB_PostgresContext *pg) GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_end }; - enum GNUNET_DB_QueryStatus qs1; - enum GNUNET_DB_QueryStatus qs2; - enum GNUNET_DB_QueryStatus qs3; + uint64_t total; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("expire_locks", + &total), + GNUNET_PQ_result_spec_end + }; + enum GNUNET_DB_QueryStatus qs; check_connection (pg); PREPARE (pg, - "unlock_products", - "DELETE FROM merchant_inventory_locks" - " WHERE expiration < $1"); - qs1 = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "unlock_products", - params); - if (qs1 < 0) - { - GNUNET_break (0); - return qs1; - } - PREPARE (pg, - "unlock_orders", - "DELETE FROM merchant_orders" - " WHERE pay_deadline < $1"); - qs2 = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "unlock_orders", - params); - if (qs2 < 0) - { - GNUNET_break (0); - return qs2; - } - PREPARE (pg, - "unlock_contracts", - "DELETE FROM merchant_contract_terms" - " WHERE NOT paid" - " AND pay_deadline < $1"); - qs3 = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "unlock_contracts", - params); - if (qs3 < 0) + "expire_locks", + "SELECT merchant.expire_locks($1) AS expire_locks"); + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "expire_locks", + params, + rs); + if (qs < 0) { GNUNET_break (0); - return qs3; + return qs; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Released %d+%d+%d locks\n", - qs1, - qs2, - qs3); - return qs1 + qs2 + qs3; + "Released %llu locks across all instances\n", + (unsigned long long) total); + return (enum GNUNET_DB_QueryStatus) total; } diff --git a/src/backenddb/finalize_transfer_status.c b/src/backenddb/finalize_transfer_status.c @@ -49,30 +49,34 @@ TALER_MERCHANTDB_finalize_transfer_status (struct TALER_MERCHANTDB_PostgresConte GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "finalize_transfer_status", - "WITH subquery AS (" - " SELECT signkey_serial" - " FROM merchant_exchange_signing_keys" - " WHERE exchange_pub=$6" - ")" - "UPDATE merchant_expected_transfers SET" - " last_http_status=200" - ",last_ec=0" - ",last_detail=NULL" - ",retry_needed=FALSE" - ",retry_time=0" - ",expected_credit_amount=$3" - ",wire_fee=$4" - ",h_details=$5" - ",signkey_serial=subquery.signkey_serial" - ",exchange_sig=$7" - " FROM subquery" - " WHERE wtid=$1" - " AND exchange_url=$2"); + PREPARE_INSTANCE (pg, + stmt, + "finalize_transfer_status", + "WITH subquery AS (" + " SELECT signkey_serial" + " FROM merchant.merchant_exchange_signing_keys" + " WHERE exchange_pub=$6" + ")" + "UPDATE merchant_expected_transfers SET" + " last_http_status=200" + ",last_ec=0" + ",last_detail=NULL" + ",retry_needed=FALSE" + ",retry_time=0" + ",expected_credit_amount=$3" + ",wire_fee=$4" + ",h_details=$5" + ",signkey_serial=subquery.signkey_serial" + ",exchange_sig=$7" + " FROM subquery" + " WHERE wtid=$1" + " AND exchange_url=$2"); return GNUNET_PQ_eval_prepared_non_select ( pg->conn, - "finalize_transfer_status", + stmt, params); } diff --git a/src/backenddb/get_kyc_limits.c b/src/backenddb/get_kyc_limits.c @@ -36,7 +36,6 @@ TALER_MERCHANTDB_get_kyc_limits (struct TALER_MERCHANTDB_PostgresContext *pg, { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (merchant_account_uri.full_payto), - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (exchange_url), GNUNET_PQ_query_param_end }; @@ -51,27 +50,28 @@ TALER_MERCHANTDB_get_kyc_limits (struct TALER_MERCHANTDB_PostgresContext *pg, NULL), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "get_kyc_limits", - "SELECT" - " mk.kyc_ok" - ",mk.jaccount_limits::TEXT" - ",mk.access_token IS NULL AS no_access_token" - " FROM merchant_kyc mk" - " WHERE mk.exchange_url=$3" - " AND mk.account_serial=" - " (SELECT account_serial" - " FROM merchant_accounts" - " WHERE payto_uri=$1" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$2));"); + PREPARE_INSTANCE (pg, + stmt, + "get_kyc_limits", + "SELECT" + " mk.kyc_ok" + ",mk.jaccount_limits::TEXT" + ",mk.access_token IS NULL AS no_access_token" + " FROM merchant_kyc mk" + " WHERE mk.exchange_url=$2" + " AND mk.account_serial=" + " (SELECT account_serial" + " FROM merchant_accounts" + " WHERE payto_uri=$1);"); *jlimits = NULL; return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "get_kyc_limits", + stmt, params, rs); } diff --git a/src/backenddb/get_kyc_status.c b/src/backenddb/get_kyc_status.c @@ -45,7 +45,6 @@ TALER_MERCHANTDB_get_kyc_status (struct TALER_MERCHANTDB_PostgresContext *pg, { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (merchant_account_uri.full_payto), - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (exchange_url), GNUNET_PQ_query_param_end }; @@ -80,34 +79,35 @@ TALER_MERCHANTDB_get_kyc_status (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "get_kyc_status", - "SELECT" - " mk.access_token" - ",mk.exchange_http_status" - ",mk.exchange_ec_code" - ",mk.kyc_ok" - ",mk.last_rule_gen" - ",mk.kyc_timestamp" - ",mk.next_kyc_poll" - ",mk.kyc_backoff" - ",mk.aml_review" - ",mk.jaccount_limits::TEXT" - " FROM merchant_kyc mk" - " WHERE mk.exchange_url=$3" - " AND mk.account_serial=" - " (SELECT account_serial" - " FROM merchant_accounts" - " WHERE payto_uri=$1" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$2));"); + PREPARE_INSTANCE (pg, + stmt, + "get_kyc_status", + "SELECT" + " mk.access_token" + ",mk.exchange_http_status" + ",mk.exchange_ec_code" + ",mk.kyc_ok" + ",mk.last_rule_gen" + ",mk.kyc_timestamp" + ",mk.next_kyc_poll" + ",mk.kyc_backoff" + ",mk.aml_review" + ",mk.jaccount_limits::TEXT" + " FROM merchant_kyc mk" + " WHERE mk.exchange_url=$2" + " AND mk.account_serial=" + " (SELECT account_serial" + " FROM merchant_accounts" + " WHERE payto_uri=$1);"); *jlimits = NULL; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "get_kyc_status", + stmt, params, rs); *last_ec = (enum TALER_ErrorCode) (int) e32; diff --git a/src/backenddb/helper.h b/src/backenddb/helper.h @@ -53,6 +53,20 @@ struct TALER_MERCHANTDB_PostgresContext const char *transaction_name; /** + * Instance id ("merchant_id") that the search_path is currently + * pointing at, or NULL if no per-instance schema is selected. + * Owned by this struct; set by TALER_MERCHANTDB_set_instance(). + */ + char *current_merchant_id; + + /** + * merchant_serial corresponding to @e current_merchant_id, or 0 + * if no per-instance schema is selected. Used as the suffix in + * per-instance prepared-statement names. + */ + uint64_t current_merchant_serial; + + /** * How many times have we connected to the DB. */ uint64_t prep_gen; @@ -93,6 +107,94 @@ struct TALER_MERCHANTDB_PostgresContext /** + * Maximum length (incl. NUL) of a per-instance prepared-statement name buffer. + * Generous: longest base name in this library is well under 56 chars and a + * 64-bit serial fits in 20 decimal digits + underscore + NUL. + */ +#define PG_PREP_INSTANCE_NAME_MAX 96 + + +/** + * Number of slots in each per-call-site (serial -> gen) cache used by + * #PREPARE_INSTANCE. A typical merchant deployment has a handful of + * instances; this is sized to comfortably cover that case. On a miss + * (cache thrash with more instances active than slots), the macro + * falls back to re-issuing PREPARE, which is correct as long as the + * statement was deallocated by the connection drop that bumped + * @e prep_gen. Increase if multi-tenant deployments thrash. + */ +#define PG_PREP_INSTANCE_CACHE_SLOTS 8 + + +/** + * Per-instance variant of #PREPARE. Prepares SQL statement @a sql + * under the connection-scoped name "<base>_<merchant_serial>" once + * per (call-site, merchant_serial, prep_gen). Writes the resolved + * name into @a sname (a caller-provided char buffer of at least + * #PG_PREP_INSTANCE_NAME_MAX bytes) so the caller can pass it to + * GNUNET_PQ_eval_prepared_*. + * + * Per-instance call sites must run with @e current_merchant_serial + * != 0 (i.e. after a successful TALER_MERCHANTDB_set_instance()). + * + * Returns with #GNUNET_DB_STATUS_HARD_ERROR on failure. + * + * @param pg a `struct TALER_MERCHANTDB_PostgresContext` + * @param sname caller-owned char buffer receiving the resolved name + * @param base base statement name (string literal) + * @param sql actual SQL text + */ +#define PREPARE_INSTANCE(pg,sname,base,sql) \ + do { \ + static struct { \ + uint64_t serial; \ + unsigned long long gen; \ + } _pi_cache[PG_PREP_INSTANCE_CACHE_SLOTS]; \ + static unsigned int _pi_next; \ + bool _pi_hit = false; \ + \ + GNUNET_assert (0 != (pg)->current_merchant_serial); \ + GNUNET_snprintf ((sname), \ + PG_PREP_INSTANCE_NAME_MAX, \ + "%s_%llu", \ + base, \ + (unsigned long long) (pg)-> \ + current_merchant_serial); \ + for (unsigned int _i = 0; \ + _i < PG_PREP_INSTANCE_CACHE_SLOTS; \ + _i++) \ + { \ + if ( (_pi_cache[_i].gen == (pg)->prep_gen) && \ + (_pi_cache[_i].serial == \ + (pg)->current_merchant_serial) ) \ + { \ + _pi_hit = true; \ + break; \ + } \ + } \ + if (! _pi_hit) \ + { \ + struct GNUNET_PQ_PreparedStatement _pi_ps[] = { \ + GNUNET_PQ_make_prepare ((sname), sql), \ + GNUNET_PQ_PREPARED_STATEMENT_END \ + }; \ + \ + if (GNUNET_OK != \ + GNUNET_PQ_prepare_statements ((pg)->conn, \ + _pi_ps)) \ + { \ + GNUNET_break (0); \ + return GNUNET_DB_STATUS_HARD_ERROR; \ + } \ + _pi_cache[_pi_next].gen = (pg)->prep_gen; \ + _pi_cache[_pi_next].serial = \ + (pg)->current_merchant_serial; \ + _pi_next = (_pi_next + 1) % PG_PREP_INSTANCE_CACHE_SLOTS; \ + } \ + } while (0) + + +/** * Check that the database connection is still up and automatically reconnects * unless we are already inside of a transaction. * diff --git a/src/backenddb/inactivate_account.c b/src/backenddb/inactivate_account.c @@ -31,7 +31,6 @@ TALER_MERCHANTDB_inactivate_account (struct TALER_MERCHANTDB_PostgresContext *pg const struct TALER_MerchantWireHashP *h_wire) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (merchant_id), GNUNET_PQ_query_param_auto_from_type (h_wire), GNUNET_PQ_query_param_end }; @@ -42,15 +41,20 @@ TALER_MERCHANTDB_inactivate_account (struct TALER_MERCHANTDB_PostgresContext *pg GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (merchant_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "inactivate_account", - "SELECT out_found AS found" - " FROM merchant_do_inactivate_account" - " ($1,$2);"); + PREPARE_INSTANCE (pg, + stmt, + "inactivate_account", + "SELECT out_found AS found" + " FROM merchant_do_inactivate_account" + " ($1);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "inactivate_account", + stmt, params, rs); if (qs < 0) diff --git a/src/backenddb/increase_refund.c b/src/backenddb/increase_refund.c @@ -66,6 +66,11 @@ struct FindRefundContext struct TALER_MERCHANTDB_PostgresContext *pg; /** + * Resolved per-instance prepared-statement name for "find_refunds_by_coin". + */ + const char *stmt_find_refunds; + + /** * Updated to reflect total amount refunded so far. */ struct TALER_Amount refunded_amount; @@ -133,6 +138,16 @@ struct InsertRefundContext * due to legal limits? */ bool legal_capped; + + /** + * Resolved per-instance prepared-statement name for "insert_refund". + */ + const char *stmt_insert_refund; + + /** + * Resolved per-instance prepared-statement name for "find_refunds_by_coin". + */ + const char *stmt_find_refunds; }; @@ -358,7 +373,8 @@ process_deposits_for_refund_cb (void *cls, GNUNET_PQ_result_spec_end }; struct FindRefundContext ictx = { - .pg = pg + .pg = pg, + .stmt_find_refunds = ctx->stmt_find_refunds }; struct ExchangeLimit *el; @@ -395,7 +411,7 @@ process_deposits_for_refund_cb (void *cls, &ictx.refunded_amount)); ires = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "find_refunds_by_coin", + ictx.stmt_find_refunds, params, &process_refund_cb, &ictx); @@ -560,7 +576,7 @@ process_deposits_for_refund_cb (void *cls, check_connection (pg); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_refund", + ctx->stmt_insert_refund, params); switch (qs) { @@ -619,63 +635,69 @@ TALER_MERCHANTDB_increase_refund ( { enum GNUNET_DB_QueryStatus qs; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_end }; + char stmt_insert_refund[PG_PREP_INSTANCE_NAME_MAX]; + char stmt_find_refunds[PG_PREP_INSTANCE_NAME_MAX]; + char stmt_find_deposits[PG_PREP_INSTANCE_NAME_MAX]; struct InsertRefundContext ctx = { .pg = pg, .refund = refund, .olc = olc, .olc_cls = olc_cls, - .reason = reason + .reason = reason, + .stmt_insert_refund = stmt_insert_refund, + .stmt_find_refunds = stmt_find_refunds }; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); // FIXME: return 'refund_serial' from this INSERT statement for #10577 - PREPARE (pg, - "insert_refund", - "INSERT INTO merchant_refunds" - "(order_serial" - ",rtransaction_id" - ",refund_timestamp" - ",coin_pub" - ",reason" - ",refund_amount" - ") VALUES" - "($1, $2, $3, $4, $5, $6)"); - PREPARE (pg, - "find_refunds_by_coin", - "SELECT" - " refund_amount" - ",rtransaction_id" - " FROM merchant_refunds" - " WHERE coin_pub=$1" - " AND order_serial=$2"); - PREPARE (pg, - "find_deposits_for_refund", - "SELECT" - " dep.coin_pub" - ",dco.order_serial" - ",dep.amount_with_fee" - ",dco.exchange_url" - " FROM merchant_deposits dep" - " JOIN merchant_deposit_confirmations dco" - " USING (deposit_confirmation_serial)" - " WHERE order_serial=" - " (SELECT order_serial" - " FROM merchant_contract_terms" - " WHERE order_id=$2" - " AND paid" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1))"); + PREPARE_INSTANCE (pg, + stmt_insert_refund, + "insert_refund", + "INSERT INTO merchant_refunds" + "(order_serial" + ",rtransaction_id" + ",refund_timestamp" + ",coin_pub" + ",reason" + ",refund_amount" + ") VALUES" + "($1, $2, $3, $4, $5, $6)"); + PREPARE_INSTANCE (pg, + stmt_find_refunds, + "find_refunds_by_coin", + "SELECT" + " refund_amount" + ",rtransaction_id" + " FROM merchant_refunds" + " WHERE coin_pub=$1" + " AND order_serial=$2"); + PREPARE_INSTANCE (pg, + stmt_find_deposits, + "find_deposits_for_refund", + "SELECT" + " dep.coin_pub" + ",dco.order_serial" + ",dep.amount_with_fee" + ",dco.exchange_url" + " FROM merchant_deposits dep" + " JOIN merchant_deposit_confirmations dco" + " USING (deposit_confirmation_serial)" + " WHERE order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " WHERE order_id=$1" + " AND paid)"); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Asked to refund %s on order %s\n", TALER_amount2s (refund), order_id); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "find_deposits_for_refund", + stmt_find_deposits, params, &process_deposits_for_refund_cb, &ctx); diff --git a/src/backenddb/increment_money_pots.c b/src/backenddb/increment_money_pots.c @@ -34,7 +34,6 @@ TALER_MERCHANTDB_increment_money_pots (struct TALER_MERCHANTDB_PostgresContext * const struct TALER_Amount *pot_increments) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_array_uint64 (money_pots_len, money_pot_ids, pg->conn), @@ -50,16 +49,21 @@ TALER_MERCHANTDB_increment_money_pots (struct TALER_MERCHANTDB_PostgresContext * GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "increment_money_pots", - "SELECT" - " out_not_found AS not_found" - " FROM merchant_do_increment_money_pots" - "($1,$2,$3);"); + PREPARE_INSTANCE (pg, + stmt, + "increment_money_pots", + "SELECT" + " out_not_found AS not_found" + " FROM merchant_do_increment_money_pots" + "($1,$2);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "increment_money_pots", + stmt, params, rs); GNUNET_PQ_cleanup_query_params_closures (params); diff --git a/src/backenddb/insert_account.c b/src/backenddb/insert_account.c @@ -31,7 +31,6 @@ TALER_MERCHANTDB_insert_account (struct TALER_MERCHANTDB_PostgresContext *pg, const struct TALER_MERCHANTDB_AccountDetails *account_details) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (account_details->instance_id), GNUNET_PQ_query_param_auto_from_type (&account_details->h_wire), GNUNET_PQ_query_param_auto_from_type (&account_details->salt), GNUNET_PQ_query_param_string (account_details->payto_uri.full_payto), @@ -48,30 +47,32 @@ TALER_MERCHANTDB_insert_account (struct TALER_MERCHANTDB_PostgresContext *pg, account_details->extra_wire_subject_metadata), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (account_details->instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_account", - "INSERT INTO merchant_accounts AS ma" - "(merchant_serial" - ",h_wire" - ",salt" - ",payto_uri" - ",credit_facade_url" - ",credit_facade_credentials" - ",active" - ",extra_wire_subject_metadata)" - " SELECT merchant_serial, $2, $3, $4, $5, $6::TEXT::JSONB, $7, $8" - " FROM merchant_instances" - " WHERE merchant_id=$1" - " ON CONFLICT(merchant_serial,payto_uri)" - " DO UPDATE SET" - " active = true" - ",credit_facade_url = EXCLUDED.credit_facade_url" - ",credit_facade_credentials = EXCLUDED.credit_facade_credentials" - ",extra_wire_subject_metadata = EXCLUDED.extra_wire_subject_metadata" - " WHERE NOT ma.active"); + PREPARE_INSTANCE (pg, + stmt, + "insert_account", + "INSERT INTO merchant_accounts AS ma" + "(h_wire" + ",salt" + ",payto_uri" + ",credit_facade_url" + ",credit_facade_credentials" + ",active" + ",extra_wire_subject_metadata)" + " VALUES ($1, $2, $3, $4, $5::TEXT::JSONB, $6, $7)" + " ON CONFLICT(payto_uri)" + " DO UPDATE SET" + " active = true" + ",credit_facade_url = EXCLUDED.credit_facade_url" + ",credit_facade_credentials = EXCLUDED.credit_facade_credentials" + ",extra_wire_subject_metadata = EXCLUDED.extra_wire_subject_metadata" + " WHERE NOT ma.active"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_account", + stmt, params); } diff --git a/src/backenddb/insert_category.c b/src/backenddb/insert_category.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2024 Taler Systems SA + Copyright (C) 2024, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -34,7 +34,6 @@ TALER_MERCHANTDB_insert_category (struct TALER_MERCHANTDB_PostgresContext *pg, uint64_t *category_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (category_name), TALER_PQ_query_param_json (category_name_i18n), GNUNET_PQ_query_param_end @@ -44,22 +43,23 @@ TALER_MERCHANTDB_insert_category (struct TALER_MERCHANTDB_PostgresContext *pg, category_id), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_category", - "INSERT INTO merchant_categories" - "(merchant_serial" - ",category_name" - ",category_name_i18n" - ")" - " SELECT merchant_serial," - " $2, $3::TEXT::JSONB" - " FROM merchant_instances" - " WHERE merchant_id=$1" - " RETURNING category_serial"); + PREPARE_INSTANCE (pg, + stmt, + "insert_category", + "INSERT INTO merchant_categories" + "(category_name" + ",category_name_i18n" + ")" + " VALUES ($1, $2::TEXT::JSONB)" + " RETURNING category_serial"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_category", + stmt, params, rs); } diff --git a/src/backenddb/insert_contract_terms.c b/src/backenddb/insert_contract_terms.c @@ -71,10 +71,12 @@ TALER_MERCHANTDB_insert_contract_terms (struct TALER_MERCHANTDB_PostgresContext fulfillment_url = json_string_value (json_object_get (contract_terms, "fulfillment_url")); + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), TALER_PQ_query_param_json (contract_terms), GNUNET_PQ_query_param_auto_from_type (&h_contract_terms), @@ -90,43 +92,40 @@ TALER_MERCHANTDB_insert_contract_terms (struct TALER_MERCHANTDB_PostgresContext order_serial), GNUNET_PQ_result_spec_end }; - PREPARE (pg, - "insert_contract_terms", - "INSERT INTO merchant_contract_terms" - "(order_serial" - ",merchant_serial" - ",order_id" - ",contract_terms" - ",h_contract_terms" - ",creation_time" - ",pay_deadline" - ",refund_deadline" - ",fulfillment_url" - ",claim_token" - ",pos_key" - ",pos_algorithm)" - "SELECT" - " mo.order_serial" - ",mo.merchant_serial" - ",mo.order_id" - ",$3::TEXT::JSONB" /* contract_terms */ - ",$4" /* h_contract_terms */ - ",mo.creation_time" - ",$5" /* pay_deadline */ - ",$6" /* refund_deadline */ - ",$7" /* fulfillment_url */ - ",mo.claim_token" - ",mo.pos_key" - ",mo.pos_algorithm" - " FROM merchant_orders mo" - " WHERE order_id=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " RETURNING order_serial"); + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + PREPARE_INSTANCE (pg, + stmt, + "insert_contract_terms", + "INSERT INTO merchant_contract_terms" + "(order_serial" + ",order_id" + ",contract_terms" + ",h_contract_terms" + ",creation_time" + ",pay_deadline" + ",refund_deadline" + ",fulfillment_url" + ",claim_token" + ",pos_key" + ",pos_algorithm)" + "SELECT" + " mo.order_serial" + ",mo.order_id" + ",$2::TEXT::JSONB" /* contract_terms */ + ",$3" /* h_contract_terms */ + ",mo.creation_time" + ",$4" /* pay_deadline */ + ",$5" /* refund_deadline */ + ",$6" /* fulfillment_url */ + ",mo.claim_token" + ",mo.pos_key" + ",mo.pos_algorithm" + " FROM merchant_orders mo" + " WHERE order_id=$1" + " RETURNING order_serial"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_contract_terms", + stmt, params, rs); } diff --git a/src/backenddb/insert_deposit.c b/src/backenddb/insert_deposit.c @@ -53,26 +53,29 @@ TALER_MERCHANTDB_insert_deposit (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + /* no preflight check here, run in transaction by caller! */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Storing deposit for coin_pub: `%s', amount_with_fee: %s\n", TALER_B2S (coin_pub), TALER_amount2s (amount_with_fee)); - check_connection (pg); - PREPARE (pg, - "insert_deposit", - "INSERT INTO merchant_deposits" - "(deposit_confirmation_serial" - ",coin_offset" - ",coin_pub" - ",coin_sig" - ",amount_with_fee" - ",deposit_fee" - ",refund_fee" - ",settlement_retry_time" - ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8)" - " ON CONFLICT DO NOTHING;"); + GNUNET_assert (NULL != pg->current_merchant_id); + PREPARE_INSTANCE (pg, + stmt, + "insert_deposit", + "INSERT INTO merchant_deposits" + "(deposit_confirmation_serial" + ",coin_offset" + ",coin_pub" + ",coin_sig" + ",amount_with_fee" + ",deposit_fee" + ",refund_fee" + ",settlement_retry_time" + ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8)" + " ON CONFLICT DO NOTHING;"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_deposit", + stmt, params); } diff --git a/src/backenddb/insert_deposit_confirmation.c b/src/backenddb/insert_deposit_confirmation.c @@ -47,7 +47,6 @@ TALER_MERCHANTDB_insert_deposit_confirmation (struct TALER_MERCHANTDB_PostgresCo = GNUNET_STRINGS_data_to_string_alloc (&nbo, sizeof (nbo)); 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_timestamp (&deposit_timestamp), GNUNET_PQ_query_param_string (exchange_url), @@ -55,21 +54,18 @@ TALER_MERCHANTDB_insert_deposit_confirmation (struct TALER_MERCHANTDB_PostgresCo total_without_fees), TALER_PQ_query_param_amount_with_currency (pg->conn, wire_fee), - GNUNET_PQ_query_param_auto_from_type (h_wire), /* 7 */ + GNUNET_PQ_query_param_auto_from_type (h_wire), /* 6 */ GNUNET_PQ_query_param_auto_from_type (exchange_sig), GNUNET_PQ_query_param_auto_from_type (exchange_pub), GNUNET_PQ_query_param_timestamp (&wire_transfer_deadline), GNUNET_PQ_query_param_string (nbo_str), GNUNET_PQ_query_param_end }; - bool no_instance; bool no_order; bool no_account; bool no_signkey; bool conflict; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_bool ("no_instance", - &no_instance), GNUNET_PQ_result_spec_bool ("no_order", &no_order), GNUNET_PQ_result_spec_bool ("no_account", @@ -83,7 +79,11 @@ TALER_MERCHANTDB_insert_deposit_confirmation (struct TALER_MERCHANTDB_PostgresCo GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); /* no preflight check here, run in transaction by caller! */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Storing deposit confirmation for instance `%s' h_contract_terms `%s', total_without_fees: %s and wire transfer deadline in %s\n", @@ -95,19 +95,19 @@ TALER_MERCHANTDB_insert_deposit_confirmation (struct TALER_MERCHANTDB_PostgresCo wire_transfer_deadline.abs_time), true)); check_connection (pg); - PREPARE (pg, - "insert_deposit_confirmation", - "SELECT " - " out_no_instance AS no_instance" - " ,out_no_account AS no_account" - " ,out_no_order AS no_order" - " ,out_no_signkey AS no_signkey" - " ,out_conflict AS conflict" - " ,out_deposit_confirmation_serial AS deposit_confirmation_serial" - " FROM merchant_do_insert_deposit_confirmation" - " ($1, $2 ,$3, $4, $5, $6, $7, $8, $9, $10, $11);"); + PREPARE_INSTANCE (pg, + stmt, + "insert_deposit_confirmation", + "SELECT " + " out_no_account AS no_account" + " ,out_no_order AS no_order" + " ,out_no_signkey AS no_signkey" + " ,out_conflict AS conflict" + " ,out_deposit_confirmation_serial AS deposit_confirmation_serial" + " FROM merchant_do_insert_deposit_confirmation" + " ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_deposit_confirmation", + stmt, params, rs); GNUNET_free (nbo_str); @@ -116,11 +116,6 @@ TALER_MERCHANTDB_insert_deposit_confirmation (struct TALER_MERCHANTDB_PostgresCo return qs; // FIXME: in the future, return these codes to the client and // return more specific error codes to the client from the API! - if (no_instance) - { - GNUNET_break (0); - return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; - } if (no_order) { GNUNET_break (0); diff --git a/src/backenddb/insert_deposit_to_transfer.c b/src/backenddb/insert_deposit_to_transfer.c @@ -51,16 +51,19 @@ TALER_MERCHANTDB_insert_deposit_to_transfer (struct TALER_MERCHANTDB_PostgresCon &dummy), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; - PREPARE (pg, - "insert_deposit_to_transfer", - "SELECT" - " out_dummy" - " FROM merchant_insert_deposit_to_transfer" - " ($1,$2,$3,$4,$5,$6,$7,$8);"); + GNUNET_assert (NULL != pg->current_merchant_id); + PREPARE_INSTANCE (pg, + stmt, + "insert_deposit_to_transfer", + "SELECT" + " out_dummy" + " FROM merchant_insert_deposit_to_transfer" + " ($1,$2,$3,$4,$5,$6,$7,$8);"); return GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, - "insert_deposit_to_transfer", + stmt, params, rs); } diff --git a/src/backenddb/insert_donau_instance.c b/src/backenddb/insert_donau_instance.c @@ -37,7 +37,6 @@ TALER_MERCHANTDB_insert_donau_instance (struct TALER_MERCHANTDB_PostgresContext struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (donau_url), GNUNET_PQ_query_param_string (charity->name), - GNUNET_PQ_query_param_auto_from_type (&charity->charity_pub), GNUNET_PQ_query_param_uint64 (&charity_id), TALER_PQ_query_param_amount_with_currency (pg->conn, &charity->max_per_year), @@ -46,26 +45,24 @@ TALER_MERCHANTDB_insert_donau_instance (struct TALER_MERCHANTDB_PostgresContext GNUNET_PQ_query_param_uint64 (&charity->current_year), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "insert_donau_instance", - "INSERT INTO merchant_donau_instances" - " (donau_url" - " ,charity_name" - " ,merchant_instance_serial" - " ,charity_id" - " ,charity_max_per_year" - " ,charity_receipts_to_date" - " ,current_year)" - "VALUES" - " ($1, $2," - " (SELECT merchant_serial" - " FROM merchant_instances mi" - " WHERE mi.merchant_pub = $3)," - " $4, $5, $6, $7)" - " ON CONFLICT DO NOTHING;"); + PREPARE_INSTANCE (pg, + stmt, + "insert_donau_instance", + "INSERT INTO merchant_donau_instances" + " (donau_url" + " ,charity_name" + " ,charity_id" + " ,charity_max_per_year" + " ,charity_receipts_to_date" + " ,current_year)" + "VALUES" + " ($1, $2, $3, $4, $5, $6)" + " ON CONFLICT DO NOTHING;"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_donau_instance", + stmt, params); } diff --git a/src/backenddb/insert_instance.c b/src/backenddb/insert_instance.c @@ -24,6 +24,7 @@ #include <taler/taler_dbevents.h> #include <taler/taler_pq_lib.h> #include "merchant-database/insert_instance.h" +#include "merchant-database/set_instance.h" #include "helper.h" enum GNUNET_DB_QueryStatus @@ -70,9 +71,9 @@ TALER_MERCHANTDB_insert_instance ( }; struct GNUNET_PQ_QueryParam params_priv[] = { GNUNET_PQ_query_param_auto_from_type (merchant_priv), - GNUNET_PQ_query_param_string (is->id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; enum GNUNET_DB_QueryStatus qs; check_connection (pg); @@ -101,20 +102,27 @@ TALER_MERCHANTDB_insert_instance ( "VALUES" "($1,$2,$3,LOWER($4),$5,$6::TEXT::JSONB,$7::TEXT::JSONB,$8,$9,$10,$11," "$12,$13,$14,$15,$16,$17,$18,$19::time_rounder_interval)"); - PREPARE (pg, - "insert_keys", - "INSERT INTO merchant_keys" - "(merchant_priv" - ",merchant_serial)" - " SELECT $1, merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$2"); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_instance", params); if (qs <= 0) return qs; + /* AFTER INSERT trigger has now created the merchant_instance_<N> schema. + Route to it so the merchant_keys INSERT lands in the per-instance table. */ + qs = TALER_MERCHANTDB_set_instance (pg, + is->id); + if (qs <= 0) + { + GNUNET_break (0); + return qs; + } + PREPARE_INSTANCE (pg, + stmt, + "insert_keys", + "INSERT INTO merchant_keys" + "(merchant_priv)" + " VALUES ($1)"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_keys", + stmt, params_priv); } diff --git a/src/backenddb/insert_issued_token.c b/src/backenddb/insert_issued_token.c @@ -50,18 +50,21 @@ TALER_MERCHANTDB_insert_issued_token (struct TALER_MERCHANTDB_PostgresContext *p GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "issued_token_insert", - "SELECT" - " out_no_family" - " ,out_existed" - " FROM merchant_do_insert_issued_token" - " ($1, $2, $3);"); + PREPARE_INSTANCE (pg, + stmt, + "issued_token_insert", + "SELECT" + " out_no_family" + " ,out_existed" + " FROM merchant_do_insert_issued_token" + " ($1, $2, $3);"); qs = GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, - "issued_token_insert", + stmt, params, rs); if (qs < 0) diff --git a/src/backenddb/insert_login_token.c b/src/backenddb/insert_login_token.c @@ -36,7 +36,6 @@ TALER_MERCHANTDB_insert_login_token (struct TALER_MERCHANTDB_PostgresContext *pg const char *description) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (id), GNUNET_PQ_query_param_auto_from_type (token), GNUNET_PQ_query_param_timestamp (&creation_time), GNUNET_PQ_query_param_timestamp (&expiration_time), @@ -44,22 +43,24 @@ TALER_MERCHANTDB_insert_login_token (struct TALER_MERCHANTDB_PostgresContext *pg GNUNET_PQ_query_param_string (description), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_login_token", - "INSERT INTO merchant_login_tokens" - "(token" - ",creation_time" - ",expiration_time" - ",validity_scope" - ",description" - ",merchant_serial" - ")" - "SELECT $2, $3, $4, $5, $6, merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1"); + PREPARE_INSTANCE (pg, + stmt, + "insert_login_token", + "INSERT INTO merchant_login_tokens" + "(token" + ",creation_time" + ",expiration_time" + ",validity_scope" + ",description" + ")" + " VALUES ($1, $2, $3, $4, $5)"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_login_token", + stmt, params); } diff --git a/src/backenddb/insert_money_pot.c b/src/backenddb/insert_money_pot.c @@ -34,7 +34,6 @@ TALER_MERCHANTDB_insert_money_pot (struct TALER_MERCHANTDB_PostgresContext *pg, uint64_t *money_pot_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (name), GNUNET_PQ_query_param_string (description), GNUNET_PQ_query_param_end @@ -44,21 +43,23 @@ TALER_MERCHANTDB_insert_money_pot (struct TALER_MERCHANTDB_PostgresContext *pg, money_pot_id), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_money_pot", - "INSERT INTO merchant_money_pots" - "(merchant_serial" - ",money_pot_name" - ",money_pot_description)" - " SELECT merchant_serial, $2, $3" - " FROM merchant_instances" - " WHERE merchant_id=$1" - " ON CONFLICT DO NOTHING" - " RETURNING money_pot_serial;"); + PREPARE_INSTANCE (pg, + stmt, + "insert_money_pot", + "INSERT INTO merchant_money_pots" + "(money_pot_name" + ",money_pot_description)" + " VALUES ($1, $2)" + " ON CONFLICT DO NOTHING" + " RETURNING money_pot_serial;"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_money_pot", + stmt, params, rs); } diff --git a/src/backenddb/insert_order.c b/src/backenddb/insert_order.c @@ -44,7 +44,6 @@ TALER_MERCHANTDB_insert_order (struct TALER_MERCHANTDB_PostgresContext *pg, = json_string_value (json_object_get (contract_terms, "fulfillment_url")); struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_timestamp (&pay_deadline), GNUNET_PQ_query_param_auto_from_type (claim_token), @@ -63,32 +62,34 @@ TALER_MERCHANTDB_insert_order (struct TALER_MERCHANTDB_PostgresContext *pg, : GNUNET_PQ_query_param_string (fulfillment_url), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; now = GNUNET_TIME_timestamp_get (); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "inserting order: order_id: %s, instance_id: %s.\n", order_id, instance_id); + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_order", - "INSERT INTO merchant_orders" - "(merchant_serial" - ",order_id" - ",pay_deadline" - ",claim_token" - ",h_post_data" - ",creation_time" - ",contract_terms" - ",pos_key" - ",pos_algorithm" - ",session_id" - ",fulfillment_url)" - " SELECT merchant_serial," - " $2, $3, $4, $5, $6, $7::TEXT::JSONB, $8, $9, $10, $11" - " FROM merchant_instances" - " WHERE merchant_id=$1"); + PREPARE_INSTANCE (pg, + stmt, + "insert_order", + "INSERT INTO merchant_orders" + "(order_id" + ",pay_deadline" + ",claim_token" + ",h_post_data" + ",creation_time" + ",contract_terms" + ",pos_key" + ",pos_algorithm" + ",session_id" + ",fulfillment_url)" + " VALUES" + " ($1, $2, $3, $4, $5, $6::TEXT::JSONB, $7, $8, $9, $10)"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_order", + stmt, params); } diff --git a/src/backenddb/insert_order_blinded_sigs.c b/src/backenddb/insert_order_blinded_sigs.c @@ -40,20 +40,24 @@ TALER_MERCHANTDB_insert_order_blinded_sigs (struct TALER_MERCHANTDB_PostgresCont GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "insert_blinded_sigs", - "INSERT INTO merchant_order_token_blinded_sigs" - " (order_serial" - " ,token_index" - " ,token_hash" - " ,token_blinded_signature" - ")" - " SELECT order_serial, $2, $3, $4" - " FROM merchant_contract_terms" - " WHERE order_id = $1"); + PREPARE_INSTANCE (pg, + stmt, + "insert_blinded_sigs", + "INSERT INTO merchant_order_token_blinded_sigs" + " (order_serial" + " ,token_index" + " ,token_hash" + " ,token_blinded_signature" + ")" + " SELECT order_serial, $2, $3, $4" + " FROM merchant_contract_terms" + " WHERE order_id = $1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_blinded_sigs", + stmt, params); } diff --git a/src/backenddb/insert_order_lock.c b/src/backenddb/insert_order_lock.c @@ -34,64 +34,63 @@ TALER_MERCHANTDB_insert_order_lock (struct TALER_MERCHANTDB_PostgresContext *pg, uint32_t quantity_frac) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_string (product_id), GNUNET_PQ_query_param_uint64 (&quantity), GNUNET_PQ_query_param_uint32 (&quantity_frac), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_order_lock", - "WITH tmp AS" - " (SELECT " - " product_serial" - " ,merchant_serial" - " ,total_stock" - " ,total_stock_frac" - " ,total_sold" - " ,total_sold_frac" - " ,total_lost" - " ,total_lost_frac" - " ,allow_fractional_quantity" - " FROM merchant_inventory" - " WHERE product_id=$3" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1))" - " INSERT INTO merchant_order_locks" - " (product_serial" - " ,total_locked" - " ,total_locked_frac" - " ,order_serial)" - " SELECT tmp.product_serial, $4::INT8, $5::INT4, order_serial" - " FROM merchant_orders" - " JOIN tmp USING(merchant_serial)" - " WHERE order_id=$2" - " AND (tmp.allow_fractional_quantity OR $5 = 0)" - " AND (tmp.total_stock = 9223372036854775807" - " OR (" - " (tmp.total_stock::NUMERIC * 1000000" - " + tmp.total_stock_frac::NUMERIC)" - " - (tmp.total_sold::NUMERIC * 1000000" - " + tmp.total_sold_frac::NUMERIC)" - " - (tmp.total_lost::NUMERIC * 1000000" - " + tmp.total_lost_frac::NUMERIC)" - " >= " - " (($4::NUMERIC * 1000000) + $5::NUMERIC)" - " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" - " + total_locked_frac::NUMERIC), 0)" - " FROM merchant_inventory_locks mil" - " WHERE mil.product_serial = tmp.product_serial)" - " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" - " + total_locked_frac::NUMERIC), 0)" - " FROM merchant_order_locks mol" - " WHERE mol.product_serial = tmp.product_serial)" - " ))"); + PREPARE_INSTANCE (pg, + stmt, + "insert_order_lock", + "WITH tmp AS" + " (SELECT " + " product_serial" + " ,total_stock" + " ,total_stock_frac" + " ,total_sold" + " ,total_sold_frac" + " ,total_lost" + " ,total_lost_frac" + " ,allow_fractional_quantity" + " FROM merchant_inventory" + " WHERE product_id=$2)" + " INSERT INTO merchant_order_locks" + " (product_serial" + " ,total_locked" + " ,total_locked_frac" + " ,order_serial)" + " SELECT tmp.product_serial, $3::INT8, $4::INT4, order_serial" + " FROM merchant_orders" + " CROSS JOIN tmp" + " WHERE order_id=$1" + " AND (tmp.allow_fractional_quantity OR $4 = 0)" + " AND (tmp.total_stock = 9223372036854775807" + " OR (" + " (tmp.total_stock::NUMERIC * 1000000" + " + tmp.total_stock_frac::NUMERIC)" + " - (tmp.total_sold::NUMERIC * 1000000" + " + tmp.total_sold_frac::NUMERIC)" + " - (tmp.total_lost::NUMERIC * 1000000" + " + tmp.total_lost_frac::NUMERIC)" + " >= " + " (($3::NUMERIC * 1000000) + $4::NUMERIC)" + " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" + " + total_locked_frac::NUMERIC), 0)" + " FROM merchant_inventory_locks mil" + " WHERE mil.product_serial = tmp.product_serial)" + " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" + " + total_locked_frac::NUMERIC), 0)" + " FROM merchant_order_locks mol" + " WHERE mol.product_serial = tmp.product_serial)" + " ))"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_order_lock", + stmt, params); } diff --git a/src/backenddb/insert_otp.c b/src/backenddb/insert_otp.c @@ -43,7 +43,6 @@ TALER_MERCHANTDB_insert_otp (struct TALER_MERCHANTDB_PostgresContext *pg, { uint32_t pos32 = (uint32_t) td->otp_algorithm; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (otp_id), GNUNET_PQ_query_param_string (td->otp_description), GNUNET_PQ_query_param_string (td->otp_key), @@ -51,23 +50,24 @@ TALER_MERCHANTDB_insert_otp (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_query_param_uint64 (&td->otp_ctr), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_otp", - "INSERT INTO merchant_otp_devices" - "(merchant_serial" - ",otp_id" - ",otp_description" - ",otp_key" - ",otp_algorithm" - ",otp_ctr" - ")" - " SELECT merchant_serial," - " $2, $3, $4, $5, $6" - " FROM merchant_instances" - " WHERE merchant_id=$1"); + PREPARE_INSTANCE (pg, + stmt, + "insert_otp", + "INSERT INTO merchant_otp_devices" + "(otp_id" + ",otp_description" + ",otp_key" + ",otp_algorithm" + ",otp_ctr" + ")" + " VALUES ($1, $2, $3, $4, $5)"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_otp", + stmt, params); } diff --git a/src/backenddb/insert_pending_webhook.c b/src/backenddb/insert_pending_webhook.c @@ -35,7 +35,6 @@ TALER_MERCHANTDB_insert_pending_webhook (struct TALER_MERCHANTDB_PostgresContext const char *body) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&webhook_serial), GNUNET_PQ_query_param_string (url), GNUNET_PQ_query_param_string (http_method), @@ -47,23 +46,25 @@ TALER_MERCHANTDB_insert_pending_webhook (struct TALER_MERCHANTDB_PostgresContext : GNUNET_PQ_query_param_string (body), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_pending_webhook", - "INSERT INTO merchant_pending_webhooks" - "(merchant_serial" - ",webhook_serial" - ",url" - ",http_method" - ",header" - ",body" - ")" - " SELECT mi.merchant_serial," - " $2, $3, $4, $5, $6" - " FROM merchant_instances mi" - " WHERE mi.merchant_id=$1"); + PREPARE_INSTANCE (pg, + stmt, + "insert_pending_webhook", + "INSERT INTO merchant_pending_webhooks" + "(webhook_serial" + ",url" + ",http_method" + ",header" + ",body" + ")" + " VALUES ($1, $2, $3, $4, $5)"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_pending_webhook", + stmt, params); } diff --git a/src/backenddb/insert_product.c b/src/backenddb/insert_product.c @@ -41,35 +41,34 @@ TALER_MERCHANTDB_insert_product (struct TALER_MERCHANTDB_PostgresContext *pg, bool *no_pot) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (product_id), GNUNET_PQ_query_param_string (pd->description), - TALER_PQ_query_param_json (pd->description_i18n), /* $4 */ + TALER_PQ_query_param_json (pd->description_i18n), /* $3 */ GNUNET_PQ_query_param_string (pd->unit), GNUNET_PQ_query_param_string (pd->image), - TALER_PQ_query_param_json (pd->taxes), /* $7 */ + TALER_PQ_query_param_json (pd->taxes), /* $6 */ TALER_PQ_query_param_array_amount_with_currency ( pd->price_array_length, pd->price_array, - pg->conn), /* $8 */ - GNUNET_PQ_query_param_uint64 (&pd->total_stock), /* $9 */ - GNUNET_PQ_query_param_uint32 (&pd->total_stock_frac), /* $10 */ + pg->conn), /* $7 */ + GNUNET_PQ_query_param_uint64 (&pd->total_stock), /* $8 */ + GNUNET_PQ_query_param_uint32 (&pd->total_stock_frac), /* $9 */ GNUNET_PQ_query_param_bool (pd->allow_fractional_quantity), GNUNET_PQ_query_param_uint32 (&pd->fractional_precision_level), - TALER_PQ_query_param_json (pd->address), /* $13 */ + TALER_PQ_query_param_json (pd->address), /* $12 */ GNUNET_PQ_query_param_timestamp (&pd->next_restock), GNUNET_PQ_query_param_uint32 (&pd->minimum_age), GNUNET_PQ_query_param_array_uint64 (num_cats, cats, - pg->conn), /* $16 */ + pg->conn), /* $15 */ GNUNET_PQ_query_param_string (pd->product_name), (0 == pd->product_group_id) ? GNUNET_PQ_query_param_null () - : GNUNET_PQ_query_param_uint64 (&pd->product_group_id), /* $18 */ + : GNUNET_PQ_query_param_uint64 (&pd->product_group_id), /* $17 */ (0 == pd->money_pot_id) ? GNUNET_PQ_query_param_null () - : GNUNET_PQ_query_param_uint64 (&pd->money_pot_id), /* $19 */ - GNUNET_PQ_query_param_bool (pd->price_is_net), /* $20 */ + : GNUNET_PQ_query_param_uint64 (&pd->money_pot_id), /* $18 */ + GNUNET_PQ_query_param_bool (pd->price_is_net), /* $19 */ GNUNET_PQ_query_param_end }; uint64_t ncat; @@ -77,8 +76,6 @@ TALER_MERCHANTDB_insert_product (struct TALER_MERCHANTDB_PostgresContext *pg, struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_bool ("conflict", conflict), - GNUNET_PQ_result_spec_bool ("no_instance", - no_instance), GNUNET_PQ_result_spec_allow_null ( GNUNET_PQ_result_spec_uint64 ("no_cat", &ncat), @@ -90,22 +87,27 @@ TALER_MERCHANTDB_insert_product (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + *no_instance = false; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_product", - "SELECT" - " out_conflict AS conflict" - ",out_no_instance AS no_instance" - ",out_no_cat AS no_cat" - ",out_no_group AS no_group" - ",out_no_pot AS no_pot" - " FROM merchant_do_insert_product" - "($1, $2, $3, $4::TEXT::JSONB, $5, $6, $7::TEXT::JSONB, $8" - ",$9, $10, $11, $12, $13::TEXT::JSONB, $14, $15, $16" - ",$17, $18, $19, $20);"); + PREPARE_INSTANCE (pg, + stmt, + "insert_product", + "SELECT" + " out_conflict AS conflict" + ",out_no_cat AS no_cat" + ",out_no_group AS no_group" + ",out_no_pot AS no_pot" + " FROM merchant_do_insert_product" + "($1, $2, $3::TEXT::JSONB, $4, $5, $6::TEXT::JSONB, $7" + ",$8, $9, $10, $11, $12::TEXT::JSONB, $13, $14, $15" + ",$16, $17, $18, $19);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_product", + stmt, params, rs); GNUNET_PQ_cleanup_query_params_closures (params); diff --git a/src/backenddb/insert_product_group.c b/src/backenddb/insert_product_group.c @@ -34,7 +34,6 @@ TALER_MERCHANTDB_insert_product_group (struct TALER_MERCHANTDB_PostgresContext * uint64_t *product_group_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (name), GNUNET_PQ_query_param_string (description), GNUNET_PQ_query_param_end @@ -44,22 +43,24 @@ TALER_MERCHANTDB_insert_product_group (struct TALER_MERCHANTDB_PostgresContext * product_group_id), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_product_group", - "INSERT INTO merchant_product_groups" - "(merchant_serial" - ",product_group_name" - ",product_group_description)" - " SELECT merchant_serial, $2, $3" - " FROM merchant_instances" - " WHERE merchant_id=$1" - " ON CONFLICT DO NOTHING" - " RETURNING product_group_serial"); + PREPARE_INSTANCE (pg, + stmt, + "insert_product_group", + "INSERT INTO merchant_product_groups" + "(product_group_name" + ",product_group_description)" + " VALUES ($1, $2)" + " ON CONFLICT DO NOTHING" + " RETURNING product_group_serial"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_product_group", + stmt, params, rs); } diff --git a/src/backenddb/insert_refund_proof.c b/src/backenddb/insert_refund_proof.c @@ -38,20 +38,24 @@ TALER_MERCHANTDB_insert_refund_proof (struct TALER_MERCHANTDB_PostgresContext *p GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "insert_refund_proof", - "INSERT INTO merchant_refund_proofs" - "(refund_serial" - ",exchange_sig" - ",signkey_serial)" - "SELECT $1, $2, signkey_serial" - " FROM merchant_exchange_signing_keys" - " WHERE exchange_pub=$3" - " ORDER BY start_date DESC" - " LIMIT 1"); + PREPARE_INSTANCE (pg, + stmt, + "insert_refund_proof", + "INSERT INTO merchant_refund_proofs" + "(refund_serial" + ",exchange_sig" + ",signkey_serial)" + "SELECT $1, $2, signkey_serial" + " FROM merchant.merchant_exchange_signing_keys" + " WHERE exchange_pub=$3" + " ORDER BY start_date DESC" + " LIMIT 1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_refund_proof", + stmt, params); } diff --git a/src/backenddb/insert_report.c b/src/backenddb/insert_report.c @@ -43,7 +43,6 @@ TALER_MERCHANTDB_insert_report ( struct TALER_MERCHANT_ReportToken report_token; struct GNUNET_TIME_Timestamp start; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (report_program_section), GNUNET_PQ_query_param_string (report_description), GNUNET_PQ_query_param_string (mime_type), @@ -60,7 +59,11 @@ TALER_MERCHANTDB_insert_report ( report_id), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, &report_token, sizeof (report_token)); @@ -73,27 +76,25 @@ TALER_MERCHANTDB_insert_report ( frequency_shift))); check_connection (pg); - PREPARE (pg, - "insert_report", - "INSERT INTO merchant_reports" - "(merchant_serial" - ",report_program_section" - ",report_description" - ",mime_type" - ",report_token" - ",data_source" - ",target_address" - ",frequency" - ",frequency_shift" - ",next_transmission)" - " SELECT merchant_serial, $2, $3, $4, $5," - " $6, $7, $8, $9, $10" - " FROM merchant_instances" - " WHERE merchant_id=$1" - " ON CONFLICT DO NOTHING;"); + PREPARE_INSTANCE (pg, + stmt, + "insert_report", + "INSERT INTO merchant_reports" + "(report_program_section" + ",report_description" + ",mime_type" + ",report_token" + ",data_source" + ",target_address" + ",frequency" + ",frequency_shift" + ",next_transmission)" + " VALUES ($1, $2, $3, $4," + " $5, $6, $7, $8, $9)" + " ON CONFLICT DO NOTHING;"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_report", + stmt, params, rs); } diff --git a/src/backenddb/insert_spent_token.c b/src/backenddb/insert_spent_token.c @@ -52,21 +52,24 @@ TALER_MERCHANTDB_insert_spent_token (struct TALER_MERCHANTDB_PostgresContext *pg GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Storing token spent with key %s\n", GNUNET_h2s (&h_issue_pub->hash)); - PREPARE (pg, - "spent_token_insert", - "SELECT" - " out_no_family" - " ,out_conflict" - " FROM merchant_do_insert_spent_token" - "($1, $2, $3, $4, $5);"); + PREPARE_INSTANCE (pg, + stmt, + "spent_token_insert", + "SELECT" + " out_no_family" + " ,out_conflict" + " FROM merchant_do_insert_spent_token" + "($1, $2, $3, $4, $5);"); qs = GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, - "spent_token_insert", + stmt, params, rs); if (qs < 0) diff --git a/src/backenddb/insert_template.c b/src/backenddb/insert_template.c @@ -34,7 +34,6 @@ TALER_MERCHANTDB_insert_template (struct TALER_MERCHANTDB_PostgresContext *pg, const struct TALER_MERCHANTDB_TemplateDetails *td) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (template_id), GNUNET_PQ_query_param_string (td->template_description), (0 == otp_serial_id) @@ -47,24 +46,26 @@ TALER_MERCHANTDB_insert_template (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_template", - "INSERT INTO merchant_template" - "(merchant_serial" - ",template_id" - ",template_description" - ",otp_device_id" - ",template_contract" - ",editable_defaults" - ")" - " SELECT merchant_serial," - " $2, $3, $4, $5::TEXT::JSONB, $6::TEXT::JSONB" - " FROM merchant_instances" - " WHERE merchant_id=$1"); + PREPARE_INSTANCE (pg, + stmt, + "insert_template", + "INSERT INTO merchant_template" + "(template_id" + ",template_description" + ",otp_device_id" + ",template_contract" + ",editable_defaults" + ")" + " VALUES" + " ($1, $2, $3, $4::TEXT::JSONB, $5::TEXT::JSONB)"); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_template", + stmt, params); GNUNET_PQ_cleanup_query_params_closures (params); return qs; diff --git a/src/backenddb/insert_token_family.c b/src/backenddb/insert_token_family.c @@ -46,48 +46,52 @@ TALER_MERCHANTDB_insert_token_family (struct TALER_MERCHANTDB_PostgresContext *p GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_token_family", - "INSERT INTO merchant_token_families" - "(merchant_serial" - ",slug" - ",name" - ",description" - ",description_i18n" - ",extra_data" - ",valid_after" - ",valid_before" - ",duration" - ",validity_granularity" - ",start_offset" - ",kind)" - " SELECT merchant_serial, $2, $3, $4, $5::TEXT::JSONB," - " $6::TEXT::JSONB, $7, $8, $9, $10, $11, $12" - " FROM merchant_instances" - " WHERE merchant_id=$1" - " ON CONFLICT DO NOTHING;"); { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (token_family_slug), - GNUNET_PQ_query_param_string (details->name), - GNUNET_PQ_query_param_string (details->description), - TALER_PQ_query_param_json (details->description_i18n), - NULL == details->extra_data - ? GNUNET_PQ_query_param_null () - : TALER_PQ_query_param_json (details->extra_data), - GNUNET_PQ_query_param_timestamp (&details->valid_after), - GNUNET_PQ_query_param_timestamp (&details->valid_before), - GNUNET_PQ_query_param_relative_time (&details->duration), - GNUNET_PQ_query_param_relative_time (&details->validity_granularity), - GNUNET_PQ_query_param_relative_time (&details->start_offset), - GNUNET_PQ_query_param_string (kind), - GNUNET_PQ_query_param_end - }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_token_family", - params); + PREPARE_INSTANCE (pg, + stmt, + "insert_token_family", + "INSERT INTO merchant_token_families" + "(slug" + ",name" + ",description" + ",description_i18n" + ",extra_data" + ",valid_after" + ",valid_before" + ",duration" + ",validity_granularity" + ",start_offset" + ",kind)" + " VALUES ($1, $2, $3, $4::TEXT::JSONB," + " $5::TEXT::JSONB, $6, $7, $8, $9, $10, $11)" + " ON CONFLICT DO NOTHING;"); + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (token_family_slug), + GNUNET_PQ_query_param_string (details->name), + GNUNET_PQ_query_param_string (details->description), + TALER_PQ_query_param_json (details->description_i18n), + NULL == details->extra_data + ? GNUNET_PQ_query_param_null () + : TALER_PQ_query_param_json (details->extra_data), + GNUNET_PQ_query_param_timestamp (&details->valid_after), + GNUNET_PQ_query_param_timestamp (&details->valid_before), + GNUNET_PQ_query_param_relative_time (&details->duration), + GNUNET_PQ_query_param_relative_time (&details->validity_granularity), + GNUNET_PQ_query_param_relative_time (&details->start_offset), + GNUNET_PQ_query_param_string (kind), + GNUNET_PQ_query_param_end + }; + + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + stmt, + params); + } } } diff --git a/src/backenddb/insert_token_family_key.c b/src/backenddb/insert_token_family_key.c @@ -89,50 +89,53 @@ TALER_MERCHANTDB_insert_token_family_key (struct TALER_MERCHANTDB_PostgresContex valid_after.abs_time)); GNUNET_assert (! GNUNET_TIME_absolute_is_zero ( valid_before.abs_time)); - PREPARE (pg, - "token_family_key_insert", - "INSERT INTO merchant_token_family_keys " - "(token_family_serial" - ",pub" - ",h_pub" - ",priv" - ",private_key_created_at" - ",private_key_deleted_at" - ",signature_validity_start" - ",signature_validity_end" - ",cipher)" - " SELECT token_family_serial, $2, $3, $4, $5, $6, $7, $8, $9" - " FROM merchant_token_families" - " WHERE (slug = $1)" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$10)"); + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (merchant_id, + pg->current_merchant_id)); { - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (token_family_slug), - GNUNET_PQ_query_param_blind_sign_pub (pub->public_key), - GNUNET_PQ_query_param_auto_from_type (&pub->public_key->pub_key_hash), - GNUNET_PQ_query_param_blind_sign_priv (priv->private_key), - GNUNET_PQ_query_param_timestamp (&now), - GNUNET_PQ_query_param_timestamp (&key_expires), - GNUNET_PQ_query_param_timestamp (&valid_after), - GNUNET_PQ_query_param_timestamp (&valid_before), - GNUNET_PQ_query_param_string (cipher), - GNUNET_PQ_query_param_string (merchant_id), - GNUNET_PQ_query_param_end - }; - enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "token_family_key_insert", - params); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Insert into MTFK %s with valid [%llu,%llu] got %d\n", - token_family_slug, - (unsigned long long) valid_after.abs_time.abs_value_us, - (unsigned long long) valid_before.abs_time.abs_value_us, - (int) qs); - return qs; + PREPARE_INSTANCE (pg, + stmt, + "token_family_key_insert", + "INSERT INTO merchant_token_family_keys " + "(token_family_serial" + ",pub" + ",h_pub" + ",priv" + ",private_key_created_at" + ",private_key_deleted_at" + ",signature_validity_start" + ",signature_validity_end" + ",cipher)" + " SELECT token_family_serial, $2, $3, $4, $5, $6, $7, $8, $9" + " FROM merchant_token_families" + " WHERE (slug = $1)"); + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (token_family_slug), + GNUNET_PQ_query_param_blind_sign_pub (pub->public_key), + GNUNET_PQ_query_param_auto_from_type (&pub->public_key->pub_key_hash), + GNUNET_PQ_query_param_blind_sign_priv (priv->private_key), + GNUNET_PQ_query_param_timestamp (&now), + GNUNET_PQ_query_param_timestamp (&key_expires), + GNUNET_PQ_query_param_timestamp (&valid_after), + GNUNET_PQ_query_param_timestamp (&valid_before), + GNUNET_PQ_query_param_string (cipher), + GNUNET_PQ_query_param_end + }; + enum GNUNET_DB_QueryStatus qs; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + stmt, + params); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Insert into MTFK %s with valid [%llu,%llu] got %d\n", + token_family_slug, + (unsigned long long) valid_after.abs_time.abs_value_us, + (unsigned long long) valid_before.abs_time.abs_value_us, + (int) qs); + return qs; + } } } diff --git a/src/backenddb/insert_transfer.c b/src/backenddb/insert_transfer.c @@ -40,7 +40,6 @@ TALER_MERCHANTDB_insert_transfer (struct TALER_MERCHANTDB_PostgresContext *pg, { struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (exchange_url), GNUNET_PQ_query_param_auto_from_type (wtid), TALER_PQ_query_param_amount_with_currency (pg->conn, @@ -53,26 +52,29 @@ TALER_MERCHANTDB_insert_transfer (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_bool ("no_instance", - no_instance), GNUNET_PQ_result_spec_bool ("no_account", no_account), GNUNET_PQ_result_spec_bool ("conflict", conflict), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + *no_instance = false; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_transfer", - "SELECT " - " out_no_instance AS no_instance" - " ,out_no_account AS no_account" - " ,out_conflict AS conflict" - " FROM merchant_do_insert_transfer" - " ($1, $2, $3, $4, $5, $6, $7);"); + PREPARE_INSTANCE (pg, + stmt, + "insert_transfer", + "SELECT " + " out_no_account AS no_account" + " ,out_conflict AS conflict" + " FROM merchant_do_insert_transfer" + " ($1, $2, $3, $4, $5, $6);"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_transfer", + stmt, params, rs); } diff --git a/src/backenddb/insert_transfer_details.c b/src/backenddb/insert_transfer_details.c @@ -48,6 +48,11 @@ TALER_MERCHANTDB_insert_transfer_details (struct TALER_MERCHANTDB_PostgresContex const struct TALER_PrivateContractHashP *contract_terms[GNUNET_NZL (len)]; enum GNUNET_DB_QueryStatus qs; bool duplicate; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); for (unsigned int i = 0; i<len; i++) { @@ -60,18 +65,18 @@ TALER_MERCHANTDB_insert_transfer_details (struct TALER_MERCHANTDB_PostgresContex } check_connection (pg); - PREPARE (pg, - "insert_transfer_details", - "SELECT" - " out_no_instance" - ",out_no_account" - ",out_no_exchange" - ",out_duplicate" - ",out_conflict" - ",out_order_id" - ",out_merchant_pub" - " FROM merchant_do_insert_transfer_details" - " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13);"); + PREPARE_INSTANCE (pg, + stmt, + "insert_transfer_details", + "SELECT" + " out_no_account" + ",out_no_exchange" + ",out_duplicate" + ",out_conflict" + ",out_order_id" + ",out_merchant_pub" + " FROM merchant_do_insert_transfer_details" + " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12);"); for (unsigned int retries = 0; retries < MAX_RETRIES; @@ -87,7 +92,6 @@ TALER_MERCHANTDB_insert_transfer_details (struct TALER_MERCHANTDB_PostgresContex { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (exchange_url), GNUNET_PQ_query_param_string (payto_uri.full_payto), GNUNET_PQ_query_param_auto_from_type (wtid), @@ -116,15 +120,12 @@ TALER_MERCHANTDB_insert_transfer_details (struct TALER_MERCHANTDB_PostgresContex pg->conn), GNUNET_PQ_query_param_end }; - bool no_instance; bool no_account; bool no_exchange; bool conflict; char *order_id = NULL; struct TALER_MerchantPublicKeyP merchant_pub; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_bool ("out_no_instance", - &no_instance), GNUNET_PQ_result_spec_bool ("out_no_account", &no_account), GNUNET_PQ_result_spec_bool ("out_no_exchange", @@ -145,7 +146,7 @@ TALER_MERCHANTDB_insert_transfer_details (struct TALER_MERCHANTDB_PostgresContex }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_transfer_details", + stmt, params, rs); GNUNET_PQ_cleanup_query_params_closures (params); @@ -181,8 +182,7 @@ TALER_MERCHANTDB_insert_transfer_details (struct TALER_MERCHANTDB_PostgresContex GNUNET_free (order_id); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Transfer details inserted: %s%s%s%s%s\n", - no_instance ? "no instance " : "", + "Transfer details inserted: %s%s%s%s\n", no_account ? "no account " : "", no_exchange ? "no exchange ": "", duplicate ? "duplicate ": "", diff --git a/src/backenddb/insert_unclaim_signature.c b/src/backenddb/insert_unclaim_signature.c @@ -61,7 +61,6 @@ TALER_MERCHANTDB_insert_unclaim_signature ( char *notify_str = get_notify_str (order_id, merchant_pub); struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_string (nonce_str), GNUNET_PQ_query_param_string (notify_str), @@ -76,17 +75,22 @@ TALER_MERCHANTDB_insert_unclaim_signature ( GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_unclaim_signature", - "SELECT" - " out_found" - " FROM merchant_do_insert_unclaim_signature" - "($1, $2, $3, $4, $5, $6);"); + PREPARE_INSTANCE (pg, + stmt, + "insert_unclaim_signature", + "SELECT" + " out_found" + " FROM merchant_do_insert_unclaim_signature" + "($1, $2, $3, $4, $5);"); qs = GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, - "insert_unclaim_signature", + stmt, params, rs); GNUNET_free (nonce_str); diff --git a/src/backenddb/insert_unit.c b/src/backenddb/insert_unit.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2025 Taler Systems SA + Copyright (C) 2025, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -35,7 +35,6 @@ TALER_MERCHANTDB_insert_unit (struct TALER_MERCHANTDB_PostgresContext *pg, uint64_t *unit_serial) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (ud->unit), GNUNET_PQ_query_param_string (ud->unit_name_long), GNUNET_PQ_query_param_string (ud->unit_name_short), @@ -48,8 +47,6 @@ TALER_MERCHANTDB_insert_unit (struct TALER_MERCHANTDB_PostgresContext *pg, }; bool unit_serial_present = true; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_bool ("no_instance", - no_instance), GNUNET_PQ_result_spec_bool ("conflict", conflict), GNUNET_PQ_result_spec_allow_null ( @@ -59,21 +56,25 @@ TALER_MERCHANTDB_insert_unit (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; *no_instance = false; *conflict = false; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_unit", - "SELECT" - " out_no_instance AS no_instance" - " ,out_conflict AS conflict" - " ,out_unit_serial AS unit_serial" - " FROM merchant_do_insert_unit" - " ($1,$2,$3,$4,$5,$6,$7,$8,$9);"); + PREPARE_INSTANCE (pg, + stmt, + "insert_unit", + "SELECT" + " out_conflict AS conflict" + " ,out_unit_serial AS unit_serial" + " FROM merchant_do_insert_unit" + " ($1,$2,$3,$4,$5,$6,$7,$8);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_unit", + stmt, params, rs); GNUNET_PQ_cleanup_query_params_closures (params); diff --git a/src/backenddb/insert_webhook.c b/src/backenddb/insert_webhook.c @@ -32,7 +32,6 @@ TALER_MERCHANTDB_insert_webhook (struct TALER_MERCHANTDB_PostgresContext *pg, const struct TALER_MERCHANTDB_WebhookDetails *wb) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (webhook_id), GNUNET_PQ_query_param_string (wb->event_type), GNUNET_PQ_query_param_string (wb->url), @@ -45,25 +44,26 @@ TALER_MERCHANTDB_insert_webhook (struct TALER_MERCHANTDB_PostgresContext *pg, : GNUNET_PQ_query_param_string (wb->body_template), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "insert_webhook", - "INSERT INTO merchant_webhook" - "(merchant_serial" - ",webhook_id" - ",event_type" - ",url" - ",http_method" - ",header_template" - ",body_template" - ")" - " SELECT merchant_serial," - " $2, $3, $4, $5, $6, $7" - " FROM merchant_instances" - " WHERE merchant_id=$1"); + PREPARE_INSTANCE (pg, + stmt, + "insert_webhook", + "INSERT INTO merchant_webhook" + "(webhook_id" + ",event_type" + ",url" + ",http_method" + ",header_template" + ",body_template" + ")" + " VALUES ($1, $2, $3, $4, $5, $6)"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_webhook", + stmt, params); } diff --git a/src/backenddb/lock_product.c b/src/backenddb/lock_product.c @@ -35,7 +35,6 @@ TALER_MERCHANTDB_lock_product (struct TALER_MERCHANTDB_PostgresContext *pg, struct GNUNET_TIME_Timestamp expiration_time) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (product_id), GNUNET_PQ_query_param_auto_from_type (uuid), GNUNET_PQ_query_param_uint64 (&quantity), @@ -43,59 +42,56 @@ TALER_MERCHANTDB_lock_product (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_query_param_timestamp (&expiration_time), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lock_product", - "WITH ps AS" - " (SELECT product_serial" - " FROM merchant_inventory" - " WHERE product_id=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1))" - ",tmp AS" - " (SELECT" - " mi.product_serial" - " ,mi.total_stock" - " ,mi.total_stock_frac" - " ,mi.total_sold" - " ,mi.total_sold_frac" - " ,mi.total_lost" - " ,mi.total_lost_frac" - " ,mi.allow_fractional_quantity" - " FROM merchant_inventory mi" - " JOIN ps USING (product_serial))" - "INSERT INTO merchant_inventory_locks" - "(product_serial" - ",lock_uuid" - ",total_locked" - ",total_locked_frac" - ",expiration)" - " SELECT tmp.product_serial, $3, $4::INT8, $5::INT4, $6" - " FROM tmp" - " WHERE (tmp.allow_fractional_quantity OR $5 = 0)" - " AND (tmp.total_stock = 9223372036854775807" - " OR (" - " (tmp.total_stock::NUMERIC * 1000000" - " + tmp.total_stock_frac::NUMERIC)" - " - (tmp.total_sold::NUMERIC * 1000000" - " + tmp.total_sold_frac::NUMERIC)" - " - (tmp.total_lost::NUMERIC * 1000000" - " + tmp.total_lost_frac::NUMERIC)" - " >= " - " (($4::NUMERIC * 1000000) + $5::NUMERIC)" - " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" - " + total_locked_frac::NUMERIC), 0)" - " FROM merchant_inventory_locks mil" - " WHERE mil.product_serial = tmp.product_serial)" - " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" - " + total_locked_frac::NUMERIC), 0)" - " FROM merchant_order_locks mol" - " WHERE mol.product_serial = tmp.product_serial)" - " ))"); + PREPARE_INSTANCE (pg, + stmt, + "lock_product", + "WITH tmp AS" + " (SELECT" + " mi.product_serial" + " ,mi.total_stock" + " ,mi.total_stock_frac" + " ,mi.total_sold" + " ,mi.total_sold_frac" + " ,mi.total_lost" + " ,mi.total_lost_frac" + " ,mi.allow_fractional_quantity" + " FROM merchant_inventory mi" + " WHERE mi.product_id=$1)" + "INSERT INTO merchant_inventory_locks" + "(product_serial" + ",lock_uuid" + ",total_locked" + ",total_locked_frac" + ",expiration)" + " SELECT tmp.product_serial, $2, $3::INT8, $4::INT4, $5" + " FROM tmp" + " WHERE (tmp.allow_fractional_quantity OR $4 = 0)" + " AND (tmp.total_stock = 9223372036854775807" + " OR (" + " (tmp.total_stock::NUMERIC * 1000000" + " + tmp.total_stock_frac::NUMERIC)" + " - (tmp.total_sold::NUMERIC * 1000000" + " + tmp.total_sold_frac::NUMERIC)" + " - (tmp.total_lost::NUMERIC * 1000000" + " + tmp.total_lost_frac::NUMERIC)" + " >= " + " (($3::NUMERIC * 1000000) + $4::NUMERIC)" + " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" + " + total_locked_frac::NUMERIC), 0)" + " FROM merchant_inventory_locks mil" + " WHERE mil.product_serial = tmp.product_serial)" + " + (SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" + " + total_locked_frac::NUMERIC), 0)" + " FROM merchant_order_locks mol" + " WHERE mol.product_serial = tmp.product_serial)" + " ))"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "lock_product", + stmt, params); } diff --git a/src/backenddb/lookup_account.c b/src/backenddb/lookup_account.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023 Taler Systems SA + Copyright (C) 2023, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -32,7 +32,6 @@ TALER_MERCHANTDB_lookup_account (struct TALER_MERCHANTDB_PostgresContext *pg, uint64_t *account_serial) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (payto_uri.full_payto), GNUNET_PQ_query_param_end }; @@ -41,21 +40,22 @@ TALER_MERCHANTDB_lookup_account (struct TALER_MERCHANTDB_PostgresContext *pg, account_serial), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_account", - "SELECT" - " account_serial" - " FROM merchant_accounts" - " WHERE REGEXP_REPLACE(payto_uri,'\\?.*','')" - " =REGEXP_REPLACE($2,'\\?.*','')" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_account", + "SELECT" + " account_serial" + " FROM merchant_accounts" + " WHERE REGEXP_REPLACE(payto_uri,'\\?.*','')" + " =REGEXP_REPLACE($1,'\\?.*','')"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_account", + stmt, params, rs); } diff --git a/src/backenddb/lookup_all_products.c b/src/backenddb/lookup_all_products.c @@ -173,53 +173,54 @@ TALER_MERCHANTDB_lookup_all_products ( .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_all_products", - "SELECT" - " description" - ",description_i18n::TEXT" - ",product_name" - ",unit" - ",price_array" - ",taxes::TEXT" - ",total_stock" - ",total_stock_frac" - ",allow_fractional_quantity" - ",fractional_precision_level" - ",total_sold" - ",total_sold_frac" - ",total_lost" - ",total_lost_frac" - ",image" - ",minv.address::TEXT" - ",next_restock" - ",minimum_age" - ",product_id" - ",product_serial" - ",t.category_array AS categories" - ",product_group_serial" - ",money_pot_serial" - ",price_is_net" - " FROM merchant_inventory minv" - " JOIN merchant_instances inst" - " USING (merchant_serial)" - ",LATERAL (" - " SELECT ARRAY (" - " SELECT mpc.category_serial" - " FROM merchant_product_categories mpc" - " WHERE mpc.product_serial = minv.product_serial" - " ) AS category_array" - " ) t" - " WHERE inst.merchant_id=$1"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_all_products", + "SELECT" + " description" + ",description_i18n::TEXT" + ",product_name" + ",unit" + ",price_array" + ",taxes::TEXT" + ",total_stock" + ",total_stock_frac" + ",allow_fractional_quantity" + ",fractional_precision_level" + ",total_sold" + ",total_sold_frac" + ",total_lost" + ",total_lost_frac" + ",image" + ",minv.address::TEXT" + ",next_restock" + ",minimum_age" + ",product_id" + ",product_serial" + ",t.category_array AS categories" + ",product_group_serial" + ",money_pot_serial" + ",price_is_net" + " FROM merchant_inventory minv" + ",LATERAL (" + " SELECT ARRAY (" + " SELECT mpc.category_serial" + " FROM merchant_product_categories mpc" + " WHERE mpc.product_serial = minv.product_serial" + " ) AS category_array" + " ) t"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_all_products", + stmt, params, &lookup_products_cb, &plc); diff --git a/src/backenddb/lookup_categories.c b/src/backenddb/lookup_categories.c @@ -113,30 +113,31 @@ TALER_MERCHANTDB_lookup_categories (struct TALER_MERCHANTDB_PostgresContext *pg, .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_categories", - "SELECT" - " mc.category_serial" - ",mc.category_name" - ",mc.category_name_i18n::TEXT" - ",COALESCE(COUNT(mpc.product_serial),0)" - " AS product_count" - " FROM merchant_categories mc" - " LEFT JOIN merchant_product_categories mpc" - " USING (category_serial)" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " WHERE mi.merchant_id=$1" - " GROUP BY mc.category_serial" - " ORDER BY mc.category_serial;"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_categories", + "SELECT" + " mc.category_serial" + ",mc.category_name" + ",mc.category_name_i18n::TEXT" + ",COALESCE(COUNT(mpc.product_serial),0)" + " AS product_count" + " FROM merchant_categories mc" + " LEFT JOIN merchant_product_categories mpc" + " USING (category_serial)" + " GROUP BY mc.category_serial" + " ORDER BY mc.category_serial;"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_categories", + stmt, params, &lookup_categories_cb, &tlc); diff --git a/src/backenddb/lookup_categories_by_ids.c b/src/backenddb/lookup_categories_by_ids.c @@ -107,34 +107,35 @@ TALER_MERCHANTDB_lookup_categories_by_ids (struct TALER_MERCHANTDB_PostgresConte .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_array_uint64 (num_category_ids, category_ids, pg->conn), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_categories_by_ids", - "SELECT" - " mc.category_serial" - ",mc.category_name" - ",mc.category_name_i18n::TEXT" - ",COALESCE(COUNT(mpc.product_serial),0)" - " AS product_count" - " FROM merchant_categories mc" - " LEFT JOIN merchant_product_categories mpc" - " USING (category_serial)" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " WHERE mi.merchant_id=$1" - " AND mc.category_serial = ANY ($2)" - " GROUP BY mc.category_serial" - " ORDER BY mc.category_serial;"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_categories_by_ids", + "SELECT" + " mc.category_serial" + ",mc.category_name" + ",mc.category_name_i18n::TEXT" + ",COALESCE(COUNT(mpc.product_serial),0)" + " AS product_count" + " FROM merchant_categories mc" + " LEFT JOIN merchant_product_categories mpc" + " USING (category_serial)" + " WHERE mc.category_serial = ANY ($1)" + " GROUP BY mc.category_serial" + " ORDER BY mc.category_serial;"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_categories_by_ids", + stmt, params, &lookup_categories_cb, &tlc); diff --git a/src/backenddb/lookup_contract_terms.c b/src/backenddb/lookup_contract_terms.c @@ -38,7 +38,6 @@ TALER_MERCHANTDB_lookup_contract_terms (struct TALER_MERCHANTDB_PostgresContext enum GNUNET_DB_QueryStatus qs; struct TALER_ClaimTokenP ct; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_end }; @@ -52,22 +51,23 @@ TALER_MERCHANTDB_lookup_contract_terms (struct TALER_MERCHANTDB_PostgresContext &ct), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_contract_terms", - "SELECT" - " contract_terms::TEXT" - ",order_serial" - ",claim_token" - " FROM merchant_contract_terms" - " WHERE order_id=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_contract_terms", + "SELECT" + " contract_terms::TEXT" + ",order_serial" + ",claim_token" + " FROM merchant_contract_terms" + " WHERE order_id=$1"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_contract_terms", + stmt, params, (NULL != contract_terms) ? rs diff --git a/src/backenddb/lookup_contract_terms2.c b/src/backenddb/lookup_contract_terms2.c @@ -39,7 +39,6 @@ TALER_MERCHANTDB_lookup_contract_terms2 (struct TALER_MERCHANTDB_PostgresContext enum GNUNET_DB_QueryStatus qs; struct TALER_ClaimTokenP ct; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_end }; @@ -64,25 +63,26 @@ TALER_MERCHANTDB_lookup_contract_terms2 (struct TALER_MERCHANTDB_PostgresContext NULL), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_contract_terms2", - "SELECT" - " contract_terms::TEXT" - ",order_serial" - ",claim_token" - ",paid" - ",pos_key" - ",pos_algorithm" - " FROM merchant_contract_terms" - " WHERE order_id=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_contract_terms2", + "SELECT" + " contract_terms::TEXT" + ",order_serial" + ",claim_token" + ",paid" + ",pos_key" + ",pos_algorithm" + " FROM merchant_contract_terms" + " WHERE order_id=$1"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_contract_terms2", + stmt, params, (NULL != contract_terms) ? rs diff --git a/src/backenddb/lookup_contract_terms3.c b/src/backenddb/lookup_contract_terms3.c @@ -46,7 +46,6 @@ TALER_MERCHANTDB_lookup_contract_terms3 (struct TALER_MERCHANTDB_PostgresContext uint16_t ci = 0; bool choice_index_null = false; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), NULL == session_id ? GNUNET_PQ_query_param_null () @@ -75,27 +74,28 @@ TALER_MERCHANTDB_lookup_contract_terms3 (struct TALER_MERCHANTDB_PostgresContext &choice_index_null), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); *session_matches = false; check_connection (pg); - PREPARE (pg, - "lookup_contract_terms3", - "SELECT" - " contract_terms::TEXT" - ",order_serial" - ",claim_token" - ",paid" - ",wired" - ",(session_id=$3) AS session_matches" - ",choice_index" - " FROM merchant_contract_terms" - " WHERE order_id=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_contract_terms3", + "SELECT" + " contract_terms::TEXT" + ",order_serial" + ",claim_token" + ",paid" + ",wired" + ",(session_id=$2) AS session_matches" + ",choice_index" + " FROM merchant_contract_terms" + " WHERE order_id=$1"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_contract_terms3", + stmt, params, (NULL != contract_terms) ? rs diff --git a/src/backenddb/lookup_custom_units_by_names.c b/src/backenddb/lookup_custom_units_by_names.c @@ -102,36 +102,36 @@ TALER_MERCHANTDB_lookup_custom_units_by_names (struct TALER_MERCHANTDB_PostgresC .extract_failed = false }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_array_ptrs_string (num_units, (const char **) units, pg->conn), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_custom_units_by_names", - "WITH mi AS (" - " SELECT merchant_serial FROM merchant_instances WHERE merchant_id=$1" - ")" - "SELECT cu.unit_serial" - " ,cu.unit" - " ,cu.unit_name_long" - " ,cu.unit_name_short" - " ,cu.unit_name_long_i18n" - " ,cu.unit_name_short_i18n" - " ,cu.unit_allow_fraction" - " ,cu.unit_precision_level" - " ,cu.unit_active" - " ,FALSE AS unit_builtin" - " FROM merchant_custom_units cu" - " JOIN mi ON cu.merchant_serial = mi.merchant_serial" - " WHERE cu.unit = ANY ($2)" - " ORDER BY cu.unit"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_custom_units_by_names", + "SELECT cu.unit_serial" + " ,cu.unit" + " ,cu.unit_name_long" + " ,cu.unit_name_short" + " ,cu.unit_name_long_i18n" + " ,cu.unit_name_short_i18n" + " ,cu.unit_allow_fraction" + " ,cu.unit_precision_level" + " ,cu.unit_active" + " ,FALSE AS unit_builtin" + " FROM merchant_custom_units cu" + " WHERE cu.unit = ANY ($1)" + " ORDER BY cu.unit"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_custom_units_by_names", + stmt, params, &lookup_units_cb, &luc); diff --git a/src/backenddb/lookup_deposits.c b/src/backenddb/lookup_deposits.c @@ -118,7 +118,6 @@ TALER_MERCHANTDB_lookup_deposits ( void *cb_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_end }; @@ -128,33 +127,34 @@ TALER_MERCHANTDB_lookup_deposits ( .pg = pg }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); /* no preflight check here, run in its own transaction by the caller! */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Finding deposits for h_contract_terms '%s'\n", GNUNET_h2s (&h_contract_terms->hash)); check_connection (pg); - PREPARE (pg, - "lookup_deposits", - "SELECT" - " dcom.exchange_url" - ",dep.coin_pub" - ",dep.amount_with_fee" - ",dep.deposit_fee" - ",dep.refund_fee" - " FROM merchant_deposits dep" - " JOIN merchant_deposit_confirmations dcom" - " USING (deposit_confirmation_serial)" - " WHERE dcom.order_serial=" - " (SELECT order_serial" - " FROM merchant_contract_terms" - " WHERE h_contract_terms=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1))"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_deposits", + "SELECT" + " dcom.exchange_url" + ",dep.coin_pub" + ",dep.amount_with_fee" + ",dep.deposit_fee" + ",dep.refund_fee" + " FROM merchant_deposits dep" + " JOIN merchant_deposit_confirmations dcom" + " USING (deposit_confirmation_serial)" + " WHERE dcom.order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " WHERE h_contract_terms=$1)"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_deposits", + stmt, params, &lookup_deposits_cb, &ldc); diff --git a/src/backenddb/lookup_deposits_by_contract_and_coin.c b/src/backenddb/lookup_deposits_by_contract_and_coin.c @@ -245,7 +245,6 @@ TALER_MERCHANTDB_lookup_deposits_by_contract_and_coin ( void *cb_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_auto_from_type (coin_pub), GNUNET_PQ_query_param_end @@ -256,71 +255,70 @@ TALER_MERCHANTDB_lookup_deposits_by_contract_and_coin ( .pg = pg }; enum GNUNET_DB_QueryStatus qs; + char stmt_refunds[PG_PREP_INSTANCE_NAME_MAX]; + char stmt_deposits[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); /* no preflight check here, run in transaction by caller! */ TALER_LOG_DEBUG ("Looking for refund of h_contract_terms %s at `%s'\n", GNUNET_h2s (&h_contract_terms->hash), instance_id); check_connection (pg); - PREPARE (pg, - "lookup_refunds_by_coin_and_contract", - "SELECT" - " refund_amount" - " FROM merchant_refunds" - /* Join to filter by refunds that actually - did work, not only those we approved */ - " JOIN merchant_refund_proofs" - " USING (refund_serial)" - " WHERE coin_pub=$3" - " AND order_serial=" - " (SELECT order_serial" - " FROM merchant_contract_terms" - " WHERE h_contract_terms=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1))"); + PREPARE_INSTANCE (pg, + stmt_refunds, + "lookup_refunds_by_coin_and_contract", + "SELECT" + " refund_amount" + " FROM merchant_refunds" + /* Join to filter by refunds that actually + did work, not only those we approved */ + " JOIN merchant_refund_proofs" + " USING (refund_serial)" + " WHERE coin_pub=$2" + " AND order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " WHERE h_contract_terms=$1)"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_refunds_by_coin_and_contract", + stmt_refunds, params, &lookup_refunds_cb, &ldcc); if (0 > qs) return qs; - PREPARE (pg, - "lookup_deposits_by_contract_and_coin", - "SELECT" - " mcon.exchange_url" - ",dep.amount_with_fee" - ",dep.deposit_fee" - ",dep.refund_fee" - ",mcon.wire_fee" - ",acc.h_wire" - ",mcon.deposit_timestamp" - ",mct.refund_deadline" - ",mcon.exchange_sig" - ",msig.exchange_pub" - " FROM merchant_contract_terms mct" - " JOIN merchant_deposit_confirmations mcon" - " USING (order_serial)" - " JOIN merchant_deposits dep" - " USING (deposit_confirmation_serial)" - " JOIN merchant_exchange_signing_keys msig" - " ON (mcon.signkey_serial=msig.signkey_serial)" - " JOIN merchant_accounts acc" - " USING (account_serial)" - " WHERE h_contract_terms=$2" - " AND dep.coin_pub=$3" - " AND mct.merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"); + PREPARE_INSTANCE (pg, + stmt_deposits, + "lookup_deposits_by_contract_and_coin", + "SELECT" + " mcon.exchange_url" + ",dep.amount_with_fee" + ",dep.deposit_fee" + ",dep.refund_fee" + ",mcon.wire_fee" + ",acc.h_wire" + ",mcon.deposit_timestamp" + ",mct.refund_deadline" + ",mcon.exchange_sig" + ",msig.exchange_pub" + " FROM merchant_contract_terms mct" + " JOIN merchant_deposit_confirmations mcon" + " USING (order_serial)" + " JOIN merchant_deposits dep" + " USING (deposit_confirmation_serial)" + " JOIN merchant_exchange_signing_keys msig" + " ON (mcon.signkey_serial=msig.signkey_serial)" + " JOIN merchant_accounts acc" + " USING (account_serial)" + " WHERE h_contract_terms=$1" + " AND dep.coin_pub=$2"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_deposits_by_contract_and_coin", + stmt_deposits, params, &lookup_deposits_by_contract_and_coin_cb, &ldcc); diff --git a/src/backenddb/lookup_deposits_by_order.c b/src/backenddb/lookup_deposits_by_order.c @@ -135,26 +135,30 @@ TALER_MERCHANTDB_lookup_deposits_by_order (struct TALER_MERCHANTDB_PostgresConte }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "lookup_deposits_by_order", - "SELECT" - " dep.deposit_serial" - ",mcon.exchange_url" - ",acc.h_wire" - ",mcon.deposit_timestamp" - ",dep.amount_with_fee" - ",dep.deposit_fee" - ",dep.coin_pub" - " FROM merchant_deposits dep" - " JOIN merchant_deposit_confirmations mcon" - " USING(deposit_confirmation_serial)" - " JOIN merchant_accounts acc" - " USING (account_serial)" - " WHERE mcon.order_serial=$1"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_deposits_by_order", + "SELECT" + " dep.deposit_serial" + ",mcon.exchange_url" + ",acc.h_wire" + ",mcon.deposit_timestamp" + ",dep.amount_with_fee" + ",dep.deposit_fee" + ",dep.coin_pub" + " FROM merchant_deposits dep" + " JOIN merchant_deposit_confirmations mcon" + " USING(deposit_confirmation_serial)" + " JOIN merchant_accounts acc" + " USING (account_serial)" + " WHERE mcon.order_serial=$1"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_deposits_by_order", + stmt, params, &lookup_deposits_by_order_cb, &ldoc); diff --git a/src/backenddb/lookup_expected_transfer.c b/src/backenddb/lookup_expected_transfer.c @@ -40,7 +40,6 @@ TALER_MERCHANTDB_lookup_expected_transfer (struct TALER_MERCHANTDB_PostgresConte struct TALER_MasterPublicKeyP *master_pub) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&expected_incoming_serial), GNUNET_PQ_query_param_end }; @@ -67,36 +66,38 @@ TALER_MERCHANTDB_lookup_expected_transfer (struct TALER_MERCHANTDB_PostgresConte master_pub), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); *execution_time = GNUNET_TIME_UNIT_ZERO_TS; memset (expected_credit_amount, 0, sizeof (*expected_credit_amount)); check_connection (pg); - PREPARE (pg, - "lookup_expected_transfer", - "SELECT" - " met.expected_time" - " ,met.confirmed" - " ,met.expected_credit_amount" - " ,met.exchange_url" - " ,met.wtid" - " ,ma.payto_uri" - " ,mts.execution_time" - " ,esk.master_pub" - " FROM merchant_expected_transfers met" - " JOIN merchant_exchange_signing_keys esk" - " USING (signkey_serial)" - " JOIN merchant_accounts ma" - " USING (account_serial)" - " JOIN merchant_instances inst" - " USING (merchant_serial)" - " LEFT JOIN merchant_transfer_signatures mts" - " USING (expected_credit_serial)" - " WHERE inst.merchant_id=$1" - " AND met.expected_credit_serial=$2"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_expected_transfer", + "SELECT" + " met.expected_time" + " ,met.confirmed" + " ,met.expected_credit_amount" + " ,met.exchange_url" + " ,met.wtid" + " ,ma.payto_uri" + " ,mts.execution_time" + " ,esk.master_pub" + " FROM merchant_expected_transfers met" + " JOIN merchant_exchange_signing_keys esk" + " USING (signkey_serial)" + " JOIN merchant_accounts ma" + " USING (account_serial)" + " LEFT JOIN merchant_transfer_signatures mts" + " USING (expected_credit_serial)" + " WHERE met.expected_credit_serial=$1"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_expected_transfer", + stmt, params, rs); } diff --git a/src/backenddb/lookup_expected_transfers.c b/src/backenddb/lookup_expected_transfers.c @@ -171,7 +171,6 @@ TALER_MERCHANTDB_lookup_expected_transfers (struct TALER_MERCHANTDB_PostgresCont .pg = pg }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_timestamp (&before), GNUNET_PQ_query_param_timestamp (&after), GNUNET_PQ_query_param_uint64 (&offset), @@ -179,7 +178,7 @@ TALER_MERCHANTDB_lookup_expected_transfers (struct TALER_MERCHANTDB_PostgresCont NULL == payto_uri.full_payto ? GNUNET_PQ_query_param_null () /* NULL: do not filter by payto URI */ : GNUNET_PQ_query_param_string (payto_uri.full_payto), - GNUNET_PQ_query_param_bool (! by_time), /* $7: filter by time? */ + GNUNET_PQ_query_param_bool (! by_time), /* $6: filter by time? */ GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_ALL == confirmed), /* filter by confirmed? */ GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_YES == confirmed), GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_ALL == verified), /* filter by verified? */ @@ -188,87 +187,84 @@ TALER_MERCHANTDB_lookup_expected_transfers (struct TALER_MERCHANTDB_PostgresCont GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt_asc[PG_PREP_INSTANCE_NAME_MAX]; + char stmt_desc[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_expected_transfers_asc", - "SELECT" - " met.expected_credit_amount" - ",met.wtid" - ",mac.payto_uri" - ",met.exchange_url" - ",met.expected_credit_serial" - ",mts.execution_time" - ",met.confirmed" - ",met.last_http_status" - ",met.last_ec" - ",met.last_detail" - " FROM merchant_expected_transfers met" - " JOIN merchant_accounts mac" - " USING (account_serial)" - " LEFT JOIN merchant_transfer_signatures mts" - " USING (expected_credit_serial)" - " WHERE ( $7 OR " - " (mts.execution_time IS NOT NULL AND" - " mts.execution_time < $2 AND" - " mts.execution_time >= $3) )" - " AND ( (CAST($6 AS TEXT) IS NULL) OR " - " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')" - " =REGEXP_REPLACE($6,'\\?.*','')) )" - " AND ( $8 OR " - " (met.confirmed = $9) )" - " AND ( $10 OR " - " ($11 = (200=met.last_http_status) AND" - " (0=met.last_ec) ) )" - " AND merchant_serial =" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND (met.expected_credit_serial > $4)" - " ORDER BY met.expected_credit_serial ASC" - " LIMIT $5"); - PREPARE (pg, - "lookup_expected_transfers_desc", - "SELECT" - " met.expected_credit_amount" - ",met.wtid" - ",mac.payto_uri" - ",met.exchange_url" - ",met.expected_credit_serial" - ",mts.execution_time" - ",met.confirmed" - ",met.last_http_status" - ",met.last_ec" - ",met.last_detail" - " FROM merchant_expected_transfers met" - " JOIN merchant_accounts mac" - " USING (account_serial)" - " LEFT JOIN merchant_transfer_signatures mts" - " USING (expected_credit_serial)" - " WHERE ( $7 OR " - " (mts.execution_time IS NOT NULL AND" - " mts.execution_time < $2 AND" - " mts.execution_time >= $3) )" - " AND ( (CAST($6 AS TEXT) IS NULL) OR " - " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')" - " =REGEXP_REPLACE($6,'\\?.*','')) )" - " AND ( $8 OR " - " (met.confirmed = $9) )" - " AND ( $10 OR " - " ($11 = (200=met.last_http_status) AND" - " (0=met.last_ec) ) )" - " AND merchant_serial =" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND (met.expected_credit_serial < $4)" - " ORDER BY met.expected_credit_serial DESC" - " LIMIT $5"); + PREPARE_INSTANCE (pg, + stmt_asc, + "lookup_expected_transfers_asc", + "SELECT" + " met.expected_credit_amount" + ",met.wtid" + ",mac.payto_uri" + ",met.exchange_url" + ",met.expected_credit_serial" + ",mts.execution_time" + ",met.confirmed" + ",met.last_http_status" + ",met.last_ec" + ",met.last_detail" + " FROM merchant_expected_transfers met" + " JOIN merchant_accounts mac" + " USING (account_serial)" + " LEFT JOIN merchant_transfer_signatures mts" + " USING (expected_credit_serial)" + " WHERE ( $6 OR " + " (mts.execution_time IS NOT NULL AND" + " mts.execution_time < $1 AND" + " mts.execution_time >= $2) )" + " AND ( (CAST($5 AS TEXT) IS NULL) OR " + " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')" + " =REGEXP_REPLACE($5,'\\?.*','')) )" + " AND ( $7 OR " + " (met.confirmed = $8) )" + " AND ( $9 OR " + " ($10 = (200=met.last_http_status) AND" + " (0=met.last_ec) ) )" + " AND (met.expected_credit_serial > $3)" + " ORDER BY met.expected_credit_serial ASC" + " LIMIT $4"); + PREPARE_INSTANCE (pg, + stmt_desc, + "lookup_expected_transfers_desc", + "SELECT" + " met.expected_credit_amount" + ",met.wtid" + ",mac.payto_uri" + ",met.exchange_url" + ",met.expected_credit_serial" + ",mts.execution_time" + ",met.confirmed" + ",met.last_http_status" + ",met.last_ec" + ",met.last_detail" + " FROM merchant_expected_transfers met" + " JOIN merchant_accounts mac" + " USING (account_serial)" + " LEFT JOIN merchant_transfer_signatures mts" + " USING (expected_credit_serial)" + " WHERE ( $6 OR " + " (mts.execution_time IS NOT NULL AND" + " mts.execution_time < $1 AND" + " mts.execution_time >= $2) )" + " AND ( (CAST($5 AS TEXT) IS NULL) OR " + " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')" + " =REGEXP_REPLACE($5,'\\?.*','')) )" + " AND ( $7 OR " + " (met.confirmed = $8) )" + " AND ( $9 OR " + " ($10 = (200=met.last_http_status) AND" + " (0=met.last_ec) ) )" + " AND (met.expected_credit_serial < $3)" + " ORDER BY met.expected_credit_serial DESC" + " LIMIT $4"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - (limit > 0) - ? "lookup_expected_transfers_asc" - : "lookup_expected_transfers_desc", + (limit > 0) ? stmt_asc : stmt_desc, params, &lookup_expected_transfers_cb, &ltc); diff --git a/src/backenddb/lookup_instances.c b/src/backenddb/lookup_instances.c @@ -80,61 +80,61 @@ lookup_instances_cb (void *cls, bool no_priv; char *dwtri; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("merchant_serial", + GNUNET_PQ_result_spec_uint64 ("out_merchant_serial", &instance_serial), - GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", + GNUNET_PQ_result_spec_auto_from_type ("out_merchant_pub", &merchant_pub), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_auto_from_type ("auth_hash", + GNUNET_PQ_result_spec_auto_from_type ("out_auth_hash", &ias.auth_hash), &no_auth), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_auto_from_type ("auth_salt", + GNUNET_PQ_result_spec_auto_from_type ("out_auth_salt", &ias.auth_salt), &no_salt), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_auto_from_type ("merchant_priv", + GNUNET_PQ_result_spec_auto_from_type ("out_merchant_priv", &merchant_priv), &no_priv), - GNUNET_PQ_result_spec_string ("merchant_id", + GNUNET_PQ_result_spec_string ("out_merchant_id", &is.id), - GNUNET_PQ_result_spec_string ("merchant_name", + GNUNET_PQ_result_spec_string ("out_merchant_name", &is.name), - TALER_PQ_result_spec_json ("address", + TALER_PQ_result_spec_json ("out_address", &is.address), - TALER_PQ_result_spec_json ("jurisdiction", + TALER_PQ_result_spec_json ("out_jurisdiction", &is.jurisdiction), - GNUNET_PQ_result_spec_bool ("use_stefan", + GNUNET_PQ_result_spec_bool ("out_use_stefan", &is.use_stefan), - GNUNET_PQ_result_spec_bool ("phone_validated", + GNUNET_PQ_result_spec_bool ("out_phone_validated", &is.phone_validated), - GNUNET_PQ_result_spec_bool ("email_validated", + GNUNET_PQ_result_spec_bool ("out_email_validated", &is.email_validated), GNUNET_PQ_result_spec_relative_time ( - "default_wire_transfer_delay", + "out_default_wire_transfer_delay", &is.default_wire_transfer_delay), - GNUNET_PQ_result_spec_relative_time ("default_pay_delay", + GNUNET_PQ_result_spec_relative_time ("out_default_pay_delay", &is.default_pay_delay), - GNUNET_PQ_result_spec_relative_time ("default_refund_delay", + GNUNET_PQ_result_spec_relative_time ("out_default_refund_delay", &is.default_refund_delay), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("website", + GNUNET_PQ_result_spec_string ("out_website", &is.website), NULL), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("email", + GNUNET_PQ_result_spec_string ("out_email", &is.email), NULL), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("phone_number", + GNUNET_PQ_result_spec_string ("out_phone_number", &is.phone), NULL), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("logo", + GNUNET_PQ_result_spec_string ("out_logo", &is.logo), NULL), GNUNET_PQ_result_spec_string ( - "default_wire_transfer_rounding_interval", + "out_default_wire_transfer_rounding_interval", &dwtri), GNUNET_PQ_result_spec_end }; @@ -186,6 +186,8 @@ TALER_MERCHANTDB_lookup_instances ( .pg = pg }; struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_bool (active_only), + GNUNET_PQ_query_param_null (), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; @@ -194,60 +196,30 @@ TALER_MERCHANTDB_lookup_instances ( PREPARE (pg, "lookup_instances", "SELECT" - " mi.merchant_serial" - ",mi.merchant_pub" - ",mi.auth_hash" - ",mi.auth_salt" - ",mi.merchant_id" - ",mi.merchant_name" - ",mi.address::TEXT" - ",mi.jurisdiction::TEXT" - ",mi.use_stefan" - ",mi.default_wire_transfer_delay" - ",mi.default_pay_delay" - ",mi.default_refund_delay" - ",mi.website" - ",mi.email" - ",mi.phone_number" - ",mi.phone_validated" - ",mi.email_validated" - ",mi.logo" - ",mi.default_wire_transfer_rounding_interval::TEXT" - ",mk.merchant_priv" - " FROM merchant_instances mi" - " LEFT JOIN merchant_keys mk" - " USING (merchant_serial)"); - PREPARE (pg, - "lookup_active_instances", - "SELECT " - " mi.merchant_serial" - ",mi.merchant_pub" - ",mi.auth_hash" - ",mi.auth_salt" - ",mi.merchant_id" - ",mi.merchant_name" - ",mi.address::TEXT" - ",mi.jurisdiction::TEXT" - ",mi.use_stefan" - ",mi.default_wire_transfer_delay" - ",mi.default_pay_delay" - ",mi.default_refund_delay" - ",mi.website" - ",mi.email" - ",mi.phone_number" - ",mi.phone_validated" - ",mi.email_validated" - ",mi.logo" - ",mi.default_wire_transfer_rounding_interval::TEXT" - ",mk.merchant_priv" - " FROM merchant_instances mi" - " JOIN merchant_keys mk" - " USING (merchant_serial)"); + " out_merchant_serial" + " ,out_merchant_pub" + " ,out_auth_hash" + " ,out_auth_salt" + " ,out_merchant_priv" + " ,out_merchant_id" + " ,out_merchant_name" + " ,out_address::TEXT" + " ,out_jurisdiction::TEXT" + " ,out_use_stefan" + " ,out_phone_validated" + " ,out_email_validated" + " ,out_default_wire_transfer_delay" + " ,out_default_pay_delay" + " ,out_default_refund_delay" + " ,out_website" + " ,out_email" + " ,out_phone_number" + " ,out_logo" + " ,out_default_wire_transfer_rounding_interval::TEXT" + " FROM merchant.lookup_instances($1, $2)"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - active_only - ? "lookup_active_instances" - : "lookup_instances", + "lookup_instances", params, &lookup_instances_cb, &lic); @@ -270,6 +242,7 @@ TALER_MERCHANTDB_lookup_instance (struct TALER_MERCHANTDB_PostgresContext *pg, .pg = pg }; struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_bool (active_only), GNUNET_PQ_query_param_string (id), GNUNET_PQ_query_param_end }; @@ -279,62 +252,30 @@ TALER_MERCHANTDB_lookup_instance (struct TALER_MERCHANTDB_PostgresContext *pg, PREPARE (pg, "lookup_instance", "SELECT" - " mi.merchant_serial" - ",mi.merchant_pub" - ",mi.auth_hash" - ",mi.auth_salt" - ",mi.merchant_id" - ",mi.merchant_name" - ",mi.address::TEXT" - ",mi.jurisdiction::TEXT" - ",mi.use_stefan" - ",mi.default_wire_transfer_delay" - ",mi.default_pay_delay" - ",mi.default_refund_delay" - ",mi.website" - ",mi.email" - ",mi.phone_number" - ",mi.phone_validated" - ",mi.email_validated" - ",mi.logo" - ",mi.default_wire_transfer_rounding_interval::TEXT" - ",mk.merchant_priv" - " FROM merchant_instances mi" - " LEFT JOIN merchant_keys mk" - " USING (merchant_serial)" - " WHERE merchant_id=$1"); - PREPARE (pg, - "lookup_active_instance", - "SELECT" - " mi.merchant_serial" - ",mi.merchant_pub" - ",mi.auth_hash" - ",mi.auth_salt" - ",mi.merchant_id" - ",mi.merchant_name" - ",mi.address::TEXT" - ",mi.jurisdiction::TEXT" - ",mi.use_stefan" - ",mi.default_wire_transfer_delay" - ",mi.default_pay_delay" - ",mi.default_refund_delay" - ",mi.website" - ",mi.email" - ",mi.phone_number" - ",mi.phone_validated" - ",mi.email_validated" - ",mi.logo" - ",mi.default_wire_transfer_rounding_interval::TEXT" - ",mk.merchant_priv" - " FROM merchant_instances mi" - " JOIN merchant_keys mk" - " USING (merchant_serial)" - " WHERE merchant_id=$1"); + " out_merchant_serial" + " ,out_merchant_pub" + " ,out_auth_hash" + " ,out_auth_salt" + " ,out_merchant_priv" + " ,out_merchant_id" + " ,out_merchant_name" + " ,out_address::TEXT" + " ,out_jurisdiction::TEXT" + " ,out_use_stefan" + " ,out_phone_validated" + " ,out_email_validated" + " ,out_default_wire_transfer_delay" + " ,out_default_pay_delay" + " ,out_default_refund_delay" + " ,out_website" + " ,out_email" + " ,out_phone_number" + " ,out_logo" + " ,out_default_wire_transfer_rounding_interval::TEXT" + " FROM merchant.lookup_instances($1, $2)"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - active_only - ? "lookup_active_instance" - : "lookup_instance", + "lookup_instance", params, &lookup_instances_cb, &lic); diff --git a/src/backenddb/lookup_inventory_products.c b/src/backenddb/lookup_inventory_products.c @@ -145,74 +145,75 @@ TALER_MERCHANTDB_lookup_inventory_products ( .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_inventory_products", - "SELECT" - " description" - ",description_i18n::TEXT" - ",product_name" - ",unit" - ",price_array" - ",CASE WHEN minv.total_stock = 9223372036854775807" - " THEN minv.total_stock" - " ELSE (GREATEST(0, rt.remaining_total) / 1000000)::INT8" - " END AS remaining_stock" - ",CASE WHEN minv.total_stock = 9223372036854775807" - " THEN 2147483647" - " ELSE mod(GREATEST(0, rt.remaining_total), 1000000)::INT4" - " END AS remaining_stock_frac" - ",taxes::TEXT" - ",image_hash" - ",allow_fractional_quantity" - ",fractional_precision_level" - ",product_id" - ",t.category_array AS categories" - " FROM merchant_inventory minv" - " JOIN merchant_instances inst" - " USING (merchant_serial)" - ",LATERAL (" - " SELECT ARRAY (" - " SELECT mpc.category_serial" - " FROM merchant_product_categories mpc" - " WHERE mpc.product_serial = minv.product_serial" - " ) AS category_array" - " ) t" - ",LATERAL (" - " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" - " + total_locked_frac::NUMERIC), 0)" - " AS total_locked" - " FROM merchant_inventory_locks mil" - " WHERE mil.product_serial = minv.product_serial" - " ) il" - ",LATERAL (" - " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" - " + total_locked_frac::NUMERIC), 0)" - " AS total_locked" - " FROM merchant_order_locks mol" - " WHERE mol.product_serial = minv.product_serial" - " ) ol" - ",LATERAL (" - " SELECT" - " (minv.total_stock::NUMERIC * 1000000" - " + minv.total_stock_frac::NUMERIC)" - " - (minv.total_sold::NUMERIC * 1000000" - " + minv.total_sold_frac::NUMERIC)" - " - (minv.total_lost::NUMERIC * 1000000" - " + minv.total_lost_frac::NUMERIC)" - " - il.total_locked" - " - ol.total_locked" - " AS remaining_total" - " ) rt" - " WHERE inst.merchant_id=$1"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_inventory_products", + "SELECT" + " description" + ",description_i18n::TEXT" + ",product_name" + ",unit" + ",price_array" + ",CASE WHEN minv.total_stock = 9223372036854775807" + " THEN minv.total_stock" + " ELSE (GREATEST(0, rt.remaining_total) / 1000000)::INT8" + " END AS remaining_stock" + ",CASE WHEN minv.total_stock = 9223372036854775807" + " THEN 2147483647" + " ELSE mod(GREATEST(0, rt.remaining_total), 1000000)::INT4" + " END AS remaining_stock_frac" + ",taxes::TEXT" + ",image_hash" + ",allow_fractional_quantity" + ",fractional_precision_level" + ",product_id" + ",t.category_array AS categories" + " FROM merchant_inventory minv" + ",LATERAL (" + " SELECT ARRAY (" + " SELECT mpc.category_serial" + " FROM merchant_product_categories mpc" + " WHERE mpc.product_serial = minv.product_serial" + " ) AS category_array" + " ) t" + ",LATERAL (" + " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" + " + total_locked_frac::NUMERIC), 0)" + " AS total_locked" + " FROM merchant_inventory_locks mil" + " WHERE mil.product_serial = minv.product_serial" + " ) il" + ",LATERAL (" + " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" + " + total_locked_frac::NUMERIC), 0)" + " AS total_locked" + " FROM merchant_order_locks mol" + " WHERE mol.product_serial = minv.product_serial" + " ) ol" + ",LATERAL (" + " SELECT" + " (minv.total_stock::NUMERIC * 1000000" + " + minv.total_stock_frac::NUMERIC)" + " - (minv.total_sold::NUMERIC * 1000000" + " + minv.total_sold_frac::NUMERIC)" + " - (minv.total_lost::NUMERIC * 1000000" + " + minv.total_lost_frac::NUMERIC)" + " - il.total_locked" + " - ol.total_locked" + " AS remaining_total" + " ) rt"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_inventory_products", + stmt, params, &lookup_inventory_products_cb, &plc); diff --git a/src/backenddb/lookup_inventory_products_filtered.c b/src/backenddb/lookup_inventory_products_filtered.c @@ -142,7 +142,6 @@ TALER_MERCHANTDB_lookup_inventory_products_filtered ( .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), (0 == num_product_ids) ? GNUNET_PQ_query_param_null () : GNUNET_PQ_query_param_array_ptrs_string ( @@ -157,82 +156,84 @@ TALER_MERCHANTDB_lookup_inventory_products_filtered ( GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_inventory_products_filtered", - "SELECT" - " description" - ",description_i18n::TEXT" - ",product_name" - ",unit" - ",price_array" - ",CASE WHEN minv.total_stock = 9223372036854775807" - " THEN minv.total_stock" - " ELSE (GREATEST(0, rt.remaining_total) / 1000000)::INT8" - " END AS remaining_stock" - ",CASE WHEN minv.total_stock = 9223372036854775807" - " THEN 2147483647" - " ELSE mod(GREATEST(0, rt.remaining_total), 1000000)::INT4" - " END AS remaining_stock_frac" - ",taxes::TEXT" - ",image_hash" - ",allow_fractional_quantity" - ",fractional_precision_level" - ",product_id" - ",t.category_array AS categories" - " FROM merchant_inventory minv" - " JOIN merchant_instances inst" - " USING (merchant_serial)" - ",LATERAL (" - " SELECT ARRAY (" - " SELECT mpc.category_serial" - " FROM merchant_product_categories mpc" - " WHERE mpc.product_serial = minv.product_serial" - " ) AS category_array" - " ) t" - ",LATERAL (" - " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" - " + total_locked_frac::NUMERIC), 0)" - " AS total_locked" - " FROM merchant_inventory_locks mil" - " WHERE mil.product_serial = minv.product_serial" - " ) il" - ",LATERAL (" - " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" - " + total_locked_frac::NUMERIC), 0)" - " AS total_locked" - " FROM merchant_order_locks mol" - " WHERE mol.product_serial = minv.product_serial" - " ) ol" - ",LATERAL (" - " SELECT" - " (minv.total_stock::NUMERIC * 1000000" - " + minv.total_stock_frac::NUMERIC)" - " - (minv.total_sold::NUMERIC * 1000000" - " + minv.total_sold_frac::NUMERIC)" - " - (minv.total_lost::NUMERIC * 1000000" - " + minv.total_lost_frac::NUMERIC)" - " - il.total_locked" - " - ol.total_locked" - " AS remaining_total" - " ) rt" - " WHERE inst.merchant_id=$1" - " AND (" - " (COALESCE (array_length ($2::TEXT[], 1), 0) > 0" - " AND minv.product_id = ANY ($2::TEXT[]))" - " OR" - " (COALESCE (array_length ($3::BIGINT[], 1), 0) > 0" - " AND EXISTS (" - " SELECT 1" - " FROM merchant_product_categories mpc" - " WHERE mpc.product_serial = minv.product_serial" - " AND mpc.category_serial = ANY ($3::BIGINT[])" - " ))" - " )"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_inventory_products_filtered", + "SELECT" + " description" + ",description_i18n::TEXT" + ",product_name" + ",unit" + ",price_array" + ",CASE WHEN minv.total_stock = 9223372036854775807" + " THEN minv.total_stock" + " ELSE (GREATEST(0, rt.remaining_total) / 1000000)::INT8" + " END AS remaining_stock" + ",CASE WHEN minv.total_stock = 9223372036854775807" + " THEN 2147483647" + " ELSE mod(GREATEST(0, rt.remaining_total), 1000000)::INT4" + " END AS remaining_stock_frac" + ",taxes::TEXT" + ",image_hash" + ",allow_fractional_quantity" + ",fractional_precision_level" + ",product_id" + ",t.category_array AS categories" + " FROM merchant_inventory minv" + ",LATERAL (" + " SELECT ARRAY (" + " SELECT mpc.category_serial" + " FROM merchant_product_categories mpc" + " WHERE mpc.product_serial = minv.product_serial" + " ) AS category_array" + " ) t" + ",LATERAL (" + " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" + " + total_locked_frac::NUMERIC), 0)" + " AS total_locked" + " FROM merchant_inventory_locks mil" + " WHERE mil.product_serial = minv.product_serial" + " ) il" + ",LATERAL (" + " SELECT COALESCE(SUM(total_locked::NUMERIC * 1000000" + " + total_locked_frac::NUMERIC), 0)" + " AS total_locked" + " FROM merchant_order_locks mol" + " WHERE mol.product_serial = minv.product_serial" + " ) ol" + ",LATERAL (" + " SELECT" + " (minv.total_stock::NUMERIC * 1000000" + " + minv.total_stock_frac::NUMERIC)" + " - (minv.total_sold::NUMERIC * 1000000" + " + minv.total_sold_frac::NUMERIC)" + " - (minv.total_lost::NUMERIC * 1000000" + " + minv.total_lost_frac::NUMERIC)" + " - il.total_locked" + " - ol.total_locked" + " AS remaining_total" + " ) rt" + " WHERE (" + " (COALESCE (array_length ($1::TEXT[], 1), 0) > 0" + " AND minv.product_id = ANY ($1::TEXT[]))" + " OR" + " (COALESCE (array_length ($2::BIGINT[], 1), 0) > 0" + " AND EXISTS (" + " SELECT 1" + " FROM merchant_product_categories mpc" + " WHERE mpc.product_serial = minv.product_serial" + " AND mpc.category_serial = ANY ($2::BIGINT[])" + " ))" + " )"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_inventory_products_filtered", + stmt, params, &lookup_inventory_products_cb, &plc); diff --git a/src/backenddb/lookup_login_tokens.c b/src/backenddb/lookup_login_tokens.c @@ -123,54 +123,52 @@ TALER_MERCHANTDB_lookup_login_tokens (struct TALER_MERCHANTDB_PostgresContext *p .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_timestamp (&now), GNUNET_PQ_query_param_uint64 (&offset), GNUNET_PQ_query_param_uint64 (&plimit), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt_asc[PG_PREP_INSTANCE_NAME_MAX]; + char stmt_desc[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_login_tokens_asc", - "SELECT" - " token" - ",serial" - ",creation_time" - ",expiration_time" - ",validity_scope" - ",description" - " FROM merchant_login_tokens" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND expiration_time > $2" - " AND serial > $3" - " ORDER BY serial ASC" - " LIMIT $4"); - PREPARE (pg, - "lookup_login_tokens_desc", - "SELECT" - " token" - ",serial" - ",creation_time" - ",expiration_time" - ",validity_scope" - ",description" - " FROM merchant_login_tokens" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND expiration_time > $2" - " AND serial < $3" - " ORDER BY serial DESC" - " LIMIT $4"); + PREPARE_INSTANCE (pg, + stmt_asc, + "lookup_login_tokens_asc", + "SELECT" + " token" + ",serial" + ",creation_time" + ",expiration_time" + ",validity_scope" + ",description" + " FROM merchant_login_tokens" + " WHERE expiration_time > $1" + " AND serial > $2" + " ORDER BY serial ASC" + " LIMIT $3"); + PREPARE_INSTANCE (pg, + stmt_desc, + "lookup_login_tokens_desc", + "SELECT" + " token" + ",serial" + ",creation_time" + ",expiration_time" + ",validity_scope" + ",description" + " FROM merchant_login_tokens" + " WHERE expiration_time > $1" + " AND serial < $2" + " ORDER BY serial DESC" + " LIMIT $3"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - (limit > 0) - ? "lookup_login_tokens_asc" - : "lookup_login_tokens_desc", + (limit > 0) ? stmt_asc : stmt_desc, params, &lookup_login_tokens_cb, &plc); diff --git a/src/backenddb/lookup_mfa_challenge.c b/src/backenddb/lookup_mfa_challenge.c @@ -69,39 +69,38 @@ TALER_MERCHANTDB_lookup_mfa_challenge ( &chan_str), GNUNET_PQ_result_spec_string ("required_address", required_address), - GNUNET_PQ_result_spec_string ("merchant_id", - instance_name), GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; - PREPARE (pg, - "lookup_mfa_challenge", - "SELECT " - " tc.op::TEXT" - " ,tc.salt" - " ,tc.confirmation_date" - " ,tc.retransmission_date" - " ,tc.retry_counter" - " ,tc.required_address" - " ,tc.tan_channel::TEXT" - " ,mi.merchant_id" - " FROM tan_challenges tc" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " WHERE (challenge_id = $1)" - " AND (h_body = $2)" - " AND (expiration_date > $3)"); + GNUNET_assert (NULL != pg->current_merchant_id); + PREPARE_INSTANCE (pg, + stmt, + "lookup_mfa_challenge", + "SELECT " + " op::TEXT" + " ,salt" + " ,confirmation_date" + " ,retransmission_date" + " ,retry_counter" + " ,required_address" + " ,tan_channel::TEXT" + " FROM tan_challenges" + " WHERE (challenge_id = $1)" + " AND (h_body = $2)" + " AND (expiration_date > $3)"); /* Initialize to conservative values in case qs ends up <= 0 */ *tan_channel = TALER_MERCHANT_MFA_CHANNEL_NONE; *op = TALER_MERCHANT_MFA_CO_NONE; *retry_counter = 0; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_mfa_challenge", + stmt, params, rs); if (qs <= 0) return qs; + *instance_name = GNUNET_strdup (pg->current_merchant_id); if (no_conf) *confirmation_date = GNUNET_TIME_UNIT_FOREVER_ABS; *tan_channel = TALER_MERCHANT_MFA_channel_from_string (chan_str); diff --git a/src/backenddb/lookup_order.c b/src/backenddb/lookup_order.c @@ -39,7 +39,6 @@ TALER_MERCHANTDB_lookup_order ( struct TALER_ClaimTokenP ct; enum GNUNET_DB_QueryStatus qs; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_end }; @@ -52,26 +51,27 @@ TALER_MERCHANTDB_lookup_order ( h_post_data), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Finding contract term, order_id: '%s', instance_id: '%s'.\n", order_id, instance_id); check_connection (pg); - PREPARE (pg, - "lookup_order", - "SELECT" - " contract_terms::TEXT" - ",claim_token" - ",h_post_data" - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND merchant_orders.order_id=$2"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_order", + "SELECT" + " contract_terms::TEXT" + ",claim_token" + ",h_post_data" + " FROM merchant_orders" + " WHERE order_id=$1"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_order", + stmt, params, rs); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) diff --git a/src/backenddb/lookup_order_by_fulfillment.c b/src/backenddb/lookup_order_by_fulfillment.c @@ -35,7 +35,6 @@ TALER_MERCHANTDB_lookup_order_by_fulfillment (struct TALER_MERCHANTDB_PostgresCo char **order_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (fulfillment_url), GNUNET_PQ_query_param_string (session_id), GNUNET_PQ_query_param_bool (allow_refunded_for_repurchase), @@ -46,33 +45,34 @@ TALER_MERCHANTDB_lookup_order_by_fulfillment (struct TALER_MERCHANTDB_PostgresCo order_id), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_order_by_fulfillment", - "SELECT" - " mct.order_id" - " FROM merchant_contract_terms mct" - " LEFT JOIN merchant_refunds mref" - " USING (order_serial)" - " WHERE fulfillment_url=$2" - " AND session_id=$3" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND ((CAST($4 AS BOOL)) OR" - " mref.refund_serial IS NULL)" - /* Theoretically, multiple paid orders - for the same fulfillment URL could - exist for this session_id -- if a - wallet was broken and did multiple - payments without repurchase detection. - So we need to limit to 1 when returning! */ - " ORDER BY order_id DESC" - " LIMIT 1;"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_order_by_fulfillment", + "SELECT" + " mct.order_id" + " FROM merchant_contract_terms mct" + " LEFT JOIN merchant_refunds mref" + " USING (order_serial)" + " WHERE fulfillment_url=$1" + " AND session_id=$2" + " AND ((CAST($3 AS BOOL)) OR" + " mref.refund_serial IS NULL)" + /* Theoretically, multiple paid orders + for the same fulfillment URL could + exist for this session_id -- if a + wallet was broken and did multiple + payments without repurchase detection. + So we need to limit to 1 when returning! */ + " ORDER BY order_id DESC" + " LIMIT 1;"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_order_by_fulfillment", + stmt, params, rs); } diff --git a/src/backenddb/lookup_order_charity.c b/src/backenddb/lookup_order_charity.c @@ -47,7 +47,6 @@ TALER_MERCHANTDB_lookup_order_charity ( uint64_t *donau_instance_serial) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (donau_url), GNUNET_PQ_query_param_end }; @@ -67,29 +66,30 @@ TALER_MERCHANTDB_lookup_order_charity ( donau_instance_serial), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_donau_charity", - "SELECT" - " di.donau_instances_serial" - " ,di.charity_id" - " ,k.merchant_priv" - " ,dk.keys_json::TEXT" - " ,di.charity_max_per_year" - " ,di.charity_receipts_to_date" - " FROM merchant_donau_instances di" - " JOIN merchant_donau_keys dk" - " ON dk.donau_url = di.donau_url" - " JOIN merchant_instances mi" - " ON mi.merchant_serial = di.merchant_instance_serial" - " JOIN merchant_keys k" - " ON k.merchant_serial = mi.merchant_serial" - " WHERE mi.merchant_id = $1" - " AND di.donau_url = $2;"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_donau_charity", + "SELECT" + " di.donau_instances_serial" + " ,di.charity_id" + " ,k.merchant_priv" + " ,dk.keys_json::TEXT" + " ,di.charity_max_per_year" + " ,di.charity_receipts_to_date" + " FROM merchant_donau_instances di" + " JOIN merchant_donau_keys dk" + " ON dk.donau_url = di.donau_url" + " CROSS JOIN merchant_keys k" + " WHERE di.donau_url = $1;"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_donau_charity", + stmt, params, rs); } diff --git a/src/backenddb/lookup_order_status.c b/src/backenddb/lookup_order_status.c @@ -35,7 +35,6 @@ TALER_MERCHANTDB_lookup_order_status (struct TALER_MERCHANTDB_PostgresContext *p uint8_t paid8; enum GNUNET_DB_QueryStatus qs; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_end }; @@ -46,21 +45,22 @@ TALER_MERCHANTDB_lookup_order_status (struct TALER_MERCHANTDB_PostgresContext *p &paid8), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_order_status", - "SELECT" - " h_contract_terms" - ",paid" - " FROM merchant_contract_terms" - " WHERE merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND order_id=$2"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_order_status", + "SELECT" + " h_contract_terms" + ",paid" + " FROM merchant_contract_terms" + " WHERE order_id=$1"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_order_status", + stmt, params, rs); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) diff --git a/src/backenddb/lookup_order_status_by_serial.c b/src/backenddb/lookup_order_status_by_serial.c @@ -34,7 +34,6 @@ TALER_MERCHANTDB_lookup_order_status_by_serial (struct TALER_MERCHANTDB_Postgres bool *paid) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&order_serial), GNUNET_PQ_query_param_end }; @@ -47,23 +46,24 @@ TALER_MERCHANTDB_lookup_order_status_by_serial (struct TALER_MERCHANTDB_Postgres paid), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); *paid = false; /* just to be safe(r) */ - PREPARE (pg, - "lookup_order_status_by_serial", - "SELECT" - " h_contract_terms" - ",order_id" - ",paid" - " FROM merchant_contract_terms" - " WHERE merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND order_serial=$2"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_order_status_by_serial", + "SELECT" + " h_contract_terms" + ",order_id" + ",paid" + " FROM merchant_contract_terms" + " WHERE order_serial=$1"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_order_status_by_serial", + stmt, params, rs); } diff --git a/src/backenddb/lookup_order_summary.c b/src/backenddb/lookup_order_summary.c @@ -33,7 +33,6 @@ TALER_MERCHANTDB_lookup_order_summary (struct TALER_MERCHANTDB_PostgresContext * uint64_t *order_serial) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_end }; @@ -44,31 +43,28 @@ TALER_MERCHANTDB_lookup_order_summary (struct TALER_MERCHANTDB_PostgresContext * timestamp), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_order_summary", - "(SELECT" - " creation_time" - ",order_serial" - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND merchant_contract_terms.order_id=$2)" - "UNION" - "(SELECT" - " creation_time" - ",order_serial" - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND merchant_orders.order_id=$2)"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_order_summary", + "(SELECT" + " creation_time" + ",order_serial" + " FROM merchant_contract_terms" + " WHERE order_id=$1)" + "UNION" + "(SELECT" + " creation_time" + ",order_serial" + " FROM merchant_orders" + " WHERE order_id=$1)"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_order_summary", + stmt, params, rs); } diff --git a/src/backenddb/lookup_orders.c b/src/backenddb/lookup_orders.c @@ -111,18 +111,17 @@ TALER_MERCHANTDB_lookup_orders ( uint64_t limit = (of->delta > 0) ? of->delta : -of->delta; struct GNUNET_PQ_QueryParam params[] = { /* $1 */ - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&limit), GNUNET_PQ_query_param_uint64 (&of->start_row), GNUNET_PQ_query_param_timestamp (&of->date), GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL == of->paid)), - /* $6 */ + /* $5 */ GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES == of->paid)), GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL == of->refunded)), GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES == of->refunded)), GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL == of->wired)), GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES == of->wired)), - /* $11 */ + /* $10 */ GNUNET_PQ_query_param_bool (NULL == of->session_id), NULL == of->session_id ? GNUNET_PQ_query_param_null () @@ -137,151 +136,135 @@ TALER_MERCHANTDB_lookup_orders ( GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; - char stmt[128]; + char stmt_inc[PG_PREP_INSTANCE_NAME_MAX]; + char stmt_dec[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Looking up orders, using filter paid: %d, refunded: %d, wired: %d\n", of->paid, of->refunded, of->wired); - GNUNET_snprintf (stmt, - sizeof (stmt), - "lookup_orders_%s", - (of->delta > 0) ? "inc" : "dec"); - PREPARE (pg, - "lookup_orders_dec", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND ($5 OR " - " NOT CAST($6 AS BOOL))" /* unclaimed orders are never paid */ - " AND ($7 OR " - " NOT CAST($8 AS BOOL))"/* unclaimed orders are never refunded */ - " AND ($9 OR " - " NOT CAST($10 AS BOOL))" /* unclaimed orders are never wired */ - " AND" - " order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms)" /* only select unclaimed orders */ - " AND ($11 OR " - " ($12 = session_id))" - " AND ($13 OR " - " ($14 = fulfillment_url))" - " AND ( ($15::TEXT IS NULL) OR " - " (LOWER(contract_terms ->> 'summary') LIKE LOWER($15)) )" - " ORDER BY order_serial DESC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial < $3" - " AND" - " creation_time < $4" - " AND ($5 OR " - " (CAST($6 AS BOOL) = paid))" - " AND ($7 OR " - " (CAST($8 AS BOOL) = (order_serial IN" - " (SELECT order_serial " - " FROM merchant_refunds))))" - " AND ($9 OR" - " (CAST($10 AS BOOL) = wired))" - " AND ($11 OR " - " ($12 = session_id))" - " AND ($13 OR " - " ($14 = fulfillment_url))" - " AND ( ($15::TEXT IS NULL) OR " - " (LOWER(contract_terms ->> 'summary') LIKE LOWER($15)) )" - " ORDER BY order_serial DESC" - " LIMIT $2)" - " ORDER BY order_serial DESC" - " LIMIT $2"); + PREPARE_INSTANCE (pg, + stmt_dec, + "lookup_orders_dec", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + " FROM merchant_orders" + " WHERE order_serial < $2" + " AND" + " creation_time < $3" + " AND ($4 OR " + " NOT CAST($5 AS BOOL))" /* unclaimed orders are never paid */ + " AND ($6 OR " + " NOT CAST($7 AS BOOL))"/* unclaimed orders are never refunded */ + " AND ($8 OR " + " NOT CAST($9 AS BOOL))" /* unclaimed orders are never wired */ + " AND" + " order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms)" /* only select unclaimed orders */ + " AND ($10 OR " + " ($11 = session_id))" + " AND ($12 OR " + " ($13 = fulfillment_url))" + " AND ( ($14::TEXT IS NULL) OR " + " (LOWER(contract_terms ->> 'summary') LIKE LOWER($14)) )" + " ORDER BY order_serial DESC" + " LIMIT $1)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + " FROM merchant_contract_terms" + " WHERE order_serial < $2" + " AND" + " creation_time < $3" + " AND ($4 OR " + " (CAST($5 AS BOOL) = paid))" + " AND ($6 OR " + " (CAST($7 AS BOOL) = (order_serial IN" + " (SELECT order_serial " + " FROM merchant_refunds))))" + " AND ($8 OR" + " (CAST($9 AS BOOL) = wired))" + " AND ($10 OR " + " ($11 = session_id))" + " AND ($12 OR " + " ($13 = fulfillment_url))" + " AND ( ($14::TEXT IS NULL) OR " + " (LOWER(contract_terms ->> 'summary') LIKE LOWER($14)) )" + " ORDER BY order_serial DESC" + " LIMIT $1)" + " ORDER BY order_serial DESC" + " LIMIT $1"); - PREPARE (pg, - "lookup_orders_inc", - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - " FROM merchant_orders" - " WHERE merchant_orders.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND ($5 OR " - " NOT CAST($6 AS BOOL))" /* unclaimed orders are never paid */ - " AND ($7 OR " - " NOT CAST($8 AS BOOL))"/* unclaimed orders are never refunded */ - " AND ($9 OR " - " NOT CAST($10 AS BOOL))" /* unclaimed orders are never wired */ - " AND" - " (order_serial NOT IN" - " (SELECT order_serial" - " FROM merchant_contract_terms))" /* only select unclaimed orders */ - " AND ($11 OR " - " ($12 = session_id))" - " AND ($13 OR " - " ($14 = fulfillment_url))" - " AND ( ($15::TEXT IS NULL) OR " - " (LOWER(contract_terms ->> 'summary') LIKE LOWER($15)) )" - " ORDER BY order_serial ASC" - " LIMIT $2)" - "UNION " /* union ensures elements are distinct! */ - "(SELECT" - " order_id" - ",order_serial" - ",creation_time" - " FROM merchant_contract_terms" - " WHERE merchant_contract_terms.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND" - " order_serial > $3" - " AND" - " creation_time > $4" - " AND ($5 OR " - " (CAST($6 AS BOOL) = paid))" - " AND ($7 OR " - " (CAST($8 AS BOOL) = (order_serial IN" - " (SELECT order_serial " - " FROM merchant_refunds))))" - " AND ($9 OR" - " (CAST($10 AS BOOL) = wired))" - " AND ($11 OR " - " ($12 = session_id))" - " AND ($13 OR " - " ($14 = fulfillment_url))" - " AND ( ($15::TEXT IS NULL) OR " - " (LOWER(contract_terms ->> 'summary') LIKE LOWER($15)) )" - " ORDER BY order_serial ASC" - " LIMIT $2)" - " ORDER BY order_serial ASC" - " LIMIT $2"); + PREPARE_INSTANCE (pg, + stmt_inc, + "lookup_orders_inc", + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + " FROM merchant_orders" + " WHERE order_serial > $2" + " AND" + " creation_time > $3" + " AND ($4 OR " + " NOT CAST($5 AS BOOL))" /* unclaimed orders are never paid */ + " AND ($6 OR " + " NOT CAST($7 AS BOOL))"/* unclaimed orders are never refunded */ + " AND ($8 OR " + " NOT CAST($9 AS BOOL))" /* unclaimed orders are never wired */ + " AND" + " (order_serial NOT IN" + " (SELECT order_serial" + " FROM merchant_contract_terms))" /* only select unclaimed orders */ + " AND ($10 OR " + " ($11 = session_id))" + " AND ($12 OR " + " ($13 = fulfillment_url))" + " AND ( ($14::TEXT IS NULL) OR " + " (LOWER(contract_terms ->> 'summary') LIKE LOWER($14)) )" + " ORDER BY order_serial ASC" + " LIMIT $1)" + "UNION " /* union ensures elements are distinct! */ + "(SELECT" + " order_id" + ",order_serial" + ",creation_time" + " FROM merchant_contract_terms" + " WHERE order_serial > $2" + " AND" + " creation_time > $3" + " AND ($4 OR " + " (CAST($5 AS BOOL) = paid))" + " AND ($6 OR " + " (CAST($7 AS BOOL) = (order_serial IN" + " (SELECT order_serial " + " FROM merchant_refunds))))" + " AND ($8 OR" + " (CAST($9 AS BOOL) = wired))" + " AND ($10 OR " + " ($11 = session_id))" + " AND ($12 OR " + " ($13 = fulfillment_url))" + " AND ( ($14::TEXT IS NULL) OR " + " (LOWER(contract_terms ->> 'summary') LIKE LOWER($14)) )" + " ORDER BY order_serial ASC" + " LIMIT $1)" + " ORDER BY order_serial ASC" + " LIMIT $1"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - stmt, + (of->delta > 0) + ? stmt_inc + : stmt_dec, params, &lookup_orders_cb, &plc); diff --git a/src/backenddb/lookup_otp_devices.c b/src/backenddb/lookup_otp_devices.c @@ -106,23 +106,24 @@ TALER_MERCHANTDB_lookup_otp_devices ( .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_otp_devices", - "SELECT" - " otp_id" - ",otp_description" - " FROM merchant_otp_devices" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_otp_devices", + "SELECT" + " otp_id" + ",otp_description" + " FROM merchant_otp_devices"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_otp_devices", + stmt, params, &lookup_otp_devices_cb, &tlc); diff --git a/src/backenddb/lookup_pending_deposits.c b/src/backenddb/lookup_pending_deposits.c @@ -82,25 +82,25 @@ lookup_deposits_cb (void *cls, struct TALER_Amount deposit_fee; struct TALER_CoinSpendPublicKeyP coin_pub; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("deposit_serial", + GNUNET_PQ_result_spec_uint64 ("out_deposit_serial", &deposit_serial), - GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms", + GNUNET_PQ_result_spec_auto_from_type ("out_h_contract_terms", &h_contract_terms), - GNUNET_PQ_result_spec_auto_from_type ("merchant_priv", + GNUNET_PQ_result_spec_auto_from_type ("out_merchant_priv", &merchant_priv), - GNUNET_PQ_result_spec_string ("merchant_id", + GNUNET_PQ_result_spec_string ("out_merchant_id", &instance_id), - GNUNET_PQ_result_spec_absolute_time ("wire_transfer_deadline", + GNUNET_PQ_result_spec_absolute_time ("out_wire_transfer_deadline", &wire_deadline), - GNUNET_PQ_result_spec_absolute_time ("retry_time", + GNUNET_PQ_result_spec_absolute_time ("out_retry_time", &retry_time), - GNUNET_PQ_result_spec_auto_from_type ("h_wire", + GNUNET_PQ_result_spec_auto_from_type ("out_h_wire", &h_wire), - TALER_PQ_result_spec_amount_with_currency ("amount_with_fee", + TALER_PQ_result_spec_amount_with_currency ("out_amount_with_fee", &amount_with_fee), - TALER_PQ_result_spec_amount_with_currency ("deposit_fee", + TALER_PQ_result_spec_amount_with_currency ("out_deposit_fee", &deposit_fee), - GNUNET_PQ_result_spec_auto_from_type ("coin_pub", + GNUNET_PQ_result_spec_auto_from_type ("out_coin_pub", &coin_pub), GNUNET_PQ_result_spec_end }; @@ -159,35 +159,17 @@ TALER_MERCHANTDB_lookup_pending_deposits ( PREPARE (pg, "lookup_pending_deposits", "SELECT" - " md.deposit_serial" - ",mct.h_contract_terms" - ",mk.merchant_priv" - ",mi.merchant_id" - ",mdc.wire_transfer_deadline" - ",md.settlement_retry_time AS retry_time" - ",ma.h_wire" - ",md.amount_with_fee" - ",md.deposit_fee" - ",md.coin_pub" - " FROM merchant_deposits md" - " JOIN merchant_deposit_confirmations mdc" - " USING (deposit_confirmation_serial)" - " JOIN merchant_contract_terms mct" - " ON (mct.order_serial=mdc.order_serial)" - " JOIN merchant_accounts ma" - " ON (mdc.account_serial=ma.account_serial)" - " LEFT JOIN merchant_kyc kyc" - " ON (mdc.account_serial=kyc.account_serial)" - " JOIN merchant_instances mi" - " ON (mct.merchant_serial=mi.merchant_serial)" - " JOIN merchant_keys mk" - " ON (mi.merchant_serial=mk.merchant_serial)" - " WHERE (mdc.exchange_url=$1)" - " AND md.settlement_retry_needed" - " AND ($4 OR (md.settlement_retry_time < $2))" - " AND ( (kyc.kyc_ok IS NULL) OR kyc.kyc_ok)" - " ORDER BY md.settlement_retry_time ASC" - " LIMIT $3"); + " out_deposit_serial" + " ,out_h_contract_terms" + " ,out_merchant_priv" + " ,out_merchant_id" + " ,out_wire_transfer_deadline" + " ,out_retry_time" + " ,out_h_wire" + " ,out_amount_with_fee" + " ,out_deposit_fee" + " ,out_coin_pub" + " FROM merchant.lookup_pending_deposits($1, $2, $3, $4)"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "lookup_pending_deposits", params, diff --git a/src/backenddb/lookup_pending_webhooks.c b/src/backenddb/lookup_pending_webhooks.c @@ -72,22 +72,22 @@ lookup_pending_webhooks_cb (void *cls, char *header = NULL; char *body = NULL; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("webhook_pending_serial", + GNUNET_PQ_result_spec_uint64 ("out_webhook_pending_serial", &webhook_pending_serial), - GNUNET_PQ_result_spec_absolute_time ("next_attempt", + GNUNET_PQ_result_spec_absolute_time ("out_next_attempt", &next_attempt), - GNUNET_PQ_result_spec_uint32 ("retries", + GNUNET_PQ_result_spec_uint32 ("out_retries", &retries), - GNUNET_PQ_result_spec_string ("url", + GNUNET_PQ_result_spec_string ("out_url", &url), - GNUNET_PQ_result_spec_string ("http_method", + GNUNET_PQ_result_spec_string ("out_http_method", &http_method), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("header", + GNUNET_PQ_result_spec_string ("out_header", &header), NULL), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("body", + GNUNET_PQ_result_spec_string ("out_body", &body), NULL), GNUNET_PQ_result_spec_end @@ -138,17 +138,14 @@ TALER_MERCHANTDB_lookup_pending_webhooks ( PREPARE (pg, "lookup_pending_webhooks", "SELECT" - " webhook_pending_serial" - ",next_attempt" - ",retries" - ",url" - ",http_method" - ",header" - ",body" - " FROM merchant_pending_webhooks" - " WHERE next_attempt <= $1" - " ORDER BY next_attempt ASC" - ); + " out_webhook_pending_serial" + " ,out_next_attempt" + " ,out_retries" + " ,out_url" + " ,out_http_method" + " ,out_header" + " ,out_body" + " FROM merchant.lookup_pending_webhooks($1)"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "lookup_pending_webhooks", @@ -182,16 +179,14 @@ TALER_MERCHANTDB_lookup_future_webhook (struct TALER_MERCHANTDB_PostgresContext PREPARE (pg, "lookup_future_webhook", "SELECT" - " webhook_pending_serial" - ",next_attempt" - ",retries" - ",url" - ",http_method" - ",header" - ",body" - " FROM merchant_pending_webhooks" - " ORDER BY next_attempt ASC LIMIT 1" - ); + " out_webhook_pending_serial" + " ,out_next_attempt" + " ,out_retries" + " ,out_url" + " ,out_http_method" + " ,out_header" + " ,out_body" + " FROM merchant.lookup_future_webhook()"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "lookup_future_webhook", @@ -220,35 +215,35 @@ TALER_MERCHANTDB_lookup_all_webhooks (struct TALER_MERCHANTDB_PostgresContext *p }; uint64_t max_results64 = max_results; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&min_row), GNUNET_PQ_query_param_uint64 (&max_results64), GNUNET_PQ_query_param_end }; - enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_all_webhooks", - " SELECT" - " webhook_pending_serial" - ",next_attempt" - ",retries" - ",url" - ",http_method" - ",header" - ",body" - " FROM merchant_pending_webhooks" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND webhook_pending_serial > $2" - " ORDER BY webhook_pending_serial" - " ASC LIMIT $3"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_all_webhooks", + " SELECT" + " webhook_pending_serial AS out_webhook_pending_serial" + " ,next_attempt AS out_next_attempt" + " ,retries AS out_retries" + " ,url AS out_url" + " ,http_method AS out_http_method" + " ,header AS out_header" + " ,body AS out_body" + " FROM merchant_pending_webhooks" + " WHERE webhook_pending_serial > $1" + " ORDER BY webhook_pending_serial" + " ASC LIMIT $2"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_all_webhooks", + stmt, params, &lookup_pending_webhooks_cb, &pwlc); diff --git a/src/backenddb/lookup_product.c b/src/backenddb/lookup_product.c @@ -35,49 +35,50 @@ TALER_MERCHANTDB_lookup_product (struct TALER_MERCHANTDB_PostgresContext *pg, uint64_t **categories) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (product_id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; - PREPARE (pg, - "lookup_product", - "SELECT" - " mi.description" - ",mi.description_i18n::TEXT" - ",mi.product_name" - ",mi.unit" - ",mi.price_array" - ",mi.taxes::TEXT" - ",mi.total_stock" - ",mi.total_stock_frac" - ",mi.allow_fractional_quantity" - ",mi.fractional_precision_level" - ",mi.total_sold" - ",mi.total_sold_frac" - ",mi.total_lost" - ",mi.total_lost_frac" - ",mi.image" - ",mi.address::TEXT" - ",mi.next_restock" - ",mi.minimum_age" - ",mi.product_group_serial" - ",mi.money_pot_serial" - ",mi.price_is_net" - ",t.category_array AS categories" - " FROM merchant_inventory mi" - " JOIN merchant_instances inst" - " USING (merchant_serial)" - ",LATERAL (" - " SELECT ARRAY (" - " SELECT mpc.category_serial" - " FROM merchant_product_categories mpc" - " WHERE mpc.product_serial = mi.product_serial" - " ) AS category_array" - " ) t" - " WHERE inst.merchant_id=$1" - " AND mi.product_id=$2" - ); + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); + PREPARE_INSTANCE (pg, + stmt, + "lookup_product", + "SELECT" + " mi.description" + ",mi.description_i18n::TEXT" + ",mi.product_name" + ",mi.unit" + ",mi.price_array" + ",mi.taxes::TEXT" + ",mi.total_stock" + ",mi.total_stock_frac" + ",mi.allow_fractional_quantity" + ",mi.fractional_precision_level" + ",mi.total_sold" + ",mi.total_sold_frac" + ",mi.total_lost" + ",mi.total_lost_frac" + ",mi.image" + ",mi.address::TEXT" + ",mi.next_restock" + ",mi.minimum_age" + ",mi.product_group_serial" + ",mi.money_pot_serial" + ",mi.price_is_net" + ",t.category_array AS categories" + " FROM merchant_inventory mi" + ",LATERAL (" + " SELECT ARRAY (" + " SELECT mpc.category_serial" + " FROM merchant_product_categories mpc" + " WHERE mpc.product_serial = mi.product_serial" + " ) AS category_array" + " ) t" + " WHERE mi.product_id=$1" + ); if (NULL == pd) { struct GNUNET_PQ_ResultSpec rs_null[] = { @@ -86,7 +87,7 @@ TALER_MERCHANTDB_lookup_product (struct TALER_MERCHANTDB_PostgresContext *pg, check_connection (pg); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_product", + stmt, params, rs_null); } @@ -163,7 +164,7 @@ TALER_MERCHANTDB_lookup_product (struct TALER_MERCHANTDB_PostgresContext *pg, pd->product_group_id = 0; pd->money_pot_id = 0; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_product", + stmt, params, rs); pd->product_name = my_name; diff --git a/src/backenddb/lookup_product_image.c b/src/backenddb/lookup_product_image.c @@ -33,22 +33,23 @@ TALER_MERCHANTDB_lookup_product_image_by_hash ( char **image) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (image_hash), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE ( + PREPARE_INSTANCE ( pg, + stmt, "lookup_product_image_by_hash", "SELECT" " mi.image" " FROM merchant_inventory mi" - " JOIN merchant_instances inst" - " USING (merchant_serial)" - " WHERE inst.merchant_id=$1" - " AND mi.image_hash=$2"); + " WHERE mi.image_hash=$1"); *image = NULL; { @@ -60,7 +61,7 @@ TALER_MERCHANTDB_lookup_product_image_by_hash ( return GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, - "lookup_product_image_by_hash", + stmt, params, rs); } diff --git a/src/backenddb/lookup_products.c b/src/backenddb/lookup_products.c @@ -112,7 +112,6 @@ TALER_MERCHANTDB_lookup_products ( .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&offset), NULL == category_filter ? GNUNET_PQ_query_param_null () @@ -128,65 +127,64 @@ TALER_MERCHANTDB_lookup_products ( GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt_asc[PG_PREP_INSTANCE_NAME_MAX]; + char stmt_desc[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_products_asc", - "SELECT" - " product_id" - " ,product_serial" - " FROM merchant_inventory" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND product_serial > $2" - " AND ( ($3::TEXT IS NULL) OR" - " (product_serial IN" - " (SELECT product_serial" - " FROM merchant_product_categories" - " WHERE category_serial IN" - " (SELECT category_serial" - " FROM merchant_categories" - " WHERE category_name LIKE LOWER($3)))) )" - " AND ( (0 = $7::INT8) OR" - " (product_group_serial = $7) )" - " AND ( ($4::TEXT IS NULL) OR" - " (product_name LIKE LOWER($4)) )" - " AND ( ($5::TEXT IS NULL) OR" - " (description LIKE LOWER($5)) )" - " ORDER BY product_serial ASC" - " LIMIT $6"); - PREPARE (pg, - "lookup_products_desc", - "SELECT" - " product_id" - " ,product_serial" - " FROM merchant_inventory" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND product_serial < $2" - " AND ( ($3::TEXT IS NULL) OR" - " (product_serial IN" - " (SELECT product_serial" - " FROM merchant_product_categories" - " WHERE category_serial IN" - " (SELECT category_serial" - " FROM merchant_categories" - " WHERE category_name LIKE LOWER($3)))) )" - " AND ( (0 = $7::INT8) OR" - " (product_group_serial = $7) )" - " AND ( ($4::TEXT IS NULL) OR" - " (product_name LIKE LOWER($4)) )" - " AND ( ($5::TEXT IS NULL) OR" - " (description LIKE LOWER($5)) )" - " ORDER BY product_serial DESC" - " LIMIT $6"); + PREPARE_INSTANCE (pg, + stmt_asc, + "lookup_products_asc", + "SELECT" + " product_id" + " ,product_serial" + " FROM merchant_inventory" + " WHERE product_serial > $1" + " AND ( ($2::TEXT IS NULL) OR" + " (product_serial IN" + " (SELECT product_serial" + " FROM merchant_product_categories" + " WHERE category_serial IN" + " (SELECT category_serial" + " FROM merchant_categories" + " WHERE category_name LIKE LOWER($2)))) )" + " AND ( (0 = $6::INT8) OR" + " (product_group_serial = $6) )" + " AND ( ($3::TEXT IS NULL) OR" + " (product_name LIKE LOWER($3)) )" + " AND ( ($4::TEXT IS NULL) OR" + " (description LIKE LOWER($4)) )" + " ORDER BY product_serial ASC" + " LIMIT $5"); + PREPARE_INSTANCE (pg, + stmt_desc, + "lookup_products_desc", + "SELECT" + " product_id" + " ,product_serial" + " FROM merchant_inventory" + " WHERE product_serial < $1" + " AND ( ($2::TEXT IS NULL) OR" + " (product_serial IN" + " (SELECT product_serial" + " FROM merchant_product_categories" + " WHERE category_serial IN" + " (SELECT category_serial" + " FROM merchant_categories" + " WHERE category_name LIKE LOWER($2)))) )" + " AND ( (0 = $6::INT8) OR" + " (product_group_serial = $6) )" + " AND ( ($3::TEXT IS NULL) OR" + " (product_name LIKE LOWER($3)) )" + " AND ( ($4::TEXT IS NULL) OR" + " (description LIKE LOWER($4)) )" + " ORDER BY product_serial DESC" + " LIMIT $5"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - (limit > 0) - ? "lookup_products_asc" - : "lookup_products_desc", + (limit > 0) ? stmt_asc : stmt_desc, params, &lookup_products_cb, &plc); diff --git a/src/backenddb/lookup_reconciliation_details.c b/src/backenddb/lookup_reconciliation_details.c @@ -139,36 +139,37 @@ TALER_MERCHANTDB_lookup_reconciliation_details ( .pg = pg }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&expected_incoming_serial), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_reconciliation_details", - "SELECT" - " mct.order_id" - ",SUM((etc.exchange_deposit_value).val)::INT8 AS deposit_value_sum" - ",SUM((etc.exchange_deposit_value).frac)::INT8 AS deposit_frac_sum" - ",SUM((etc.exchange_deposit_fee).val)::INT8 AS fee_value_sum" - ",SUM((etc.exchange_deposit_fee).frac)::INT8 AS fee_frac_sum" - ",(etc.exchange_deposit_value).curr AS currency" - " FROM merchant_expected_transfer_to_coin etc" - " JOIN merchant_deposits md" - " USING (deposit_serial)" - " JOIN merchant_deposit_confirmations mdc" - " USING (deposit_confirmation_serial)" - " JOIN merchant_contract_terms mct" - " USING (order_serial)" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " WHERE expected_credit_serial=$2" - " AND mi.merchant_id=$1" - " GROUP BY mct.order_id, (etc.exchange_deposit_value).curr;"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_reconciliation_details", + "SELECT" + " mct.order_id" + ",SUM((etc.exchange_deposit_value).val)::INT8 AS deposit_value_sum" + ",SUM((etc.exchange_deposit_value).frac)::INT8 AS deposit_frac_sum" + ",SUM((etc.exchange_deposit_fee).val)::INT8 AS fee_value_sum" + ",SUM((etc.exchange_deposit_fee).frac)::INT8 AS fee_frac_sum" + ",(etc.exchange_deposit_value).curr AS currency" + " FROM merchant_expected_transfer_to_coin etc" + " JOIN merchant_deposits md" + " USING (deposit_serial)" + " JOIN merchant_deposit_confirmations mdc" + " USING (deposit_confirmation_serial)" + " JOIN merchant_contract_terms mct" + " USING (order_serial)" + " WHERE expected_credit_serial=$1" + " GROUP BY mct.order_id, (etc.exchange_deposit_value).curr;"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_reconciliation_details", + stmt, params, &reconciliation_cb, &lic); diff --git a/src/backenddb/lookup_refund_proof.c b/src/backenddb/lookup_refund_proof.c @@ -43,20 +43,24 @@ TALER_MERCHANTDB_lookup_refund_proof (struct TALER_MERCHANTDB_PostgresContext *p GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "lookup_refund_proof", - "SELECT" - " merchant_exchange_signing_keys.exchange_pub" - ",exchange_sig" - " FROM merchant_refund_proofs" - " JOIN merchant_exchange_signing_keys" - " USING (signkey_serial)" - " WHERE" - " refund_serial=$1"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_refund_proof", + "SELECT" + " merchant.merchant_exchange_signing_keys.exchange_pub" + ",exchange_sig" + " FROM merchant_refund_proofs" + " JOIN merchant.merchant_exchange_signing_keys" + " USING (signkey_serial)" + " WHERE" + " refund_serial=$1"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_refund_proof", + stmt, params, rs); } diff --git a/src/backenddb/lookup_refunds.c b/src/backenddb/lookup_refunds.c @@ -105,7 +105,6 @@ TALER_MERCHANTDB_lookup_refunds (struct TALER_MERCHANTDB_PostgresContext *pg, void *rc_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_end }; @@ -115,28 +114,29 @@ TALER_MERCHANTDB_lookup_refunds (struct TALER_MERCHANTDB_PostgresContext *pg, .pg = pg }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); /* no preflight check here, run in transaction by caller! */ TALER_LOG_DEBUG ("Looking for refund of h_contract_terms %s at `%s'\n", GNUNET_h2s (&h_contract_terms->hash), instance_id); check_connection (pg); - PREPARE (pg, - "lookup_refunds", - "SELECT" - " coin_pub" - ",refund_amount" - " FROM merchant_refunds" - " WHERE order_serial=" - " (SELECT order_serial" - " FROM merchant_contract_terms" - " WHERE h_contract_terms=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1))"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_refunds", + "SELECT" + " coin_pub" + ",refund_amount" + " FROM merchant_refunds" + " WHERE order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " WHERE h_contract_terms=$1)"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_refunds", + stmt, params, &lookup_refunds_cb, &lrc); diff --git a/src/backenddb/lookup_refunds_detailed.c b/src/backenddb/lookup_refunds_detailed.c @@ -131,7 +131,6 @@ TALER_MERCHANTDB_lookup_refunds_detailed ( void *rc_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_end }; @@ -141,42 +140,43 @@ TALER_MERCHANTDB_lookup_refunds_detailed ( .pg = pg }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); /* no preflight check here, run in transaction by caller! */ TALER_LOG_DEBUG ("Looking for refund %s + %s\n", GNUNET_h2s (&h_contract_terms->hash), instance_id); check_connection (pg); - PREPARE (pg, - "lookup_refunds_detailed", - "SELECT" - " ref.refund_serial" - ",ref.refund_timestamp" - ",dep.coin_pub" - ",mcon.exchange_url" - ",ref.rtransaction_id" - ",ref.reason" - ",ref.refund_amount" - ",merchant_refund_proofs.exchange_sig IS NULL AS pending" - " FROM merchant_deposit_confirmations mcon" - " JOIN merchant_deposits dep" - " USING (deposit_confirmation_serial)" - " JOIN merchant_refunds ref" - " USING (order_serial, coin_pub)" - " LEFT JOIN merchant_refund_proofs" - " USING (refund_serial)" - " WHERE mcon.order_serial=" - " (SELECT order_serial" - " FROM merchant_contract_terms" - " WHERE h_contract_terms=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1))"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_refunds_detailed", + "SELECT" + " ref.refund_serial" + ",ref.refund_timestamp" + ",dep.coin_pub" + ",mcon.exchange_url" + ",ref.rtransaction_id" + ",ref.reason" + ",ref.refund_amount" + ",merchant_refund_proofs.exchange_sig IS NULL AS pending" + " FROM merchant_deposit_confirmations mcon" + " JOIN merchant_deposits dep" + " USING (deposit_confirmation_serial)" + " JOIN merchant_refunds ref" + " USING (order_serial, coin_pub)" + " LEFT JOIN merchant_refund_proofs" + " USING (refund_serial)" + " WHERE mcon.order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " WHERE h_contract_terms=$1)"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_refunds_detailed", + stmt, params, &lookup_refunds_detailed_cb, &lrdc); diff --git a/src/backenddb/lookup_reports_pending.c b/src/backenddb/lookup_reports_pending.c @@ -77,27 +77,27 @@ select_pending_reports_cb (void *cls, struct GNUNET_TIME_Absolute next_transmission; bool one_shot; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_string ("merchant_id", + GNUNET_PQ_result_spec_string ("out_merchant_id", &instance_id), - GNUNET_PQ_result_spec_uint64 ("report_serial", + GNUNET_PQ_result_spec_uint64 ("out_report_serial", &report_serial), - GNUNET_PQ_result_spec_string ("report_program_section", + GNUNET_PQ_result_spec_string ("out_report_program_section", &report_program_section), - GNUNET_PQ_result_spec_string ("report_description", + GNUNET_PQ_result_spec_string ("out_report_description", &report_description), - GNUNET_PQ_result_spec_string ("mime_type", + GNUNET_PQ_result_spec_string ("out_mime_type", &mime_type), - GNUNET_PQ_result_spec_auto_from_type ("report_token", + GNUNET_PQ_result_spec_auto_from_type ("out_report_token", &report_token), - GNUNET_PQ_result_spec_string ("target_address", + GNUNET_PQ_result_spec_string ("out_target_address", &target_address), - GNUNET_PQ_result_spec_relative_time ("frequency", + GNUNET_PQ_result_spec_relative_time ("out_frequency", &frequency), - GNUNET_PQ_result_spec_relative_time ("frequency_shift", + GNUNET_PQ_result_spec_relative_time ("out_frequency_shift", &frequency_shift), - GNUNET_PQ_result_spec_absolute_time ("next_transmission", + GNUNET_PQ_result_spec_absolute_time ("out_next_transmission", &next_transmission), - GNUNET_PQ_result_spec_bool ("one_shot_hidden", + GNUNET_PQ_result_spec_bool ("out_one_shot_hidden", &one_shot), GNUNET_PQ_result_spec_end }; @@ -149,22 +149,18 @@ TALER_MERCHANTDB_lookup_reports_pending ( PREPARE (pg, "lookup_reports_pending", "SELECT" - " mi.merchant_id" - " ,mr.report_serial" - " ,mr.report_program_section" - " ,mr.report_description" - " ,mr.mime_type" - " ,mr.report_token" - " ,mr.target_address" - " ,mr.frequency" - " ,mr.frequency_shift" - " ,mr.next_transmission" - " ,mr.one_shot_hidden" - " FROM merchant_reports mr" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " ORDER BY next_transmission ASC" - " LIMIT 1;"); + " out_merchant_id" + " ,out_report_serial" + " ,out_report_program_section" + " ,out_report_description" + " ,out_mime_type" + " ,out_report_token" + " ,out_target_address" + " ,out_frequency" + " ,out_frequency_shift" + " ,out_next_transmission" + " ,out_one_shot_hidden" + " FROM merchant.lookup_reports_pending()"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, "lookup_reports_pending", diff --git a/src/backenddb/lookup_spent_tokens_by_order.c b/src/backenddb/lookup_spent_tokens_by_order.c @@ -133,25 +133,29 @@ TALER_MERCHANTDB_lookup_spent_tokens_by_order ( }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "lookup_spent_tokens_by_order", - "SELECT" - " spent_token_serial" - ",h_contract_terms" - ",h_pub" - ",token_pub" - ",token_sig" - ",blind_sig" - " FROM merchant_used_tokens" - " JOIN merchant_contract_terms" - " USING (h_contract_terms)" - " JOIN merchant_token_family_keys" - " USING (token_family_key_serial)" - " WHERE order_serial=$1"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_spent_tokens_by_order", + "SELECT" + " spent_token_serial" + ",h_contract_terms" + ",h_pub" + ",token_pub" + ",token_sig" + ",blind_sig" + " FROM merchant_used_tokens" + " JOIN merchant_contract_terms" + " USING (h_contract_terms)" + " JOIN merchant_token_family_keys" + " USING (token_family_key_serial)" + " WHERE order_serial=$1"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_spent_tokens_by_order", + stmt, params, &lookup_spent_tokens_by_order_cb, &ctx); diff --git a/src/backenddb/lookup_statistics_amount_by_bucket.c b/src/backenddb/lookup_statistics_amount_by_bucket.c @@ -186,33 +186,34 @@ TALER_MERCHANTDB_lookup_statistics_amount_by_bucket ( .pg = pg, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (slug), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_statistics_amount_by_bucket", - "SELECT" - " bmeta_serial_id" - ",description" - ",bucket_start" - ",bucket_range::TEXT" - ",merchant_statistics_bucket_end(bucket_start, bucket_range) AS bucket_end" - ",(cumulative_value,cumulative_frac,curr)::taler_amount_currency AS cumulative_amount" - " FROM merchant_statistic_bucket_amount" - " JOIN merchant_statistic_bucket_meta" - " USING (bmeta_serial_id)" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND merchant_statistic_bucket_meta.slug=$2" - " AND merchant_statistic_bucket_meta.stype='amount'"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_statistics_amount_by_bucket", + "SELECT" + " bmeta_serial_id" + ",description" + ",bucket_start" + ",bucket_range::TEXT" + ",merchant_statistics_bucket_end(bucket_start, bucket_range) AS bucket_end" + ",(cumulative_value,cumulative_frac,curr)::taler_amount_currency AS cumulative_amount" + " FROM merchant_statistic_bucket_amount" + " JOIN merchant_statistic_bucket_meta" + " USING (bmeta_serial_id)" + " WHERE merchant_statistic_bucket_meta.slug=$1" + " AND merchant_statistic_bucket_meta.stype='amount'"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_statistics_amount_by_bucket", + stmt, params, &lookup_statistics_amount_by_bucket_cb, &context); diff --git a/src/backenddb/lookup_statistics_amount_by_bucket2.c b/src/backenddb/lookup_statistics_amount_by_bucket2.c @@ -123,37 +123,38 @@ TALER_MERCHANTDB_lookup_statistics_amount_by_bucket2 ( .pg = pg, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (slug), GNUNET_PQ_query_param_string (granularity), GNUNET_PQ_query_param_uint64 (&counter), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_statistics_amount_by_bucket2", - "SELECT" - " msba.bucket_start" - ",ARRAY_AGG (" - " ROW(msba.cumulative_value::INT8, msba.cumulative_frac::INT4, msba.curr)::taler_amount_currency" - " ) AS cumulative_amounts" - " FROM merchant_statistic_bucket_meta msbm" - " JOIN merchant_statistic_bucket_amount msba" - " USING (bmeta_serial_id)" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " WHERE mi.merchant_id=$1" - " AND msbm.slug=$2" - " AND msba.bucket_range=$3::TEXT::statistic_range" - " AND msbm.stype='amount'" - " GROUP BY msba.bucket_start" - " ORDER BY msba.bucket_start DESC" - " LIMIT $4;"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_statistics_amount_by_bucket2", + "SELECT" + " msba.bucket_start" + ",ARRAY_AGG (" + " ROW(msba.cumulative_value::INT8, msba.cumulative_frac::INT4, msba.curr)::taler_amount_currency" + " ) AS cumulative_amounts" + " FROM merchant_statistic_bucket_meta msbm" + " JOIN merchant_statistic_bucket_amount msba" + " USING (bmeta_serial_id)" + " WHERE msbm.slug=$1" + " AND msba.bucket_range=$2::TEXT::statistic_range" + " AND msbm.stype='amount'" + " GROUP BY msba.bucket_start" + " ORDER BY msba.bucket_start DESC" + " LIMIT $3;"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_statistics_amount_by_bucket2", + stmt, params, &lookup_statistics_amount_by_bucket_cb2, &context); diff --git a/src/backenddb/lookup_statistics_amount_by_interval.c b/src/backenddb/lookup_statistics_amount_by_interval.c @@ -198,21 +198,26 @@ TALER_MERCHANTDB_lookup_statistics_amount_by_interval ( GNUNET_PQ_query_param_end }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (slug), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt_desc[PG_PREP_INSTANCE_NAME_MAX]; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_statistics_amount_by_interval_description", - "SELECT description" - " FROM merchant_statistic_interval_meta" - " WHERE slug=$1 LIMIT 1"); + PREPARE_INSTANCE (pg, + stmt_desc, + "lookup_statistics_amount_by_interval_description", + "SELECT description" + " FROM merchant_statistic_interval_meta" + " WHERE slug=$1 LIMIT 1"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_statistics_amount_by_interval_description", + stmt_desc, descParams, &lookup_statistics_amount_by_interval_desc_cb, &context); @@ -222,13 +227,14 @@ TALER_MERCHANTDB_lookup_statistics_amount_by_interval ( GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } - PREPARE (pg, - "lookup_statistics_amount_by_interval", - "SELECT *" - " FROM merchant_statistic_interval_amount_get($2,$1)"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_statistics_amount_by_interval", + "SELECT *" + " FROM merchant_statistic_interval_amount_get($1)"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_statistics_amount_by_interval", + stmt, params, &lookup_statistics_amount_by_interval_cb, &context); diff --git a/src/backenddb/lookup_statistics_counter_by_bucket.c b/src/backenddb/lookup_statistics_counter_by_bucket.c @@ -125,32 +125,33 @@ TALER_MERCHANTDB_lookup_statistics_counter_by_bucket ( .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (slug), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_statistics_counter_by_bucket", - "SELECT" - " description" - ",bucket_start" - ",bucket_range::TEXT" - ",merchant_statistics_bucket_end(bucket_start, bucket_range) AS bucket_end" - ",cumulative_number" - " FROM merchant_statistic_bucket_counter" - " JOIN merchant_statistic_bucket_meta" - " USING (bmeta_serial_id)" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND merchant_statistic_bucket_meta.slug=$2" - " AND merchant_statistic_bucket_meta.stype = 'number'"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_statistics_counter_by_bucket", + "SELECT" + " description" + ",bucket_start" + ",bucket_range::TEXT" + ",merchant_statistics_bucket_end(bucket_start, bucket_range) AS bucket_end" + ",cumulative_number" + " FROM merchant_statistic_bucket_counter" + " JOIN merchant_statistic_bucket_meta" + " USING (bmeta_serial_id)" + " WHERE merchant_statistic_bucket_meta.slug=$1" + " AND merchant_statistic_bucket_meta.stype = 'number'"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_statistics_counter_by_bucket", + stmt, params, &lookup_statistics_counter_by_bucket_cb, &context); diff --git a/src/backenddb/lookup_statistics_counter_by_bucket2.c b/src/backenddb/lookup_statistics_counter_by_bucket2.c @@ -147,36 +147,37 @@ TALER_MERCHANTDB_lookup_statistics_counter_by_bucket2 ( .pg = pg }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (prefix), GNUNET_PQ_query_param_string (granularity), GNUNET_PQ_query_param_uint64 (&counter), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_statistics_counter_by_bucket2", - "SELECT" - " msbc.bucket_start" - ",ARRAY_AGG(msbm.slug) AS slugs" - ",ARRAY_AGG(msbc.cumulative_number) AS counters" - " FROM merchant_statistic_bucket_counter msbc" - " JOIN merchant_statistic_bucket_meta msbm" - " USING (bmeta_serial_id)" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " WHERE mi.merchant_id=$1" - " AND msbm.slug LIKE $2" - " AND msbc.bucket_range=$3::TEXT::statistic_range" - " AND msbm.stype = 'number'" - " GROUP BY msbc.bucket_start" - " ORDER BY msbc.bucket_start DESC" - " LIMIT $4"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_statistics_counter_by_bucket2", + "SELECT" + " msbc.bucket_start" + ",ARRAY_AGG(msbm.slug) AS slugs" + ",ARRAY_AGG(msbc.cumulative_number) AS counters" + " FROM merchant_statistic_bucket_counter msbc" + " JOIN merchant_statistic_bucket_meta msbm" + " USING (bmeta_serial_id)" + " WHERE msbm.slug LIKE $1" + " AND msbc.bucket_range=$2::TEXT::statistic_range" + " AND msbm.stype = 'number'" + " GROUP BY msbc.bucket_start" + " ORDER BY msbc.bucket_start DESC" + " LIMIT $3"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_statistics_counter_by_bucket2", + stmt, params, &lookup_statistics_counter_by_bucket_cb2, &context); diff --git a/src/backenddb/lookup_statistics_counter_by_interval.c b/src/backenddb/lookup_statistics_counter_by_interval.c @@ -164,21 +164,26 @@ TALER_MERCHANTDB_lookup_statistics_counter_by_interval ( GNUNET_PQ_query_param_end }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (slug), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt_desc[PG_PREP_INSTANCE_NAME_MAX]; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_statistics_counter_by_interval_description", - "SELECT description" - " FROM merchant_statistic_interval_meta" - " WHERE slug=$1 LIMIT 1"); + PREPARE_INSTANCE (pg, + stmt_desc, + "lookup_statistics_counter_by_interval_description", + "SELECT description" + " FROM merchant_statistic_interval_meta" + " WHERE slug=$1 LIMIT 1"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_statistics_counter_by_interval_description", + stmt_desc, descParams, &lookup_statistics_counter_by_interval_desc_cb, &context); @@ -188,13 +193,14 @@ TALER_MERCHANTDB_lookup_statistics_counter_by_interval ( GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } - PREPARE (pg, - "lookup_statistics_counter_by_interval", - "SELECT *" - " FROM merchant_statistic_interval_number_get($2,$1)"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_statistics_counter_by_interval", + "SELECT *" + " FROM merchant_statistic_interval_number_get($1)"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_statistics_counter_by_interval", + stmt, params, &lookup_statistics_counter_by_interval_cb, &context); diff --git a/src/backenddb/lookup_template.c b/src/backenddb/lookup_template.c @@ -43,27 +43,28 @@ TALER_MERCHANTDB_lookup_template (struct TALER_MERCHANTDB_PostgresContext *pg, struct TALER_MERCHANTDB_TemplateDetails *td) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (template_id), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_template", - "SELECT" - " mt.template_description" - ",mod.otp_id" - ",mt.template_contract::TEXT" - ",mt.editable_defaults::TEXT" - " FROM merchant_template mt" - " JOIN merchant_instances mi" - " ON (mi.merchant_serial = mt.merchant_serial)" - " LEFT JOIN merchant_otp_devices mod" - " ON (mod.otp_serial = mt.otp_device_id)" - " WHERE mi.merchant_id=$1" - " AND mt.template_id=$2"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_template", + "SELECT" + " mt.template_description" + ",mod.otp_id" + ",mt.template_contract::TEXT" + ",mt.editable_defaults::TEXT" + " FROM merchant_template mt" + " LEFT JOIN merchant_otp_devices mod" + " ON (mod.otp_serial = mt.otp_device_id)" + " WHERE mt.template_id=$1"); if (NULL == td) { struct GNUNET_PQ_ResultSpec rs_null[] = { @@ -71,7 +72,7 @@ TALER_MERCHANTDB_lookup_template (struct TALER_MERCHANTDB_PostgresContext *pg, }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_template", + stmt, params, rs_null); GNUNET_PQ_cleanup_query_params_closures (params); @@ -99,7 +100,7 @@ TALER_MERCHANTDB_lookup_template (struct TALER_MERCHANTDB_PostgresContext *pg, 0, sizeof (*td)); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_template", + stmt, params, rs); GNUNET_PQ_cleanup_query_params_closures (params); diff --git a/src/backenddb/lookup_templates.c b/src/backenddb/lookup_templates.c @@ -105,23 +105,24 @@ TALER_MERCHANTDB_lookup_templates (struct TALER_MERCHANTDB_PostgresContext *pg, .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_templates", - "SELECT" - " template_id" - ",template_description" - " FROM merchant_template" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_templates", + "SELECT" + " template_id" + ",template_description" + " FROM merchant_template"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_templates", + stmt, params, &lookup_templates_cb, &tlc); diff --git a/src/backenddb/lookup_token_families.c b/src/backenddb/lookup_token_families.c @@ -130,28 +130,29 @@ TALER_MERCHANTDB_lookup_token_families ( .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_token_families", - "SELECT" - " slug" - ",name" - ",description" - ",description_i18n::TEXT" - ",valid_after" - ",valid_before" - ",kind" - " FROM merchant_token_families" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_token_families", + "SELECT" + " slug" + ",name" + ",description" + ",description_i18n::TEXT" + ",valid_after" + ",valid_before" + ",kind" + " FROM merchant_token_families"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_token_families", + stmt, params, &lookup_token_families_cb, &context); diff --git a/src/backenddb/lookup_token_family.c b/src/backenddb/lookup_token_family.c @@ -33,20 +33,43 @@ TALER_MERCHANTDB_lookup_token_family (struct TALER_MERCHANTDB_PostgresContext *p struct TALER_MERCHANTDB_TokenFamilyDetails *details) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (token_family_slug), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); + check_connection (pg); + PREPARE_INSTANCE (pg, + stmt, + "lookup_token_family", + "SELECT" + " slug" + ",name" + ",cipher_choice" + ",description" + ",description_i18n::TEXT" + ",extra_data::TEXT" + ",valid_after" + ",valid_before" + ",duration" + ",validity_granularity" + ",start_offset" + ",kind" + ",issued" + ",used" + " FROM merchant_token_families" + " WHERE slug=$1"); if (NULL == details) { struct GNUNET_PQ_ResultSpec rs_null[] = { GNUNET_PQ_result_spec_end }; - check_connection (pg); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_token_family", + stmt, params, rs_null); } @@ -88,34 +111,11 @@ TALER_MERCHANTDB_lookup_token_family (struct TALER_MERCHANTDB_PostgresContext *p }; enum GNUNET_DB_QueryStatus qs; - check_connection (pg); - PREPARE (pg, - "lookup_token_family", - "SELECT" - " slug" - ",name" - ",cipher_choice" - ",description" - ",description_i18n::TEXT" - ",extra_data::TEXT" - ",valid_after" - ",valid_before" - ",duration" - ",validity_granularity" - ",start_offset" - ",kind" - ",issued" - ",used" - " FROM merchant_token_families" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND merchant_token_families.slug=$2"); memset (details, 0, sizeof (*details)); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_token_family", + stmt, params, rs); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) diff --git a/src/backenddb/lookup_token_family_key.c b/src/backenddb/lookup_token_family_key.c @@ -39,48 +39,49 @@ TALER_MERCHANTDB_lookup_token_family_key ( struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (token_family_slug), GNUNET_PQ_query_param_timestamp (&valid_at), GNUNET_PQ_query_param_timestamp (&sign_until), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_token_family_key", - "SELECT" - " h_pub" - ",pub" - ",priv" - ",cipher_choice" - ",mtfk.signature_validity_start" - ",mtfk.signature_validity_end" - ",mtfk.private_key_deleted_at" - ",slug" - ",name" - ",description" - ",description_i18n::TEXT" - ",mtf.valid_after" - ",mtf.valid_before" - ",duration" - ",validity_granularity" - ",start_offset" - ",kind" - ",issued" - ",used" - " FROM merchant_token_families mtf" - " LEFT JOIN merchant_token_family_keys mtfk" - " USING (token_family_serial)" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " WHERE mi.merchant_id=$1" - " AND slug=$2" - " AND COALESCE ($3 >= mtfk.signature_validity_start, TRUE)" - " AND COALESCE ($3 <= mtfk.signature_validity_end, TRUE)" - " AND COALESCE ($4 <= mtfk.private_key_deleted_at, TRUE)" - " ORDER BY mtfk.signature_validity_start ASC" - " LIMIT 1"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_token_family_key", + "SELECT" + " h_pub" + ",pub" + ",priv" + ",cipher_choice" + ",mtfk.signature_validity_start" + ",mtfk.signature_validity_end" + ",mtfk.private_key_deleted_at" + ",slug" + ",name" + ",description" + ",description_i18n::TEXT" + ",mtf.valid_after" + ",mtf.valid_before" + ",duration" + ",validity_granularity" + ",start_offset" + ",kind" + ",issued" + ",used" + " FROM merchant_token_families mtf" + " LEFT JOIN merchant_token_family_keys mtfk" + " USING (token_family_serial)" + " WHERE slug=$1" + " AND COALESCE ($2 >= mtfk.signature_validity_start, TRUE)" + " AND COALESCE ($2 <= mtfk.signature_validity_end, TRUE)" + " AND COALESCE ($3 <= mtfk.private_key_deleted_at, TRUE)" + " ORDER BY mtfk.signature_validity_start ASC" + " LIMIT 1"); if (NULL == details) { @@ -89,7 +90,7 @@ TALER_MERCHANTDB_lookup_token_family_key ( }; return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_token_family_key", + stmt, params, rs_null); } @@ -152,7 +153,7 @@ TALER_MERCHANTDB_lookup_token_family_key ( 0, sizeof (*details)); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_token_family_key", + stmt, params, rs); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) diff --git a/src/backenddb/lookup_token_family_keys.c b/src/backenddb/lookup_token_family_keys.c @@ -166,7 +166,6 @@ TALER_MERCHANTDB_lookup_token_family_keys ( void *cb_cls) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (token_family_slug), GNUNET_PQ_query_param_timestamp (&start_time), GNUNET_PQ_query_param_timestamp (&end_time), @@ -178,42 +177,44 @@ TALER_MERCHANTDB_lookup_token_family_keys ( .cb_cls = cb_cls }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_token_family_keys", - "SELECT" - " h_pub" - ",mtfk.pub" - ",mtfk.priv" - ",cipher_choice" - ",mtfk.signature_validity_start" - ",mtfk.signature_validity_end" - ",mtfk.private_key_deleted_at" - ",slug" - ",name" - ",description" - ",description_i18n::TEXT" - ",mtf.valid_after" - ",mtf.valid_before" - ",duration" - ",validity_granularity" - ",start_offset" - ",kind" - ",issued" - ",used" - " FROM merchant_token_families mtf" - " LEFT JOIN merchant_token_family_keys mtfk" - " USING (token_family_serial)" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " WHERE mi.merchant_id=$1" - " AND slug=$2" - " AND COALESCE ($3 <= mtfk.signature_validity_end, TRUE)" - " AND COALESCE ($4 >= mtfk.signature_validity_start, TRUE);"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_token_family_keys", + "SELECT" + " h_pub" + ",mtfk.pub" + ",mtfk.priv" + ",cipher_choice" + ",mtfk.signature_validity_start" + ",mtfk.signature_validity_end" + ",mtfk.private_key_deleted_at" + ",slug" + ",name" + ",description" + ",description_i18n::TEXT" + ",mtf.valid_after" + ",mtf.valid_before" + ",duration" + ",validity_granularity" + ",start_offset" + ",kind" + ",issued" + ",used" + " FROM merchant_token_families mtf" + " LEFT JOIN merchant_token_family_keys mtfk" + " USING (token_family_serial)" + " WHERE slug=$1" + " AND COALESCE ($2 <= mtfk.signature_validity_end, TRUE)" + " AND COALESCE ($3 >= mtfk.signature_validity_start, TRUE);"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_token_family_keys", + stmt, params, &lookup_token_keys_cb, &ctx); diff --git a/src/backenddb/lookup_transfer_details.c b/src/backenddb/lookup_transfer_details.c @@ -123,30 +123,34 @@ TALER_MERCHANTDB_lookup_transfer_details ( }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "lookup_transfer_details", - "SELECT" - " mterm.h_contract_terms" - ",mtcoin.offset_in_exchange_list" - ",dep.coin_pub" - ",mtcoin.exchange_deposit_value" - ",mtcoin.exchange_deposit_fee" - " FROM merchant_expected_transfer_to_coin mtcoin" - " JOIN merchant_deposits dep" - " USING (deposit_serial)" - " JOIN merchant_deposit_confirmations mcon" - " USING (deposit_confirmation_serial)" - " JOIN merchant_contract_terms mterm" - " USING (order_serial)" - " JOIN merchant_expected_transfers met" - " USING (expected_credit_serial)" - " WHERE met.wtid=$2" - " AND met.exchange_url=$1"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_transfer_details", + "SELECT" + " mterm.h_contract_terms" + ",mtcoin.offset_in_exchange_list" + ",dep.coin_pub" + ",mtcoin.exchange_deposit_value" + ",mtcoin.exchange_deposit_fee" + " FROM merchant_expected_transfer_to_coin mtcoin" + " JOIN merchant_deposits dep" + " USING (deposit_serial)" + " JOIN merchant_deposit_confirmations mcon" + " USING (deposit_confirmation_serial)" + " JOIN merchant_contract_terms mterm" + " USING (order_serial)" + " JOIN merchant_expected_transfers met" + " USING (expected_credit_serial)" + " WHERE met.wtid=$2" + " AND met.exchange_url=$1"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_transfer_details", + stmt, params, &lookup_transfer_details_cb, &ltdc); diff --git a/src/backenddb/lookup_transfer_details_by_order.c b/src/backenddb/lookup_transfer_details_by_order.c @@ -201,36 +201,39 @@ TALER_MERCHANTDB_lookup_transfer_details_by_order ( }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "lookup_transfer_details_by_order", - "SELECT" - " md.deposit_serial" - ",mcon.exchange_url" - ",met.wtid" - ",mtc.exchange_deposit_value" - ",mtc.exchange_deposit_fee" - ",mcon.deposit_timestamp" - ",met.confirmed" - ",met.expected_credit_serial" - " FROM merchant_expected_transfer_to_coin mtc" - " JOIN merchant_deposits md" - " USING (deposit_serial)" - " JOIN merchant_deposit_confirmations mcon" - " USING (deposit_confirmation_serial)" - " JOIN merchant_expected_transfers met" - " USING (expected_credit_serial)" - " JOIN merchant_accounts acc" - " ON (acc.account_serial = met.account_serial)" - /* Check that all this is for the same instance */ - " JOIN merchant_contract_terms contracts" - " USING (merchant_serial, order_serial)" - " WHERE mcon.order_serial=$1" - " ORDER BY met.wtid"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_transfer_details_by_order", + "SELECT" + " md.deposit_serial" + ",mcon.exchange_url" + ",met.wtid" + ",mtc.exchange_deposit_value" + ",mtc.exchange_deposit_fee" + ",mcon.deposit_timestamp" + ",met.confirmed" + ",met.expected_credit_serial" + " FROM merchant_expected_transfer_to_coin mtc" + " JOIN merchant_deposits md" + " USING (deposit_serial)" + " JOIN merchant_deposit_confirmations mcon" + " USING (deposit_confirmation_serial)" + " JOIN merchant_expected_transfers met" + " USING (expected_credit_serial)" + " JOIN merchant_accounts acc" + " ON (acc.account_serial = met.account_serial)" + " JOIN merchant_contract_terms contracts" + " USING (order_serial)" + " WHERE mcon.order_serial=$1" + " ORDER BY met.wtid"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_transfer_details_by_order", + stmt, params, &lookup_transfer_details_by_order_cb, &ltdo); diff --git a/src/backenddb/lookup_transfer_summary.c b/src/backenddb/lookup_transfer_summary.c @@ -121,28 +121,32 @@ TALER_MERCHANTDB_lookup_transfer_summary ( }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "lookup_transfer_summary", - "SELECT" - " mct.order_id" - ",mtc.exchange_deposit_value" - ",mtc.exchange_deposit_fee" - " FROM merchant_expected_transfers met" - " JOIN merchant_expected_transfer_to_coin mtc" - " USING (expected_credit_serial)" - " JOIN merchant_deposits dep" - " USING (deposit_serial)" - " JOIN merchant_deposit_confirmations mcon" - " USING (deposit_confirmation_serial)" - " JOIN merchant_contract_terms mct" - " USING (order_serial)" - " WHERE met.wtid=$2" - " AND met.exchange_url=$1"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_transfer_summary", + "SELECT" + " mct.order_id" + ",mtc.exchange_deposit_value" + ",mtc.exchange_deposit_fee" + " FROM merchant_expected_transfers met" + " JOIN merchant_expected_transfer_to_coin mtc" + " USING (expected_credit_serial)" + " JOIN merchant_deposits dep" + " USING (deposit_serial)" + " JOIN merchant_deposit_confirmations mcon" + " USING (deposit_confirmation_serial)" + " JOIN merchant_contract_terms mct" + " USING (order_serial)" + " WHERE met.wtid=$2" + " AND met.exchange_url=$1"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_transfer_summary", + stmt, params, &lookup_transfer_summary_cb, &ltdc); diff --git a/src/backenddb/lookup_transfers.c b/src/backenddb/lookup_transfers.c @@ -147,7 +147,6 @@ TALER_MERCHANTDB_lookup_transfers ( .pg = pg }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_timestamp (&before), GNUNET_PQ_query_param_timestamp (&after), GNUNET_PQ_query_param_uint64 (&offset), @@ -155,88 +154,85 @@ TALER_MERCHANTDB_lookup_transfers ( NULL == payto_uri.full_payto ? GNUNET_PQ_query_param_null () /* NULL: do not filter by payto URI */ : GNUNET_PQ_query_param_string (payto_uri.full_payto), - GNUNET_PQ_query_param_bool (! by_time), /* $7: filter by time? */ + GNUNET_PQ_query_param_bool (! by_time), /* $6: filter by time? */ GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_ALL == expected), /* filter by expected? */ GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_YES == expected), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt_asc[PG_PREP_INSTANCE_NAME_MAX]; + char stmt_desc[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_transfers_asc", - "SELECT" - " mt.credit_amount" - ",mt.wtid" - ",mac.payto_uri" - ",mt.exchange_url" - ",mt.credit_serial" - ",mt.execution_time" - ",mt.expected" - ",met.expected_credit_serial" - " FROM merchant_transfers mt" - " JOIN merchant_accounts mac" - " USING (account_serial)" - " LEFT JOIN merchant_expected_transfers met" - " ON mt.wtid = met.wtid" - " AND mt.account_serial = met.account_serial" - " AND mt.exchange_url = met.exchange_url" - " AND mt.expected" - " WHERE ( $7 OR " - " (mt.execution_time < $2 AND" - " mt.execution_time >= $3) )" - " AND ( (CAST($6 AS TEXT) IS NULL) OR " - " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')" - " =REGEXP_REPLACE($6,'\\?.*','')) )" - " AND ( $8 OR " - " (mt.expected = $9) )" - " AND merchant_serial =" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND (mt.credit_serial > $4)" - " ORDER BY mt.credit_serial ASC" - " LIMIT $5"); - PREPARE (pg, - "lookup_transfers_desc", - "SELECT" - " mt.credit_amount" - ",mt.wtid" - ",mac.payto_uri" - ",mt.exchange_url" - ",mt.credit_serial" - ",mt.execution_time" - ",mt.expected" - ",met.expected_credit_serial" - " FROM merchant_transfers mt" - " JOIN merchant_accounts mac" - " USING (account_serial)" - " LEFT JOIN merchant_expected_transfers met" - " ON mt.wtid = met.wtid" - " AND mt.account_serial = met.account_serial" - " AND mt.exchange_url = met.exchange_url" - " AND mt.expected" - " WHERE ( $7 OR " - " (mt.execution_time < $2 AND" - " mt.execution_time >= $3) )" - " AND ( (CAST($6 AS TEXT) IS NULL) OR " - " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')" - " =REGEXP_REPLACE($6,'\\?.*','')) )" - " AND ( $8 OR " - " (mt.expected = $9) )" - " AND merchant_serial =" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND (mt.credit_serial < $4)" - " ORDER BY mt.credit_serial DESC" - " LIMIT $5"); + PREPARE_INSTANCE (pg, + stmt_asc, + "lookup_transfers_asc", + "SELECT" + " mt.credit_amount" + ",mt.wtid" + ",mac.payto_uri" + ",mt.exchange_url" + ",mt.credit_serial" + ",mt.execution_time" + ",mt.expected" + ",met.expected_credit_serial" + " FROM merchant_transfers mt" + " JOIN merchant_accounts mac" + " USING (account_serial)" + " LEFT JOIN merchant_expected_transfers met" + " ON mt.wtid = met.wtid" + " AND mt.account_serial = met.account_serial" + " AND mt.exchange_url = met.exchange_url" + " AND mt.expected" + " WHERE ( $6 OR " + " (mt.execution_time < $1 AND" + " mt.execution_time >= $2) )" + " AND ( (CAST($5 AS TEXT) IS NULL) OR " + " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')" + " =REGEXP_REPLACE($5,'\\?.*','')) )" + " AND ( $7 OR " + " (mt.expected = $8) )" + " AND (mt.credit_serial > $3)" + " ORDER BY mt.credit_serial ASC" + " LIMIT $4"); + PREPARE_INSTANCE (pg, + stmt_desc, + "lookup_transfers_desc", + "SELECT" + " mt.credit_amount" + ",mt.wtid" + ",mac.payto_uri" + ",mt.exchange_url" + ",mt.credit_serial" + ",mt.execution_time" + ",mt.expected" + ",met.expected_credit_serial" + " FROM merchant_transfers mt" + " JOIN merchant_accounts mac" + " USING (account_serial)" + " LEFT JOIN merchant_expected_transfers met" + " ON mt.wtid = met.wtid" + " AND mt.account_serial = met.account_serial" + " AND mt.exchange_url = met.exchange_url" + " AND mt.expected" + " WHERE ( $6 OR " + " (mt.execution_time < $1 AND" + " mt.execution_time >= $2) )" + " AND ( (CAST($5 AS TEXT) IS NULL) OR " + " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')" + " =REGEXP_REPLACE($5,'\\?.*','')) )" + " AND ( $7 OR " + " (mt.expected = $8) )" + " AND (mt.credit_serial < $3)" + " ORDER BY mt.credit_serial DESC" + " LIMIT $4"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - (limit > 0) - ? "lookup_transfers_asc" - : "lookup_transfers_desc", + (limit > 0) ? stmt_asc : stmt_desc, params, &lookup_transfers_cb, &ltc); diff --git a/src/backenddb/lookup_units.c b/src/backenddb/lookup_units.c @@ -100,48 +100,46 @@ TALER_MERCHANTDB_lookup_units (struct TALER_MERCHANTDB_PostgresContext *pg, .extract_failed = false }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_units", - "WITH mi AS (" - " SELECT merchant_serial FROM merchant_instances WHERE merchant_id=$1" - ")" - "SELECT cu.unit_serial" - " ,cu.unit" - " ,cu.unit_name_long" - " ,cu.unit_name_short" - " ,cu.unit_name_long_i18n" - " ,cu.unit_name_short_i18n" - " ,cu.unit_allow_fraction" - " ,cu.unit_precision_level" - " ,cu.unit_active" - " ,FALSE AS unit_builtin" - " FROM merchant_custom_units cu" - " JOIN mi ON cu.merchant_serial = mi.merchant_serial" - " UNION ALL " - "SELECT bu.unit_serial" - " ,bu.unit" - " ,bu.unit_name_long" - " ,bu.unit_name_short" - " ,bu.unit_name_long_i18n" - " ,bu.unit_name_short_i18n" - " ,COALESCE(bo.override_allow_fraction, bu.unit_allow_fraction)" - " ,COALESCE(bo.override_precision_level, bu.unit_precision_level)" - " ,COALESCE(bo.override_active, bu.unit_active)" - " ,TRUE AS unit_builtin" - " FROM merchant_builtin_units bu" - " JOIN mi ON TRUE" - " LEFT JOIN merchant_builtin_unit_overrides bo" - " ON bo.builtin_unit_serial = bu.unit_serial" - " AND bo.merchant_serial = mi.merchant_serial" - " ORDER BY unit"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_units", + "SELECT cu.unit_serial" + " ,cu.unit" + " ,cu.unit_name_long" + " ,cu.unit_name_short" + " ,cu.unit_name_long_i18n" + " ,cu.unit_name_short_i18n" + " ,cu.unit_allow_fraction" + " ,cu.unit_precision_level" + " ,cu.unit_active" + " ,FALSE AS unit_builtin" + " FROM merchant_custom_units cu" + " UNION ALL " + "SELECT bu.unit_serial" + " ,bu.unit" + " ,bu.unit_name_long" + " ,bu.unit_name_short" + " ,bu.unit_name_long_i18n" + " ,bu.unit_name_short_i18n" + " ,COALESCE(bo.override_allow_fraction, bu.unit_allow_fraction)" + " ,COALESCE(bo.override_precision_level, bu.unit_precision_level)" + " ,COALESCE(bo.override_active, bu.unit_active)" + " ,TRUE AS unit_builtin" + " FROM merchant.merchant_builtin_units bu" + " LEFT JOIN merchant_builtin_unit_overrides bo" + " ON bo.builtin_unit_serial = bu.unit_serial" + " ORDER BY unit"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_units", + stmt, params, &lookup_units_cb, &luc); diff --git a/src/backenddb/lookup_webhook.c b/src/backenddb/lookup_webhook.c @@ -32,25 +32,26 @@ TALER_MERCHANTDB_lookup_webhook (struct TALER_MERCHANTDB_PostgresContext *pg, struct TALER_MERCHANTDB_WebhookDetails *wb) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (webhook_id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_webhook", - "SELECT" - " event_type" - ",url" - ",http_method" - ",header_template" - ",body_template" - " FROM merchant_webhook" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND merchant_webhook.webhook_id=$2"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_webhook", + "SELECT" + " event_type" + ",url" + ",http_method" + ",header_template" + ",body_template" + " FROM merchant_webhook" + " WHERE webhook_id=$1"); if (NULL == wb) { @@ -59,7 +60,7 @@ TALER_MERCHANTDB_lookup_webhook (struct TALER_MERCHANTDB_PostgresContext *pg, }; return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_webhook", + stmt, params, rs_null); } @@ -84,7 +85,7 @@ TALER_MERCHANTDB_lookup_webhook (struct TALER_MERCHANTDB_PostgresContext *pg, }; return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_webhook", + stmt, params, rs); } diff --git a/src/backenddb/lookup_webhook_by_event.c b/src/backenddb/lookup_webhook_by_event.c @@ -124,30 +124,31 @@ TALER_MERCHANTDB_lookup_webhook_by_event (struct TALER_MERCHANTDB_PostgresContex }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (event_type), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_webhook_by_event", - "SELECT" - " webhook_serial" - ",event_type" - ",url" - ",http_method" - ",header_template" - ",body_template" - " FROM merchant_webhook" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND event_type=$2"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_webhook_by_event", + "SELECT" + " webhook_serial" + ",event_type" + ",url" + ",http_method" + ",header_template" + ",body_template" + " FROM merchant_webhook" + " WHERE event_type=$1"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_webhook_by_event", + stmt, params, &lookup_webhook_by_event_cb, &wlc); diff --git a/src/backenddb/lookup_webhooks.c b/src/backenddb/lookup_webhooks.c @@ -104,24 +104,25 @@ TALER_MERCHANTDB_lookup_webhooks (struct TALER_MERCHANTDB_PostgresContext *pg, .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_webhooks", - "SELECT" - " webhook_id" - ",event_type" - " FROM merchant_webhook" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1"); + PREPARE_INSTANCE (pg, + stmt, + "lookup_webhooks", + "SELECT" + " webhook_id" + ",event_type" + " FROM merchant_webhook"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_webhooks", + stmt, params, &lookup_webhooks_cb, &wlc); diff --git a/src/backenddb/mark_contract_paid.c b/src/backenddb/mark_contract_paid.c @@ -33,7 +33,6 @@ TALER_MERCHANTDB_mark_contract_paid (struct TALER_MERCHANTDB_PostgresContext *pg int16_t choice_index) { 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) @@ -42,14 +41,19 @@ TALER_MERCHANTDB_mark_contract_paid (struct TALER_MERCHANTDB_PostgresContext *pg GNUNET_PQ_query_param_end }; struct GNUNET_PQ_QueryParam uparams[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_auto_from_type (h_contract_terms), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt_paid[PG_PREP_INSTANCE_NAME_MAX]; + char stmt_sold[PG_PREP_INSTANCE_NAME_MAX]; + char stmt_del[PG_PREP_INSTANCE_NAME_MAX]; /* Session ID must always be given by the caller. */ GNUNET_assert (NULL != session_id); + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); /* no preflight check here, run in transaction by caller! */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, @@ -57,65 +61,55 @@ TALER_MERCHANTDB_mark_contract_paid (struct TALER_MERCHANTDB_PostgresContext *pg GNUNET_h2s (&h_contract_terms->hash), instance_id, session_id); - PREPARE (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" - " FROM merchant_instances" - " WHERE merchant_id=$1)"); + PREPARE_INSTANCE (pg, + stmt_paid, + "mark_contract_paid", + "UPDATE merchant_contract_terms SET" + " paid=TRUE" + ",session_id=$2" + ",choice_index=$3" + " WHERE h_contract_terms=$1"); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "mark_contract_paid", + stmt_paid, params); if (qs <= 0) return qs; - PREPARE (pg, - "mark_inventory_sold", - "UPDATE merchant_inventory SET" - " total_sold = total_sold" - " + order_locks.total_locked" - " + ((merchant_inventory.total_sold_frac::BIGINT" - " + order_locks.total_locked_frac::BIGINT) / 1000000)" - " ,total_sold_frac =" - " ((merchant_inventory.total_sold_frac::BIGINT" - " + order_locks.total_locked_frac::BIGINT) % 1000000)::INT4" - " FROM (SELECT total_locked,total_locked_frac,product_serial" - " FROM merchant_order_locks" - " WHERE order_serial=" - " (SELECT order_serial" - " FROM merchant_contract_terms" - " WHERE h_contract_terms=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1))" - " ) AS order_locks" - " WHERE merchant_inventory.product_serial" - " =order_locks.product_serial"); + PREPARE_INSTANCE (pg, + stmt_sold, + "mark_inventory_sold", + "UPDATE merchant_inventory SET" + " total_sold = total_sold" + " + order_locks.total_locked" + " + ((merchant_inventory.total_sold_frac::BIGINT" + " + order_locks.total_locked_frac::BIGINT) / 1000000)" + " ,total_sold_frac =" + " ((merchant_inventory.total_sold_frac::BIGINT" + " + order_locks.total_locked_frac::BIGINT) % 1000000)::INT4" + " FROM (SELECT total_locked,total_locked_frac,product_serial" + " FROM merchant_order_locks" + " WHERE order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " WHERE h_contract_terms=$1)" + " ) AS order_locks" + " WHERE merchant_inventory.product_serial" + " =order_locks.product_serial"); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "mark_inventory_sold", + stmt_sold, uparams); if (qs < 0) return qs; /* 0: no inventory management, that's OK! */ /* ON DELETE CASCADE deletes from merchant_order_locks */ - PREPARE (pg, - "delete_completed_order", - "WITH md AS" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1) " - "DELETE" - " FROM merchant_orders" - " WHERE order_serial=" - " (SELECT order_serial" - " FROM merchant_contract_terms" - " JOIN md USING (merchant_serial)" - " WHERE h_contract_terms=$2)"); + PREPARE_INSTANCE (pg, + stmt_del, + "delete_completed_order", + "DELETE" + " FROM merchant_orders" + " WHERE order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " WHERE h_contract_terms=$1)"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_completed_order", + stmt_del, uparams); } diff --git a/src/backenddb/mark_order_wired.c b/src/backenddb/mark_order_wired.c @@ -34,14 +34,18 @@ TALER_MERCHANTDB_mark_order_wired (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "mark_order_wired", - "UPDATE merchant_contract_terms SET" - " wired=TRUE" - " WHERE order_serial=$1"); + PREPARE_INSTANCE (pg, + stmt, + "mark_order_wired", + "UPDATE merchant_contract_terms SET" + " wired=TRUE" + " WHERE order_serial=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "mark_order_wired", + stmt, params); } diff --git a/src/backenddb/meson.build b/src/backenddb/meson.build @@ -14,6 +14,7 @@ libtalermerchantdb = library( 'event_listen.c', 'event_notify.c', 'preflight.c', + 'set_instance.c', 'account_kyc_get_outdated.c', 'account_kyc_get_status.c', 'account_kyc_set_failed.c', diff --git a/src/backenddb/pg.c b/src/backenddb/pg.c @@ -85,6 +85,7 @@ void TALER_MERCHANTDB_disconnect (struct TALER_MERCHANTDB_PostgresContext *pg) { GNUNET_PQ_disconnect (pg->conn); + GNUNET_free (pg->current_merchant_id); GNUNET_free (pg); } diff --git a/src/backenddb/pg_account_kyc_get_outdated.sql b/src/backenddb/pg_account_kyc_get_outdated.sql @@ -0,0 +1,57 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +DROP FUNCTION IF EXISTS merchant.account_kyc_get_outdated(INT8); +CREATE FUNCTION merchant.account_kyc_get_outdated(IN p_now INT8) +RETURNS TABLE( + out_merchant_id TEXT, + out_h_wire BYTEA, + out_exchange_url TEXT) +LANGUAGE plpgsql +AS $FN$ +DECLARE + rec RECORD; + s TEXT; + inner_rec RECORD; +BEGIN + FOR rec IN + SELECT merchant_serial, merchant_id FROM merchant.merchant_instances + LOOP + s := 'merchant_instance_' || rec.merchant_serial::TEXT; + BEGIN + FOR inner_rec IN + EXECUTE format('SELECT ma.h_wire AS h_wire, kyc.exchange_url AS exchange_url' + ' FROM %I.merchant_kyc kyc' + ' JOIN %I.merchant_accounts ma USING (account_serial)' + ' WHERE kyc.next_kyc_poll < $1' + ' ORDER BY kyc.next_kyc_poll ASC', s, s) + USING p_now + LOOP + out_merchant_id := rec.merchant_id; + out_h_wire := inner_rec.h_wire; + out_exchange_url := inner_rec.exchange_url; + RETURN NEXT; + END LOOP; + EXCEPTION + WHEN undefined_table THEN + NULL; + END; + END LOOP; +END +$FN$; +COMMENT ON FUNCTION merchant.account_kyc_get_outdated(INT8) + IS 'Returns one row per outdated KYC entry across all instance schemas.' + ' An entry is outdated if its next_kyc_poll value is less than p_now.'; diff --git a/src/backenddb/pg_create_instance_schema.sql b/src/backenddb/pg_create_instance_schema.sql @@ -0,0 +1,39 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +DROP FUNCTION IF EXISTS merchant.create_instance_schema(BIGINT); +CREATE FUNCTION merchant.create_instance_schema(in_merchant_serial BIGINT) + RETURNS void + LANGUAGE plpgsql + AS $FN$ +DECLARE + s TEXT := 'merchant_instance_' || in_merchant_serial::TEXT; +BEGIN + -- Resolve bare type references (statistic_range, taler_amount_currency, ...) + -- and bare table references in trigger / procedure bodies against the new + -- per-instance schema first, then fall through to merchant. + EXECUTE format('SET LOCAL search_path TO %I, merchant', s); +#include "pg_create_instance_schema_tables.sql.fragment" +#include "pg_create_instance_schema_triggers.sql.fragment" +#include "pg_create_instance_schema_procedures.sql.fragment" +END; +$FN$; +COMMENT ON FUNCTION merchant.create_instance_schema(BIGINT) + IS 'Constructs the per-instance schema merchant_instance_<merchant_serial>' + ' with all per-instance tables, indexes, foreign keys, trigger functions,' + ' triggers and stored procedures. Called from' + ' merchant_instances_after_insert_trigger() whenever a new instance row' + ' is inserted into merchant.merchant_instances.'; diff --git a/src/backenddb/pg_create_instance_schema_procedures.sql.fragment b/src/backenddb/pg_create_instance_schema_procedures.sql.fragment @@ -0,0 +1,2214 @@ +-- ===================================================================== +-- Per-instance schema constructor: stored procedures and functions. +-- +-- Embedded inside merchant.create_instance_schema(BIGINT); local var +-- `s TEXT` holds the per-instance schema name. All DDL is via +-- EXECUTE format(...) with %I quoting. +-- +-- The merchant_serial column has been dropped from every per-instance +-- table; the in_instance_id / in_instance_name / in_merchant_serial / +-- out_no_instance parameters and the merchant_serial-resolution +-- prologue are removed from each procedure. Helper functions +-- (replace_placeholder, interval_to_start, ...) and global tables +-- (merchant_instances, merchant_exchange_*, merchant_builtin_units, +-- merchant_donau_keys) keep their `merchant.` qualifier. +-- ===================================================================== + + -- ------------------------------------------------------------------- + -- Statistic bump procedures (6 procedures) + -- ------------------------------------------------------------------- + + -- merchant_do_bump_amount_bucket_stat + EXECUTE format($OUTER$ + CREATE PROCEDURE %I.merchant_do_bump_amount_bucket_stat(IN in_slug text, IN in_timestamp timestamp without time zone, IN in_delta merchant.taler_amount_currency) + LANGUAGE plpgsql + AS $BODY$ + 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 merchant.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 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 + ,bucket_start + ,bucket_range + ,curr + ,cumulative_value + ,cumulative_frac + ) VALUES ( + my_meta + ,my_bucket_start + ,my_range + ,(in_delta).curr + ,(in_delta).val + ,(in_delta).frac); + END IF; + END LOOP; + CLOSE my_curs; + END $BODY$ + $OUTER$, s); + + -- merchant_do_bump_amount_interval_stat + EXECUTE format($OUTER$ + CREATE PROCEDURE %I.merchant_do_bump_amount_interval_stat(IN in_slug text, IN in_timestamp timestamp without time zone, IN in_delta merchant.taler_amount_currency) + LANGUAGE plpgsql + AS $BODY$ + 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; + 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 + RETURN; + END IF; + my_start = my_start - my_start %% my_precisionx; + my_meta = my_record.imeta_serial_id; + INSERT INTO merchant_statistic_amount_event AS msae + (imeta_serial_id + ,slot + ,delta_curr + ,delta_value + ,delta_frac + ) VALUES ( + my_meta + ,my_start + ,(in_delta).curr + ,(in_delta).val + ,(in_delta).frac + ) + ON CONFLICT (imeta_serial_id, 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 range=my_rangex + AND curr=(in_delta).curr; + IF NOT FOUND + THEN + INSERT INTO merchant_statistic_interval_amount + (imeta_serial_id + ,range + ,event_delimiter + ,curr + ,cumulative_value + ,cumulative_frac + ) VALUES ( + my_meta + ,my_rangex + ,my_event + ,(in_delta).curr + ,(in_delta).val + ,(in_delta).frac); + END IF; + END $BODY$ + $OUTER$, s); + + -- merchant_do_bump_amount_stat + EXECUTE format($OUTER$ + CREATE PROCEDURE %I.merchant_do_bump_amount_stat(IN in_slug text, IN in_timestamp timestamp without time zone, IN in_delta merchant.taler_amount_currency) + LANGUAGE plpgsql + AS $BODY$ + BEGIN + CALL merchant_do_bump_amount_bucket_stat (in_slug, in_timestamp, in_delta); + CALL merchant_do_bump_amount_interval_stat (in_slug, in_timestamp, in_delta); + END $BODY$ + $OUTER$, s); + + -- merchant_do_bump_number_bucket_stat + EXECUTE format($OUTER$ + CREATE PROCEDURE %I.merchant_do_bump_number_bucket_stat(IN in_slug text, IN in_timestamp timestamp without time zone, IN in_delta bigint) + LANGUAGE plpgsql + AS $BODY$ + 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 merchant.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 bucket_start=my_bucket_start + AND bucket_range=my_range; + IF NOT FOUND + THEN + INSERT INTO merchant_statistic_bucket_counter + (bmeta_serial_id + ,bucket_start + ,bucket_range + ,cumulative_number + ) VALUES ( + my_meta + ,my_bucket_start + ,my_range + ,in_delta); + END IF; + END LOOP; + CLOSE my_curs; + END $BODY$ + $OUTER$, s); + + -- merchant_do_bump_number_interval_stat + EXECUTE format($OUTER$ + CREATE PROCEDURE %I.merchant_do_bump_number_interval_stat(IN in_slug text, IN in_timestamp timestamp without time zone, IN in_delta bigint) + LANGUAGE plpgsql + AS $BODY$ + 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; + 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 + RETURN; + END IF; + my_meta = my_record.imeta_serial_id; + my_start = my_start - my_start %% my_precisionx; + INSERT INTO merchant_statistic_counter_event AS msce + (imeta_serial_id + ,slot + ,delta) + VALUES + (my_meta + ,my_start + ,in_delta) + ON CONFLICT (imeta_serial_id, 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 range=my_rangex; + IF NOT FOUND + THEN + INSERT INTO merchant_statistic_interval_counter + (imeta_serial_id + ,range + ,event_delimiter + ,cumulative_number + ) VALUES ( + my_meta + ,my_rangex + ,my_event + ,in_delta); + END IF; + END $BODY$ + $OUTER$, s); + + -- merchant_do_bump_number_stat + EXECUTE format($OUTER$ + CREATE PROCEDURE %I.merchant_do_bump_number_stat(IN in_slug text, IN in_timestamp timestamp without time zone, IN in_delta bigint) + LANGUAGE plpgsql + AS $BODY$ + BEGIN + CALL merchant_do_bump_number_bucket_stat (in_slug, in_timestamp, in_delta); + CALL merchant_do_bump_number_interval_stat (in_slug, in_timestamp, in_delta); + END $BODY$ + $OUTER$, s); + + -- ------------------------------------------------------------------- + -- Account / KYC functions + -- ------------------------------------------------------------------- + + -- merchant_do_account_kyc_get_status + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_account_kyc_get_status(in_now bigint, in_exchange_url text, in_h_wire bytea) RETURNS TABLE(out_h_wire bytea, out_payto_uri text, out_exchange_url text, out_kyc_timestamp bigint, out_kyc_ok boolean, out_access_token bytea, out_exchange_http_status integer, out_exchange_ec_code integer, out_aml_review boolean, out_jaccount_limits text) + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_account_serial INT8; + my_h_wire BYTEA; + my_payto_uri TEXT; + my_kyc_record RECORD; + BEGIN + FOR my_account_serial, my_h_wire, my_payto_uri + IN SELECT account_serial, h_wire, payto_uri + FROM merchant_accounts + WHERE active + AND (in_h_wire IS NULL OR h_wire = in_h_wire) + ORDER BY account_serial ASC + LOOP + FOR my_kyc_record IN + SELECT + mk.kyc_serial_id + ,mk.exchange_url + ,mk.kyc_timestamp + ,mk.kyc_ok + ,mk.access_token + ,mk.exchange_http_status + ,mk.exchange_ec_code + ,mk.aml_review + ,mk.jaccount_limits::TEXT + FROM merchant_kyc mk + WHERE mk.account_serial = my_account_serial + AND (in_exchange_url IS NULL OR mk.exchange_url = in_exchange_url) + ORDER BY mk.kyc_serial_id ASC + LOOP + UPDATE merchant_kyc + SET next_kyc_poll=in_now + WHERE kyc_serial_id = my_kyc_record.kyc_serial_id; + NOTIFY XDQM4Z4N0D3GX0H9JEXH70EBC2T3KY7HC0TJB0Z60D2H781RXR6AG; + RETURN QUERY + SELECT + my_h_wire, + my_payto_uri, + my_kyc_record.exchange_url, + my_kyc_record.kyc_timestamp, + my_kyc_record.kyc_ok, + my_kyc_record.access_token, + my_kyc_record.exchange_http_status, + my_kyc_record.exchange_ec_code, + my_kyc_record.aml_review, + my_kyc_record.jaccount_limits::TEXT; + END LOOP; + IF NOT FOUND + THEN + RETURN QUERY + SELECT + my_h_wire, + my_payto_uri, + NULL::TEXT, + NULL::INT8, + NULL::BOOLEAN, + NULL::BYTEA, + NULL::INT4, + NULL::INT4, + NULL::BOOLEAN, + NULL::TEXT; + END IF; + END LOOP; + END $BODY$ + $OUTER$, s); + + -- merchant_do_account_kyc_set_failed + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_account_kyc_set_failed(in_h_wire bytea, in_exchange_url text, in_timestamp bigint, in_exchange_http_status integer, in_kyc_ok boolean, in_notify_str text, in_notify2_str text, OUT out_no_account boolean) RETURNS boolean + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_account_serial INT8; + BEGIN + out_no_account=FALSE; + SELECT account_serial + INTO my_account_serial + FROM merchant_accounts + WHERE h_wire=in_h_wire; + IF NOT FOUND + THEN + out_no_account=TRUE; + RETURN; + END IF; + UPDATE merchant_kyc + SET kyc_timestamp=in_timestamp + ,kyc_ok=in_kyc_ok + ,exchange_http_status=in_exchange_http_status + ,exchange_ec_code=0 + WHERE account_serial=my_account_serial + AND exchange_url=in_exchange_url; + IF NOT FOUND + THEN + INSERT INTO merchant_kyc + (kyc_timestamp + ,kyc_ok + ,account_serial + ,exchange_url + ,exchange_http_status) + VALUES + (in_timestamp + ,in_kyc_ok + ,my_account_serial + ,in_exchange_url + ,in_exchange_http_status); + END IF; + EXECUTE FORMAT ( + 'NOTIFY %%s' + ,in_notify_str); + EXECUTE FORMAT ( + 'NOTIFY %%s' + ,in_notify2_str); + END $BODY$ + $OUTER$, s); + + -- merchant_do_account_kyc_set_status + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_account_kyc_set_status(in_h_wire bytea, in_exchange_url text, in_timestamp bigint, in_exchange_http_status integer, in_exchange_ec_code integer, in_access_token bytea, in_jlimits jsonb, in_aml_active boolean, in_kyc_ok boolean, in_notify_str text, in_notify2_str text, in_rule_gen bigint, in_next_time bigint, in_kyc_backoff bigint, OUT out_no_account boolean) RETURNS boolean + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_account_serial INT8; + BEGIN + out_no_account=FALSE; + SELECT account_serial + INTO my_account_serial + FROM merchant_accounts + WHERE h_wire=in_h_wire; + IF NOT FOUND + THEN + out_no_account=TRUE; + RETURN; + END IF; + UPDATE merchant_kyc + SET kyc_timestamp=in_timestamp + ,kyc_ok=in_kyc_ok + ,jaccount_limits=in_jlimits + ,aml_review=in_aml_active + ,exchange_http_status=in_exchange_http_status + ,exchange_ec_code=in_exchange_ec_code + ,access_token=in_access_token + ,last_rule_gen=in_rule_gen + ,next_kyc_poll=in_next_time + ,kyc_backoff=in_kyc_backoff + WHERE account_serial=my_account_serial + AND exchange_url=in_exchange_url; + IF NOT FOUND + THEN + INSERT INTO merchant_kyc + (kyc_timestamp + ,kyc_ok + ,account_serial + ,exchange_url + ,jaccount_limits + ,aml_review + ,exchange_http_status + ,exchange_ec_code + ,access_token + ,last_rule_gen + ,next_kyc_poll + ,kyc_backoff) + VALUES + (in_timestamp + ,in_kyc_ok + ,my_account_serial + ,in_exchange_url + ,in_jlimits + ,in_aml_active + ,in_exchange_http_status + ,in_exchange_ec_code + ,in_access_token + ,in_rule_gen + ,in_next_time + ,in_kyc_backoff); + END IF; + EXECUTE FORMAT ( + 'NOTIFY %%s' + ,in_notify_str); + EXECUTE FORMAT ( + 'NOTIFY %%s' + ,in_notify2_str); + END $BODY$ + $OUTER$, s); + + -- merchant_do_activate_account + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_activate_account(in_h_wire bytea, in_salt bytea, in_full_payto text, in_credit_facade_url text, in_credit_facade_credentials text, in_extra_wire_subject_metadata text, OUT out_h_wire bytea, OUT out_salt bytea, OUT out_not_found boolean, OUT out_conflict boolean) RETURNS record + LANGUAGE plpgsql + AS $BODY$ + DECLARE my_active BOOL; + DECLARE my_cfu TEXT; + DECLARE my_cfc TEXT; + DECLARE my_ewsm TEXT; + DECLARE my_h_wire BYTEA; + DECLARE my_salt BYTEA; + BEGIN + out_not_found = FALSE; + out_conflict = FALSE; + out_h_wire = in_h_wire; + out_salt = in_salt; + INSERT INTO merchant_accounts + AS ma + (h_wire + ,salt + ,payto_uri + ,credit_facade_url + ,credit_facade_credentials + ,active + ,extra_wire_subject_metadata + ) VALUES ( + in_h_wire + ,in_salt + ,in_full_payto + ,in_credit_facade_url + ,in_credit_facade_credentials::JSONB + ,TRUE + ,in_extra_wire_subject_metadata + ) ON CONFLICT DO NOTHING; + IF FOUND + THEN + NOTIFY XDQM4Z4N0D3GX0H9JEXH70EBC2T3KY7HC0TJB0Z60D2H781RXR6AG; + RETURN; + END IF; + SELECT h_wire + ,salt + ,active + ,credit_facade_url + ,credit_facade_credentials::TEXT + ,extra_wire_subject_metadata + INTO my_h_wire + ,my_salt + ,my_active + ,my_cfu + ,my_cfc + ,my_ewsm + FROM merchant_accounts + WHERE payto_uri=in_full_payto; + IF NOT FOUND + THEN + out_not_found = TRUE; + RETURN; + END IF; + IF (my_active AND + (ROW (my_cfu + ,my_cfc + ,my_ewsm) + IS DISTINCT FROM + ROW (in_credit_facade_url + ,in_credit_facade_credentials + ,in_extra_wire_subject_metadata))) + THEN + out_conflict = TRUE; + RETURN; + END IF; + out_salt = my_salt; + out_h_wire = my_h_wire; + IF my_active + THEN + RETURN; + END IF; + UPDATE merchant_accounts + SET active=TRUE + ,credit_facade_url=in_credit_facade_url + ,credit_facade_credentials=in_credit_facade_credentials::JSONB + ,extra_wire_subject_metadata=in_extra_wire_subject_metadata + WHERE h_wire=out_h_wire; + NOTIFY XDQM4Z4N0D3GX0H9JEXH70EBC2T3KY7HC0TJB0Z60D2H781RXR6AG; + END $BODY$ + $OUTER$, s); + + -- merchant_do_inactivate_account + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_inactivate_account(in_h_wire bytea, OUT out_found boolean) RETURNS boolean + LANGUAGE plpgsql + AS $BODY$ + BEGIN + UPDATE merchant_accounts + SET active=FALSE + WHERE h_wire=in_h_wire; + out_found = FOUND; + IF out_found + THEN + NOTIFY XDQM4Z4N0D3GX0H9JEXH70EBC2T3KY7HC0TJB0Z60D2H781RXR6AG; + END IF; + END $BODY$ + $OUTER$, s); + + -- ------------------------------------------------------------------- + -- Unit functions + -- ------------------------------------------------------------------- + + -- merchant_do_delete_unit + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_delete_unit(in_unit_id text, OUT out_no_unit boolean, OUT out_builtin_conflict boolean) RETURNS record + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_unit merchant_custom_units%%ROWTYPE; + BEGIN + out_no_unit := FALSE; + out_builtin_conflict := FALSE; + + SELECT * + INTO my_unit + FROM merchant_custom_units + WHERE unit = in_unit_id + FOR UPDATE; + + IF NOT FOUND THEN + IF EXISTS (SELECT 1 FROM merchant.merchant_builtin_units bu WHERE bu.unit = in_unit_id) THEN + out_builtin_conflict := TRUE; + ELSE + out_no_unit := TRUE; + END IF; + RETURN; + END IF; + + DELETE FROM merchant_custom_units + WHERE unit_serial = my_unit.unit_serial; + + RETURN; + END $BODY$ + $OUTER$, s); + + -- merchant_do_insert_unit + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_insert_unit(in_unit text, in_unit_name_long text, in_unit_name_short text, in_unit_name_long_i18n bytea, in_unit_name_short_i18n bytea, in_unit_allow_fraction boolean, in_unit_precision_level integer, in_unit_active boolean, OUT out_conflict boolean, OUT out_unit_serial bigint) RETURNS record + LANGUAGE plpgsql + AS $BODY$ + BEGIN + -- Reject attempts to shadow builtin identifiers. + IF EXISTS ( + SELECT 1 FROM merchant.merchant_builtin_units bu WHERE bu.unit = in_unit + ) THEN + out_conflict := TRUE; + out_unit_serial := NULL; + RETURN; + END IF; + + INSERT INTO merchant_custom_units ( + unit, + unit_name_long, + unit_name_short, + unit_name_long_i18n, + unit_name_short_i18n, + unit_allow_fraction, + unit_precision_level, + unit_active) + VALUES ( + in_unit, + in_unit_name_long, + in_unit_name_short, + in_unit_name_long_i18n, + in_unit_name_short_i18n, + in_unit_allow_fraction, + in_unit_precision_level, + in_unit_active) + ON CONFLICT (unit) DO NOTHING + RETURNING unit_serial + INTO out_unit_serial; + + IF FOUND THEN + out_conflict := FALSE; + RETURN; + END IF; + + -- Conflict: custom unit already exists. + SELECT unit_serial + INTO out_unit_serial + FROM merchant_custom_units + WHERE unit = in_unit; + + out_conflict := TRUE; + END $BODY$ + $OUTER$, s); + + -- merchant_do_update_unit + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_update_unit(in_unit_id text, in_unit_name_long text, in_unit_name_long_i18n bytea, in_unit_name_short text, in_unit_name_short_i18n bytea, in_unit_allow_fraction boolean, in_unit_precision_level integer, in_unit_active boolean, OUT out_no_unit boolean, OUT out_builtin_conflict boolean) RETURNS record + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_custom merchant_custom_units%%ROWTYPE; + my_builtin merchant.merchant_builtin_units%%ROWTYPE; + my_override merchant_builtin_unit_overrides%%ROWTYPE; + new_unit_name_long TEXT; + new_unit_name_short TEXT; + new_unit_name_long_i18n BYTEA; + new_unit_name_short_i18n BYTEA; + new_unit_allow_fraction BOOL; + new_unit_precision_level INT4; + new_unit_active BOOL; + old_unit_allow_fraction BOOL; + old_unit_precision_level INT4; + old_unit_active BOOL; + BEGIN + out_no_unit := FALSE; + out_builtin_conflict := FALSE; + + SELECT * + INTO my_custom + FROM merchant_custom_units + WHERE unit = in_unit_id + FOR UPDATE; + + IF FOUND THEN + old_unit_allow_fraction := my_custom.unit_allow_fraction; + old_unit_precision_level := my_custom.unit_precision_level; + old_unit_active := my_custom.unit_active; + + new_unit_name_long := COALESCE (in_unit_name_long, my_custom.unit_name_long); + new_unit_name_short := COALESCE (in_unit_name_short, my_custom.unit_name_short); + new_unit_name_long_i18n := COALESCE (in_unit_name_long_i18n, + my_custom.unit_name_long_i18n); + new_unit_name_short_i18n := COALESCE (in_unit_name_short_i18n, + my_custom.unit_name_short_i18n); + new_unit_allow_fraction := COALESCE (in_unit_allow_fraction, + my_custom.unit_allow_fraction); + new_unit_precision_level := COALESCE (in_unit_precision_level, + my_custom.unit_precision_level); + IF NOT new_unit_allow_fraction THEN + new_unit_precision_level := 0; + END IF; + + new_unit_active := COALESCE (in_unit_active, my_custom.unit_active); + + UPDATE merchant_custom_units SET + unit_name_long = new_unit_name_long + ,unit_name_long_i18n = new_unit_name_long_i18n + ,unit_name_short = new_unit_name_short + ,unit_name_short_i18n = new_unit_name_short_i18n + ,unit_allow_fraction = new_unit_allow_fraction + ,unit_precision_level = new_unit_precision_level + ,unit_active = new_unit_active + WHERE unit_serial = my_custom.unit_serial; + + ASSERT FOUND,'SELECTED it earlier, should UPDATE it now'; + + IF old_unit_allow_fraction IS DISTINCT FROM new_unit_allow_fraction + OR old_unit_precision_level IS DISTINCT FROM new_unit_precision_level + THEN + UPDATE merchant_inventory SET + allow_fractional_quantity = new_unit_allow_fraction + , fractional_precision_level = new_unit_precision_level + WHERE unit = in_unit_id + AND allow_fractional_quantity = old_unit_allow_fraction + AND fractional_precision_level = old_unit_precision_level; + END IF; + RETURN; + END IF; + + -- Try builtin with overrides. + SELECT * + INTO my_builtin + FROM merchant.merchant_builtin_units + WHERE unit = in_unit_id; + + IF NOT FOUND THEN + out_no_unit := TRUE; + RETURN; + END IF; + + SELECT * + INTO my_override + FROM merchant_builtin_unit_overrides + WHERE builtin_unit_serial = my_builtin.unit_serial + FOR UPDATE; + + old_unit_allow_fraction := COALESCE (my_override.override_allow_fraction, + my_builtin.unit_allow_fraction); + old_unit_precision_level := COALESCE (my_override.override_precision_level, + my_builtin.unit_precision_level); + old_unit_active := COALESCE (my_override.override_active, + my_builtin.unit_active); + + -- Only policy flags can change for builtin units. + IF in_unit_name_long IS NOT NULL + OR in_unit_name_short IS NOT NULL + OR in_unit_name_long_i18n IS NOT NULL + OR in_unit_name_short_i18n IS NOT NULL THEN + out_builtin_conflict := TRUE; + RETURN; + END IF; + + new_unit_allow_fraction := COALESCE (in_unit_allow_fraction, + old_unit_allow_fraction); + new_unit_precision_level := COALESCE (in_unit_precision_level, + old_unit_precision_level); + IF NOT new_unit_allow_fraction THEN + new_unit_precision_level := 0; + END IF; + new_unit_active := COALESCE (in_unit_active, old_unit_active); + + INSERT INTO merchant_builtin_unit_overrides ( + builtin_unit_serial, + override_allow_fraction, + override_precision_level, + override_active) + VALUES (my_builtin.unit_serial, + new_unit_allow_fraction, + new_unit_precision_level, + new_unit_active) + ON CONFLICT (builtin_unit_serial) + DO UPDATE SET override_allow_fraction = EXCLUDED.override_allow_fraction + , override_precision_level = EXCLUDED.override_precision_level + , override_active = EXCLUDED.override_active; + + IF old_unit_allow_fraction IS DISTINCT FROM new_unit_allow_fraction + OR old_unit_precision_level IS DISTINCT FROM new_unit_precision_level + THEN + UPDATE merchant_inventory SET + allow_fractional_quantity = new_unit_allow_fraction + , fractional_precision_level = new_unit_precision_level + WHERE unit = in_unit_id + AND allow_fractional_quantity = old_unit_allow_fraction + AND fractional_precision_level = old_unit_precision_level; + END IF; + + RETURN; + END $BODY$ + $OUTER$, s); + + -- ------------------------------------------------------------------- + -- Money pots + -- ------------------------------------------------------------------- + + -- merchant_do_increment_money_pots + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_increment_money_pots(ina_money_pots_ids bigint[], ina_increments merchant.taler_amount_currency[], OUT out_not_found boolean) RETURNS boolean + LANGUAGE plpgsql + AS $BODY$ + DECLARE + i INT; + ini_current_pot_id INT8; + ini_current_increment taler_amount_currency; + my_totals taler_amount_currency[]; + currency_found BOOL; + j INT; + BEGIN + out_not_found = FALSE; + IF ( COALESCE(array_length(ina_money_pots_ids, 1), 0) != + COALESCE(array_length(ina_increments, 1), 0) ) + THEN + RAISE EXCEPTION 'Array lengths must match'; + END IF; + FOR i IN 1..COALESCE(array_length(ina_money_pots_ids, 1),0) + LOOP + ini_current_pot_id = ina_money_pots_ids[i]; + ini_current_increment = ina_increments[i]; + SELECT pot_totals + INTO my_totals + FROM merchant_money_pots + WHERE money_pot_serial = ini_current_pot_id; + IF NOT FOUND + THEN + out_not_found = TRUE; + ELSE + currency_found = FALSE; + FOR j IN 1..COALESCE(array_length(my_totals, 1), 0) + LOOP + IF (my_totals[j]).currency = (ini_current_increment).currency + THEN + my_totals[j].frac + = my_totals[j].frac + ini_current_increment.frac; + my_totals[j].val + = my_totals[j].val + ini_current_increment.val; + IF my_totals[j].frac >= 100000000 + THEN + my_totals[j].frac = my_totals[j].frac - 100000000; + my_totals[j].val = my_totals[j].val + 1; + END IF; + currency_found = TRUE; + EXIT; + END IF; + END LOOP; + IF NOT currency_found + THEN + my_totals = array_append(my_totals, ini_current_increment); + END IF; + UPDATE merchant_money_pots + SET pot_totals = my_totals + WHERE money_pot_serial = ini_current_pot_id; + END IF; + END LOOP; + END $BODY$ + $OUTER$, s); + + -- merchant_do_update_money_pot + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_update_money_pot(in_money_pot_serial bigint, in_name text, in_description text, in_old_totals merchant.taler_amount_currency[], in_new_totals merchant.taler_amount_currency[], OUT out_conflict_total boolean, OUT out_conflict_name boolean, OUT out_not_found boolean) RETURNS record + LANGUAGE plpgsql + AS $BODY$ + BEGIN + BEGIN + UPDATE merchant_money_pots SET + money_pot_name=in_name + ,money_pot_description=in_description + ,pot_totals=COALESCE(in_new_totals, pot_totals) + WHERE money_pot_serial=in_money_pot_serial + AND ( (in_old_totals IS NULL) OR (pot_totals=in_old_totals) ); + IF NOT FOUND + THEN + PERFORM FROM merchant_money_pots + WHERE money_pot_serial=in_money_pot_serial; + out_conflict_total = FOUND; + out_not_found = NOT FOUND; + out_conflict_name = FALSE; + ELSE + out_conflict_total = FALSE; + out_not_found = FALSE; + out_conflict_name = FALSE; + END IF; + RETURN; + EXCEPTION + WHEN unique_violation + THEN + out_conflict_name = TRUE; + out_conflict_total = FALSE; + out_not_found = FALSE; + RETURN; + END; + END $BODY$ + $OUTER$, s); + + -- ------------------------------------------------------------------- + -- Deposit confirmations and transfers + -- ------------------------------------------------------------------- + + -- merchant_do_insert_deposit_confirmation + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_insert_deposit_confirmation(in_h_contract_terms bytea, in_deposit_timestamp bigint, in_exchange_url text, in_total_without_fee merchant.taler_amount_currency, in_wire_fee merchant.taler_amount_currency, in_h_wire bytea, in_exchange_sig bytea, in_exchange_pub bytea, in_wire_transfer_deadline bigint, in_notify_arg_str text, OUT out_no_order boolean, OUT out_no_account boolean, OUT out_no_signkey boolean, OUT out_conflict boolean, OUT out_deposit_confirmation_serial bigint) RETURNS record + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_order_serial INT8; + my_account_serial INT8; + my_signkey_serial INT8; + my_record RECORD; + my_bank_serial_id INT8; + my_credit_amount taler_amount_currency; + BEGIN + out_no_order=TRUE; + out_no_account=TRUE; + out_no_signkey=TRUE; + out_conflict=FALSE; + out_deposit_confirmation_serial=0; + SELECT account_serial + INTO my_account_serial + FROM merchant_accounts + WHERE h_wire=in_h_wire; + IF NOT FOUND + THEN + RETURN; + END IF; + out_no_account=FALSE; + SELECT signkey_serial + INTO my_signkey_serial + FROM merchant.merchant_exchange_signing_keys + WHERE exchange_pub=in_exchange_pub + ORDER BY start_date DESC + LIMIT 1; + IF NOT FOUND + THEN + RETURN; + END IF; + out_no_signkey=FALSE; + SELECT order_serial + INTO my_order_serial + FROM merchant_contract_terms + WHERE h_contract_terms=in_h_contract_terms; + IF NOT FOUND + THEN + RETURN; + END IF; + out_no_order=FALSE; + SELECT deposit_confirmation_serial + ,deposit_timestamp + ,exchange_url + ,total_without_fee + ,wire_fee + ,wire_transfer_deadline + ,account_serial + INTO my_record + FROM merchant_deposit_confirmations + WHERE order_serial=my_order_serial + AND exchange_url=in_exchange_url; + IF NOT FOUND + THEN + INSERT INTO merchant_deposit_confirmations + (order_serial + ,deposit_timestamp + ,exchange_url + ,total_without_fee + ,wire_fee + ,exchange_sig + ,wire_transfer_deadline + ,signkey_serial + ,account_serial + ) VALUES ( + my_order_serial + ,in_deposit_timestamp + ,in_exchange_url + ,in_total_without_fee + ,in_wire_fee + ,in_exchange_sig + ,in_wire_transfer_deadline + ,my_signkey_serial + ,my_account_serial + ) RETURNING deposit_confirmation_serial + INTO out_deposit_confirmation_serial; + ELSE + IF (in_deposit_timestamp, + in_wire_transfer_deadline, + in_wire_fee, + my_account_serial) + IS DISTINCT FROM + (my_record.deposit_timestamp, + my_record.wire_transfer_deadline, + my_record.wire_fee, + my_record.account_serial) + THEN + out_conflict = TRUE; + out_deposit_confirmation_serial = my_record.deposit_confirmation_serial; + RETURN; + END IF; + IF ( ((in_total_without_fee).val < (my_record.total_without_fee).val) OR + ( ((in_total_without_fee).val = (my_record.total_without_fee).val) AND + ((in_total_without_fee).frac <= (my_record.total_without_fee).frac) ) ) + THEN + out_deposit_confirmation_serial = my_record.deposit_confirmation_serial; + RETURN; + END IF; + UPDATE merchant_deposit_confirmations + SET total_without_fee = in_total_without_fee + ,exchange_sig = in_exchange_sig + ,signkey_serial = my_signkey_serial; + out_deposit_confirmation_serial = my_record.deposit_confirmation_serial; + END IF; + PERFORM pg_notify ('XBZ19D98AK2REYNX93F736A56MT14SCY2EEX7XNXQMNCQ01B121R0', + in_notify_arg_str); + END $BODY$ + $OUTER$, s); + + -- merchant_insert_deposit_to_transfer + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_insert_deposit_to_transfer(in_deposit_serial bigint, in_coin_contribution merchant.taler_amount_currency, in_execution_time bigint, in_exchange_url text, in_h_wire bytea, in_exchange_sig bytea, in_exchange_pub bytea, in_wtid bytea, OUT out_dummy boolean) RETURNS boolean + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_signkey_serial INT8; + my_account_serial INT8; + my_decose INT8; + my_expected_credit_serial INT8; + my_wire_pending_cleared BOOL; + BEGIN + out_dummy=FALSE; + SELECT signkey_serial + INTO my_signkey_serial + FROM merchant.merchant_exchange_signing_keys + WHERE exchange_pub=in_exchange_pub + ORDER BY start_date DESC + LIMIT 1; + IF NOT FOUND + THEN + UPDATE merchant_deposits + SET settlement_last_ec=2029 + ,settlement_last_http_status=200 + ,settlement_last_detail=ENCODE(in_exchange_pub, 'hex') + ,settlement_wtid=in_wtid + ,settlement_retry_needed=TRUE + ,settlement_retry_time=(EXTRACT(epoch FROM (CURRENT_TIME + interval '8 hours')) * 1000000)::INT8 + WHERE deposit_serial=in_deposit_serial; + RETURN; + END IF; + SELECT deposit_confirmation_serial + INTO my_decose + FROM merchant_deposits + WHERE deposit_serial=in_deposit_serial; + SELECT account_serial + INTO my_account_serial + FROM merchant_deposit_confirmations mdc + JOIN merchant_accounts ma + USING (account_serial) + WHERE mdc.deposit_confirmation_serial=my_decose + AND ma.h_wire=in_h_wire; + IF NOT FOUND + THEN + UPDATE merchant_deposits + SET settlement_last_ec=2558 + ,settlement_last_http_status=200 + ,settlement_last_detail=ENCODE(in_h_wire, 'hex') + ,settlement_wtid=in_wtid + ,settlement_retry_needed=FALSE + ,settlement_coin_contribution=in_coin_contribution + ,signkey_serial=my_signkey_serial + ,settlement_exchange_sig=in_exchange_sig + WHERE deposit_serial=in_deposit_serial; + RETURN; + END IF; + SELECT expected_credit_serial + INTO my_expected_credit_serial + FROM merchant_expected_transfers + WHERE wtid=in_wtid + AND exchange_url=in_exchange_url + AND account_serial=my_account_serial; + IF NOT FOUND + THEN + INSERT INTO merchant_expected_transfers + (exchange_url + ,wtid + ,account_serial + ,expected_time) + VALUES + (in_exchange_url + ,in_wtid + ,my_account_serial + ,in_execution_time) + RETURNING expected_credit_serial + INTO my_expected_credit_serial; + END IF; + UPDATE merchant_deposits + SET settlement_last_ec=0 + ,settlement_last_http_status=200 + ,settlement_last_detail=NULL + ,settlement_wtid=in_wtid + ,settlement_retry_needed=FALSE + ,settlement_coin_contribution=in_coin_contribution + ,settlement_expected_credit_serial=my_expected_credit_serial + ,signkey_serial=my_signkey_serial + ,settlement_exchange_sig=in_exchange_sig + WHERE deposit_serial=in_deposit_serial; + NOTIFY XR6849FMRD2AJFY1E2YY0GWA8GN0YT407Z66WHJB0SAKJWF8G2Q60; + END $BODY$ + $OUTER$, s); + + -- ------------------------------------------------------------------- + -- Tokens + -- ------------------------------------------------------------------- + + -- merchant_do_insert_issued_token + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_insert_issued_token(in_h_issue_pub bytea, in_h_contract_terms bytea, in_blind_sig bytea, OUT out_no_family boolean, OUT out_existed boolean) RETURNS record + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_rec RECORD; + my_tfk_serial INT8; + my_tf_serial INT8; + BEGIN + SELECT token_family_key_serial + ,token_family_serial + INTO my_rec + FROM merchant_token_family_keys + WHERE h_pub = in_h_issue_pub; + IF NOT FOUND + THEN + out_no_family = TRUE; + out_existed = FALSE; + return; + END IF; + my_tfk_serial = my_rec.token_family_key_serial; + my_tf_serial = my_rec.token_family_serial; + out_no_family = FALSE; + INSERT INTO merchant_issued_tokens + (token_family_key_serial + ,h_contract_terms + ,blind_sig + ) VALUES + (my_tfk_serial + ,in_h_contract_terms + ,in_blind_sig) + ON CONFLICT DO NOTHING; + IF NOT FOUND + THEN + out_existed = TRUE; + return; + END IF; + out_existed = FALSE; + UPDATE merchant_token_families + SET issued=issued+1 + WHERE token_family_serial=my_tf_serial; + END $BODY$ + $OUTER$, s); + + -- merchant_do_insert_spent_token + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_insert_spent_token(in_h_contract_terms bytea, in_h_issue_pub bytea, in_use_pub bytea, in_use_sig bytea, in_issue_sig bytea, OUT out_no_family boolean, OUT out_conflict boolean) RETURNS record + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_rec RECORD; + my_tfk_serial INT8; + my_tf_serial INT8; + BEGIN + SELECT token_family_key_serial + ,token_family_serial + INTO my_rec + FROM merchant_token_family_keys + WHERE h_pub = in_h_issue_pub; + IF NOT FOUND + THEN + out_no_family = TRUE; + out_conflict = FALSE; + return; + END IF; + out_no_family = FALSE; + my_tfk_serial = my_rec.token_family_key_serial; + my_tf_serial = my_rec.token_family_serial; + INSERT INTO merchant_used_tokens + (token_family_key_serial + ,h_contract_terms + ,token_pub + ,token_sig + ,blind_sig + ) VALUES + (my_tfk_serial + ,in_h_contract_terms + ,in_use_pub + ,in_use_sig + ,in_issue_sig) + ON CONFLICT DO NOTHING; + IF NOT FOUND + THEN + PERFORM FROM merchant_used_tokens + WHERE token_family_key_serial=my_tfk_serial + AND h_contract_terms=in_h_contract_terms + AND token_pub=in_use_pub + AND token_sig=in_use_sig + AND blind_sig=in_issue_sig; + out_conflict = NOT FOUND; + return; + END IF; + out_conflict = FALSE; + UPDATE merchant_token_families + SET used=used+1 + WHERE token_family_serial=my_tf_serial; + END $BODY$ + $OUTER$, s); + + -- ------------------------------------------------------------------- + -- Products and product groups + -- ------------------------------------------------------------------- + + -- merchant_do_insert_product + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_insert_product(in_product_id text, in_description text, in_description_i18n jsonb, in_unit text, in_image text, in_taxes jsonb, ina_price_list merchant.taler_amount_currency[], in_total_stock bigint, in_total_stock_frac integer, in_allow_fractional_quantity boolean, in_fractional_precision_level integer, in_address jsonb, in_next_restock bigint, in_minimum_age integer, ina_categories bigint[], in_product_name text, in_product_group_id bigint, in_money_pot_id bigint, in_price_is_net boolean, OUT out_conflict boolean, OUT out_no_cat bigint, OUT out_no_group boolean, OUT out_no_pot boolean) RETURNS record + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_product_serial INT8; + i INT8; + ini_cat INT8; + BEGIN + out_no_group = FALSE; + out_no_pot = FALSE; + IF in_product_group_id IS NOT NULL + THEN + PERFORM FROM merchant_product_groups + WHERE product_group_serial=in_product_group_id; + IF NOT FOUND + THEN + out_no_group=TRUE; + out_conflict=FALSE; + out_no_cat=NULL; + RETURN; + END IF; + END IF; + IF in_money_pot_id IS NOT NULL + THEN + PERFORM FROM merchant_money_pots + WHERE money_pot_serial=in_money_pot_id; + IF NOT FOUND + THEN + out_no_pot=TRUE; + out_conflict=FALSE; + out_no_cat=NULL; + RETURN; + END IF; + END IF; + INSERT INTO merchant_inventory + (product_id + ,product_name + ,description + ,description_i18n + ,unit + ,image + ,image_hash + ,taxes + ,price_array + ,total_stock + ,total_stock_frac + ,allow_fractional_quantity + ,fractional_precision_level + ,address + ,next_restock + ,minimum_age + ,product_group_serial + ,money_pot_serial + ,price_is_net + ) VALUES ( + in_product_id + ,in_product_name + ,in_description + ,in_description_i18n + ,in_unit + ,in_image + ,CASE + WHEN (in_image IS NULL) OR (in_image = '') + THEN NULL + ELSE encode(public.digest(convert_to(in_image, 'UTF8'), + 'sha256'), + 'hex') + END + ,in_taxes + ,ina_price_list + ,in_total_stock + ,in_total_stock_frac + ,in_allow_fractional_quantity + ,in_fractional_precision_level + ,in_address + ,in_next_restock + ,in_minimum_age + ,in_product_group_id + ,in_money_pot_id + ,in_price_is_net + ) + ON CONFLICT (product_id) DO NOTHING + RETURNING product_serial + INTO my_product_serial; + IF NOT FOUND + THEN + SELECT product_serial + INTO my_product_serial + FROM merchant_inventory + WHERE product_id=in_product_id + AND product_name=in_product_name + AND description=in_description + AND description_i18n=in_description_i18n + AND unit=in_unit + AND image=in_image + AND taxes=in_taxes + AND to_jsonb(COALESCE(price_array, ARRAY[]::taler_amount_currency[])) + = to_jsonb(COALESCE(ina_price_list, ARRAY[]::taler_amount_currency[])) + AND total_stock=in_total_stock + AND total_stock_frac=in_total_stock_frac + AND allow_fractional_quantity=in_allow_fractional_quantity + AND fractional_precision_level=in_fractional_precision_level + AND address=in_address + AND next_restock=in_next_restock + AND minimum_age=in_minimum_age + AND product_group_serial IS NOT DISTINCT FROM in_product_group_id + AND money_pot_serial IS NOT DISTINCT FROM in_money_pot_id + AND price_is_net=in_price_is_net; + IF NOT FOUND + THEN + out_conflict=TRUE; + out_no_cat=NULL; + RETURN; + END IF; + FOR i IN 1..COALESCE(array_length(ina_categories,1),0) + LOOP + ini_cat=ina_categories[i]; + PERFORM + FROM merchant_product_categories + WHERE product_serial=my_product_serial + AND category_serial=ini_cat; + IF NOT FOUND + THEN + out_conflict=TRUE; + out_no_cat=NULL; + RETURN; + END IF; + END LOOP; + SELECT COUNT(*) + INTO i + FROM merchant_product_categories + WHERE product_serial=my_product_serial; + IF i != COALESCE(array_length(ina_categories,1),0) + THEN + out_conflict=TRUE; + out_no_cat=NULL; + RETURN; + END IF; + out_conflict=FALSE; + out_no_cat=NULL; + RETURN; + END IF; + out_conflict=FALSE; + FOR i IN 1..COALESCE(array_length(ina_categories,1),0) + LOOP + ini_cat=ina_categories[i]; + INSERT INTO merchant_product_categories + (product_serial + ,category_serial) + VALUES + (my_product_serial + ,ini_cat) + ON CONFLICT DO NOTHING; + IF NOT FOUND + THEN + out_no_cat=i; + RETURN; + END IF; + END LOOP; + out_no_cat=NULL; + END $BODY$ + $OUTER$, s); + + -- merchant_do_update_product + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_update_product(in_product_id text, in_description text, in_description_i18n jsonb, in_unit text, in_image text, in_taxes jsonb, ina_price_list merchant.taler_amount_currency[], in_total_stock bigint, in_total_stock_frac integer, in_allow_fractional_quantity boolean, in_fractional_precision_level integer, in_total_lost bigint, in_address jsonb, in_next_restock bigint, in_minimum_age integer, ina_categories bigint[], in_product_name text, in_product_group_id bigint, in_money_pot_id bigint, in_price_is_net boolean, OUT out_no_product boolean, OUT out_lost_reduced boolean, OUT out_sold_reduced boolean, OUT out_stocked_reduced boolean, OUT out_no_cat bigint, OUT out_no_group boolean, OUT out_no_pot boolean) RETURNS record + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_product_serial INT8; + i INT8; + ini_cat INT8; + rec RECORD; + BEGIN + out_no_group = FALSE; + out_no_pot = FALSE; + out_no_product=FALSE; + out_lost_reduced=FALSE; + out_sold_reduced=FALSE; + out_stocked_reduced=FALSE; + out_no_cat=NULL; + IF in_product_group_id IS NOT NULL + THEN + PERFORM FROM merchant_product_groups + WHERE product_group_serial=in_product_group_id; + IF NOT FOUND + THEN + out_no_group=TRUE; + RETURN; + END IF; + END IF; + IF in_money_pot_id IS NOT NULL + THEN + PERFORM FROM merchant_money_pots + WHERE money_pot_serial=in_money_pot_id; + IF NOT FOUND + THEN + out_no_pot=TRUE; + RETURN; + END IF; + END IF; + SELECT total_stock + ,total_stock_frac + ,total_lost + ,allow_fractional_quantity + ,product_serial + INTO rec + FROM merchant_inventory + WHERE product_id=in_product_id; + IF NOT FOUND + THEN + out_no_product=TRUE; + RETURN; + END IF; + my_product_serial = rec.product_serial; + IF rec.total_stock > in_total_stock + THEN + out_stocked_reduced=TRUE; + RETURN; + END IF; + IF rec.total_lost > in_total_lost + THEN + out_lost_reduced=TRUE; + RETURN; + END IF; + IF rec.allow_fractional_quantity + AND (NOT in_allow_fractional_quantity) + THEN + DELETE + FROM merchant_inventory_locks + WHERE product_serial = my_product_serial + AND total_locked_frac <> 0; + END IF; + DELETE FROM merchant_product_categories + WHERE product_serial=my_product_serial; + FOR i IN 1..COALESCE(array_length(ina_categories,1),0) + LOOP + ini_cat=ina_categories[i]; + INSERT INTO merchant_product_categories + (product_serial + ,category_serial) + VALUES + (my_product_serial + ,ini_cat) + ON CONFLICT DO NOTHING; + IF NOT FOUND + THEN + out_no_cat=i; + RETURN; + END IF; + END LOOP; + UPDATE merchant_inventory SET + description=in_description + ,description_i18n=in_description_i18n + ,product_name=in_product_name + ,unit=in_unit + ,image=in_image + ,image_hash=CASE + WHEN (in_image IS NULL) OR (in_image = '') + THEN NULL + ELSE encode(public.digest(convert_to(in_image, 'UTF8'), + 'sha256'), + 'hex') + END + ,taxes=in_taxes + ,price_array=ina_price_list + ,total_stock=in_total_stock + ,total_stock_frac=in_total_stock_frac + ,allow_fractional_quantity=in_allow_fractional_quantity + ,fractional_precision_level=in_fractional_precision_level + ,total_lost=in_total_lost + ,address=in_address + ,next_restock=in_next_restock + ,minimum_age=in_minimum_age + ,product_group_serial=in_product_group_id + ,money_pot_serial=in_money_pot_id + ,price_is_net=in_price_is_net + WHERE product_serial=my_product_serial; + ASSERT FOUND,'SELECTED it earlier, should UPDATE it now'; + END $BODY$ + $OUTER$, s); + + -- merchant_do_update_product_group + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_update_product_group(in_product_group_serial text, in_name text, in_description text, OUT out_conflict boolean, OUT out_not_found boolean) RETURNS record + LANGUAGE plpgsql + AS $BODY$ + BEGIN + BEGIN + UPDATE merchant_product_groups SET + product_group_name=in_name + ,product_group_description=in_description + WHERE product_group_serial=in_product_group_serial; + out_not_found = NOT FOUND; + out_conflict = FALSE; + RETURN; + EXCEPTION + WHEN unique_violation + THEN + out_conflict = TRUE; + RETURN; + END; + END $BODY$ + $OUTER$, s); + + -- ------------------------------------------------------------------- + -- Transfers + -- ------------------------------------------------------------------- + + -- merchant_do_insert_transfer + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_insert_transfer(in_exchange_url text, in_wtid bytea, in_credit_amount merchant.taler_amount_currency, in_credited_account_payto text, in_bank_serial_id bigint, in_execution_time bigint, OUT out_no_account boolean, OUT out_conflict boolean) RETURNS record + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_account_serial INT8; + my_record RECORD; + my_bank_serial_id INT8; + my_credit_amount taler_amount_currency; + BEGIN + out_conflict=FALSE; + SELECT account_serial + INTO my_account_serial + FROM merchant_accounts + WHERE REGEXP_REPLACE(payto_uri, + '\\?.*','') + =REGEXP_REPLACE(in_credited_account_payto, + '\\?.*',''); + IF NOT FOUND + THEN + out_no_account=TRUE; + RETURN; + END IF; + out_no_account=FALSE; + SELECT bank_serial_id + ,credit_amount + INTO my_record + FROM merchant_transfers + WHERE wtid=in_wtid + AND account_serial=my_account_serial + AND exchange_url=in_exchange_url; + IF NOT FOUND + THEN + INSERT INTO merchant_transfers + (exchange_url + ,wtid + ,credit_amount + ,account_serial + ,bank_serial_id + ,execution_time + ) VALUES + (in_exchange_url + ,in_wtid + ,in_credit_amount + ,my_account_serial + ,in_bank_serial_id + ,in_execution_time); + NOTIFY XJ5N652FA4TBS2WXGY3S1FMPMQYTD8KAZA9B7HW9JWJ4PZ2DB852G; + RETURN; + END IF; + my_bank_serial_id = my_record.bank_serial_id; + my_credit_amount = my_record.credit_amount; + IF ( (in_credit_amount.val != my_credit_amount.val) OR + (in_credit_amount.frac != my_credit_amount.frac) OR + (in_credit_amount.curr != my_credit_amount.curr) ) + THEN + out_conflict = TRUE; + RETURN; + END IF; + IF ( (my_bank_serial_id IS NULL) AND + (in_bank_serial_id IS NOT NULL) ) + THEN + UPDATE merchant_transfers + SET bank_serial_id=in_bank_serial_id + WHERE wtid=in_wtid + AND account_serial=my_account_serial + AND exchange_url=in_exchange_url; + RETURN; + END IF; + END $BODY$ + $OUTER$, s); + + -- merchant_do_insert_transfer_details + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_insert_transfer_details(in_exchange_url text, in_payto_uri text, in_wtid bytea, in_execution_time bigint, in_exchange_pub bytea, in_exchange_sig bytea, in_total_amount merchant.taler_amount_currency, in_wire_fee merchant.taler_amount_currency, ina_coin_values merchant.taler_amount_currency[], ina_deposit_fees merchant.taler_amount_currency[], ina_coin_pubs bytea[], ina_contract_terms bytea[], OUT out_no_account boolean, OUT out_no_exchange boolean, OUT out_duplicate boolean, OUT out_conflict boolean, OUT out_order_id text) RETURNS record + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_signkey_serial INT8; + my_expected_credit_serial INT8; + my_affected_orders RECORD; + my_decose INT8; + my_order_id TEXT; + i INT8; + curs CURSOR (arg_coin_pub BYTEA) FOR + SELECT mcon.deposit_confirmation_serial, + mcon.order_serial + FROM merchant_deposits dep + JOIN merchant_deposit_confirmations mcon + USING (deposit_confirmation_serial) + WHERE dep.coin_pub=arg_coin_pub; + ini_coin_pub BYTEA; + ini_contract_term BYTEA; + ini_coin_value taler_amount_currency; + ini_deposit_fee taler_amount_currency; + BEGIN + SELECT expected_credit_serial + INTO my_expected_credit_serial + FROM merchant_expected_transfers + WHERE exchange_url=in_exchange_url + AND wtid=in_wtid + AND account_serial= + (SELECT account_serial + FROM merchant_accounts + WHERE payto_uri=in_payto_uri + AND exchange_url=in_exchange_url); + IF NOT FOUND + THEN + out_no_account=TRUE; + out_no_exchange=FALSE; + out_duplicate=FALSE; + out_conflict=FALSE; + RETURN; + END IF; + out_no_account=FALSE; + SELECT signkey_serial + INTO my_signkey_serial + FROM merchant.merchant_exchange_signing_keys + WHERE exchange_pub=in_exchange_pub + ORDER BY start_date DESC + LIMIT 1; + IF NOT FOUND + THEN + out_no_exchange=TRUE; + out_conflict=FALSE; + out_duplicate=FALSE; + RETURN; + END IF; + out_no_exchange=FALSE; + INSERT INTO merchant_transfer_signatures + (expected_credit_serial + ,signkey_serial + ,credit_amount + ,wire_fee + ,execution_time + ,exchange_sig) + VALUES + (my_expected_credit_serial + ,my_signkey_serial + ,in_total_amount + ,in_wire_fee + ,in_execution_time + ,in_exchange_sig) + ON CONFLICT DO NOTHING; + IF NOT FOUND + THEN + PERFORM 1 + FROM merchant_transfer_signatures + WHERE expected_credit_serial=my_expected_credit_serial + AND signkey_serial=my_signkey_serial + AND credit_amount=in_total_amount + AND wire_fee=in_wire_fee + AND execution_time=in_execution_time + AND exchange_sig=in_exchange_sig; + IF FOUND + THEN + out_duplicate=TRUE; + out_conflict=FALSE; + RETURN; + END IF; + out_duplicate=FALSE; + out_conflict=TRUE; + RETURN; + END IF; + out_duplicate=FALSE; + out_conflict=FALSE; + FOR i IN 1..array_length(ina_coin_pubs,1) + LOOP + ini_coin_value=ina_coin_values[i]; + ini_deposit_fee=ina_deposit_fees[i]; + ini_coin_pub=ina_coin_pubs[i]; + ini_contract_term=ina_contract_terms[i]; + INSERT INTO merchant_expected_transfer_to_coin + (deposit_serial + ,expected_credit_serial + ,offset_in_exchange_list + ,exchange_deposit_value + ,exchange_deposit_fee) + SELECT + dep.deposit_serial + ,my_expected_credit_serial + ,i + ,ini_coin_value + ,ini_deposit_fee + FROM merchant_deposits dep + JOIN merchant_deposit_confirmations dcon + USING (deposit_confirmation_serial) + JOIN merchant_contract_terms cterm + USING (order_serial) + WHERE dep.coin_pub=ini_coin_pub + AND cterm.h_contract_terms=ini_contract_term; + RAISE NOTICE 'iterating over affected orders'; + OPEN curs (arg_coin_pub:=ini_coin_pub); + LOOP + FETCH NEXT FROM curs INTO my_affected_orders; + EXIT WHEN NOT FOUND; + RAISE NOTICE 'checking affected order for completion'; + my_decose=my_affected_orders.deposit_confirmation_serial; + PERFORM FROM merchant_deposits md + WHERE md.deposit_confirmation_serial=my_decose + AND settlement_retry_needed + OR settlement_wtid IS NULL; + IF NOT FOUND + THEN + UPDATE merchant_deposit_confirmations + SET wire_pending=FALSE + WHERE (deposit_confirmation_serial=my_decose); + IF FOUND + THEN + RAISE NOTICE 'checking affected contract for completion'; + PERFORM FROM merchant_deposit_confirmations mdc + WHERE mdc.wire_pending + AND mdc.order_serial=my_affected_orders.order_serial; + IF NOT FOUND + THEN + UPDATE merchant_contract_terms + SET wired=TRUE + WHERE (order_serial=my_affected_orders.order_serial); + SELECT order_id + INTO my_order_id + FROM merchant_contract_terms + WHERE order_serial=my_affected_orders.order_serial; + out_order_id = my_order_id; + INSERT INTO merchant_pending_webhooks + (webhook_serial + ,url + ,http_method + ,header + ,body) + SELECT mw.webhook_serial + ,mw.url + ,mw.http_method + ,merchant.replace_placeholder( + merchant.replace_placeholder(mw.header_template, 'order_id', my_order_id), + 'wtid', encode(in_wtid, 'hex') + )::TEXT + ,merchant.replace_placeholder( + merchant.replace_placeholder(mw.body_template, 'order_id', my_order_id), + 'wtid', encode(in_wtid, 'hex') + )::TEXT + FROM merchant_webhook mw + WHERE mw.event_type = 'order_settled'; + IF FOUND + THEN + NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG; + END IF; + END IF; + END IF; + END IF; + END LOOP; + CLOSE curs; + END LOOP; + END $BODY$ + $OUTER$, s); + + -- ------------------------------------------------------------------- + -- TAN / MFA challenges + -- ------------------------------------------------------------------- + + -- merchant_do_solve_mfa_challenge + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_do_solve_mfa_challenge(in_challenge_id bigint, in_h_body bytea, in_solution text, in_now bigint, OUT out_solved boolean, OUT out_retry_counter integer) RETURNS record + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_confirmation_date INT8; + DECLARE + my_rec RECORD; + BEGIN + SELECT + tc.confirmation_date + ,tc.retry_counter + ,(tc.code = in_solution) AS solved + INTO + my_rec + FROM tan_challenges tc + WHERE tc.challenge_id = in_challenge_id + AND tc.h_body = in_h_body + AND tc.expiration_date > in_now; + IF NOT FOUND + THEN + out_solved = FALSE; + RETURN; + END IF; + my_confirmation_date = my_rec.confirmation_date; + out_retry_counter = my_rec.retry_counter; + out_solved = my_rec.solved; + IF my_confirmation_date IS NOT NULL + THEN + out_solved = TRUE; + RETURN; + END IF; + IF (0 = out_retry_counter) + THEN + out_solved = FALSE; + RETURN; + END IF; + IF out_solved + THEN + my_confirmation_date = in_now; + UPDATE tan_challenges + SET confirmation_date = my_confirmation_date + WHERE challenge_id = in_challenge_id; + ELSE + out_retry_counter = out_retry_counter - 1; + UPDATE tan_challenges + SET retry_counter = out_retry_counter + WHERE challenge_id = in_challenge_id; + END IF; + END; + $BODY$ + $OUTER$, s); + + -- ------------------------------------------------------------------- + -- Statistic interval getters + -- ------------------------------------------------------------------- + + -- merchant_statistic_interval_amount_get + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_statistic_interval_amount_get(in_slug text) RETURNS SETOF merchant.merchant_statistic_interval_amount_get_return_value + LANGUAGE plpgsql + AS $BODY$ + 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_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.merchant_statistic_interval_amount_get_return_value; + BEGIN + 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 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; + 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 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 + 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; + SELECT aevent_serial_id + INTO my_next_max_serial + FROM merchant_statistic_amount_event + WHERE imeta_serial_id = my_meta + 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 + 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 curr = my_currency + AND range = my_range; + ELSE + DELETE FROM merchant_statistic_interval_amount + WHERE imeta_serial_id = my_meta + AND curr = my_currency + AND range = my_range; + END IF; + IF (my_i < COALESCE(array_length(my_ranges,1),0)) + THEN + 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_frac + - 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 range=my_ranges[my_i+1]; + IF NOT FOUND + THEN + INSERT INTO merchant_statistic_interval_amount + (imeta_serial_id + ,event_delimiter + ,range + ,curr + ,cumulative_value + ,cumulative_frac + ) VALUES ( + my_meta + ,my_min_serial + ,my_ranges[my_i+1] + ,my_currency + ,my_delta_value + ,my_delta_frac); + END IF; + ELSE + DELETE FROM merchant_statistic_amount_event + WHERE imeta_serial_id = my_meta + AND slot < my_time - my_range; + END IF; + END IF; + my_rval.range = my_range; + RETURN NEXT my_rval; + END IF; + END LOOP; + END LOOP; + END $BODY$ + $OUTER$, s); + + -- merchant_statistic_interval_number_get + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_statistic_interval_number_get(in_slug text) RETURNS SETOF merchant.merchant_statistic_interval_number_get_return_value + LANGUAGE plpgsql + AS $BODY$ + 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_rec RECORD; + my_irec RECORD; + my_i INT; + my_min_serial INT8 DEFAULT NULL; + my_rval merchant.merchant_statistic_interval_number_get_return_value; + BEGIN + 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; + IF FOUND + THEN + my_min_serial = my_irec.event_delimiter; + my_rval.rvalue = my_rval.rvalue + my_irec.cumulative_number; + SELECT SUM(delta) AS delta_sum + INTO my_irec + FROM merchant_statistic_counter_event + WHERE imeta_serial_id = my_meta + 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; + SELECT nevent_serial_id + INTO my_next_max_serial + FROM merchant_statistic_counter_event + WHERE imeta_serial_id = my_meta + AND slot >= my_time - my_range + AND nevent_serial_id >= my_min_serial + ORDER BY slot ASC + LIMIT 1; + IF FOUND + THEN + 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 range = my_range; + ELSE + DELETE FROM merchant_statistic_interval_counter + WHERE imeta_serial_id = my_meta + AND range = my_range; + END IF; + IF (my_i < COALESCE(array_length(my_ranges,1),0)) + THEN + 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 range=my_ranges[my_i+1]; + IF NOT FOUND + THEN + INSERT INTO merchant_statistic_interval_counter + (imeta_serial_id + ,range + ,event_delimiter + ,cumulative_number + ) VALUES ( + my_meta + ,my_ranges[my_i+1] + ,my_min_serial + ,my_delta); + END IF; + ELSE + DELETE FROM merchant_statistic_counter_event + WHERE imeta_serial_id = my_meta + AND slot < my_time - my_range; + END IF; + END IF; + my_rval.range = my_range; + RETURN NEXT my_rval; + END IF; + END LOOP; + END $BODY$ + $OUTER$, s); diff --git a/src/backenddb/pg_create_instance_schema_tables.sql.fragment b/src/backenddb/pg_create_instance_schema_tables.sql.fragment @@ -0,0 +1,961 @@ +-- ===================================================================== +-- Per-instance schema constructor: tables, indexes, FKs. +-- +-- This fragment is embedded inside a PL/pgSQL function whose local +-- variable `s TEXT` already holds the schema name string +-- 'merchant_instance_<merchant_serial>'. Every DDL is issued via +-- EXECUTE format(...) with %I quoting on the schema name(s). +-- +-- Triggers, sequence resets, stored procedures and the merchant.* FK +-- back-references to merchant_instances are intentionally omitted. +-- ===================================================================== + + -- ------------------------------------------------------------------- + -- 1. Create the per-instance schema. + -- ------------------------------------------------------------------- + EXECUTE format('CREATE SCHEMA %I', s); + + -- ------------------------------------------------------------------- + -- 2. CREATE TABLE statements for all per-instance tables. + -- The merchant_serial column has been dropped everywhere; PK, + -- UNIQUE and CHECK constraints have been adjusted accordingly. + -- ------------------------------------------------------------------- + + -- merchant_accounts + EXECUTE format('CREATE TABLE %I.merchant_accounts (' + || ' account_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' h_wire bytea NOT NULL UNIQUE,' + || ' salt bytea NOT NULL,' + || ' credit_facade_url text,' + || ' credit_facade_credentials jsonb,' + || ' last_bank_serial bigint DEFAULT 0 NOT NULL,' + || ' payto_uri text NOT NULL UNIQUE,' + || ' active boolean NOT NULL,' + || ' extra_wire_subject_metadata text,' + || ' CONSTRAINT merchant_accounts_h_wire_check CHECK ((length(h_wire) = 64)),' + || ' CONSTRAINT merchant_accounts_salt_check CHECK ((length(salt) = 16))' + || ')', s); + + -- merchant_builtin_unit_overrides + EXECUTE format('CREATE TABLE %I.merchant_builtin_unit_overrides (' + || ' builtin_unit_serial bigint NOT NULL PRIMARY KEY,' + || ' override_allow_fraction boolean,' + || ' override_precision_level integer,' + || ' override_active boolean,' + || ' CONSTRAINT merchant_builtin_unit_overrides_override_precision_level_check' + || ' CHECK (((override_precision_level >= 0) AND (override_precision_level <= 6)))' + || ')', s); + + -- merchant_categories + EXECUTE format('CREATE TABLE %I.merchant_categories (' + || ' category_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' category_name text NOT NULL UNIQUE,' + || ' category_name_i18n jsonb NOT NULL' + || ')', s); + + -- merchant_contract_terms + EXECUTE format('CREATE TABLE %I.merchant_contract_terms (' + || ' order_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' order_id text NOT NULL UNIQUE,' + || ' contract_terms jsonb NOT NULL,' + || ' wallet_data text,' + || ' h_contract_terms bytea NOT NULL UNIQUE,' + || ' creation_time bigint NOT NULL,' + || ' pay_deadline bigint NOT NULL,' + || ' refund_deadline bigint NOT NULL,' + || ' paid boolean DEFAULT false NOT NULL,' + || ' wired boolean DEFAULT false NOT NULL,' + || ' fulfillment_url text,' + || $DDL$ session_id text DEFAULT ''::text NOT NULL,$DDL$ + || ' pos_key text,' + || ' pos_algorithm integer DEFAULT 0 NOT NULL,' + || ' claim_token bytea NOT NULL,' + || ' choice_index smallint,' + || ' CONSTRAINT merchant_contract_terms_claim_token_check CHECK ((length(claim_token) = 16)),' + || ' CONSTRAINT merchant_contract_terms_h_contract_terms_check CHECK ((length(h_contract_terms) = 64))' + || ')', s); + + -- merchant_custom_units + EXECUTE format($DDL$CREATE TABLE %I.merchant_custom_units ( + unit_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + unit text NOT NULL UNIQUE, + unit_name_long text NOT NULL, + unit_name_short text NOT NULL, + unit_name_long_i18n bytea DEFAULT convert_to('{}'::text, 'UTF8'::name) NOT NULL, + unit_name_short_i18n bytea DEFAULT convert_to('{}'::text, 'UTF8'::name) NOT NULL, + unit_allow_fraction boolean DEFAULT false NOT NULL, + unit_precision_level integer DEFAULT 0 NOT NULL, + unit_active boolean DEFAULT true NOT NULL, + CONSTRAINT merchant_custom_units_unit_precision_level_check + CHECK (((unit_precision_level >= 0) AND (unit_precision_level <= 6))) + )$DDL$, s); + + -- merchant_deposit_confirmations + EXECUTE format('CREATE TABLE %I.merchant_deposit_confirmations (' + || ' deposit_confirmation_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' order_serial bigint,' + || ' deposit_timestamp bigint NOT NULL,' + || ' exchange_url text NOT NULL,' + || ' total_without_fee merchant.taler_amount_currency NOT NULL,' + || ' wire_fee merchant.taler_amount_currency NOT NULL,' + || ' signkey_serial bigint NOT NULL,' + || ' exchange_sig bytea NOT NULL,' + || ' account_serial bigint NOT NULL,' + || ' wire_transfer_deadline bigint DEFAULT 0 NOT NULL,' + || ' wire_pending boolean DEFAULT true NOT NULL,' + || ' exchange_failure text,' + || ' retry_backoff bigint DEFAULT 0 NOT NULL,' + || ' CONSTRAINT merchant_deposit_confirmations_exchange_sig_check CHECK ((length(exchange_sig) = 64)),' + || ' UNIQUE (order_serial, exchange_sig)' + || ')', s); + + -- merchant_deposits + EXECUTE format('CREATE TABLE %I.merchant_deposits (' + || ' deposit_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' coin_offset integer NOT NULL,' + || ' deposit_confirmation_serial bigint NOT NULL,' + || ' coin_pub bytea NOT NULL,' + || ' coin_sig bytea NOT NULL,' + || ' amount_with_fee merchant.taler_amount_currency NOT NULL,' + || ' deposit_fee merchant.taler_amount_currency NOT NULL,' + || ' refund_fee merchant.taler_amount_currency NOT NULL,' + || ' settlement_retry_needed boolean DEFAULT true,' + || ' settlement_retry_time bigint DEFAULT 0,' + || ' settlement_last_http_status integer,' + || ' settlement_last_ec integer,' + || ' settlement_last_detail text,' + || ' settlement_wtid bytea,' + || ' settlement_coin_contribution merchant.taler_amount_currency,' + || ' settlement_expected_credit_serial bigint,' + || ' signkey_serial bigint,' + || ' settlement_exchange_sig bytea,' + || ' CONSTRAINT merchant_deposits_coin_pub_check CHECK ((length(coin_pub) = 32)),' + || ' CONSTRAINT merchant_deposits_coin_sig_check CHECK ((length(coin_sig) = 64)),' + || ' CONSTRAINT merchant_deposits_settlement_exchange_sig_check CHECK ((length(settlement_exchange_sig) = 64)),' + || ' CONSTRAINT merchant_deposits_settlement_wtid_check CHECK ((length(settlement_wtid) = 32)),' + || ' UNIQUE (deposit_confirmation_serial, coin_pub)' + || ')', s); + + -- merchant_donau_instances + EXECUTE format('CREATE TABLE %I.merchant_donau_instances (' + || ' donau_instances_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' donau_url text NOT NULL,' + || ' charity_name text NOT NULL,' + || ' charity_id bigint NOT NULL,' + || ' charity_max_per_year merchant.taler_amount_currency NOT NULL,' + || ' charity_receipts_to_date merchant.taler_amount_currency NOT NULL,' + || ' current_year bigint NOT NULL,' + || ' UNIQUE (donau_url, charity_id)' + || ')', s); + + -- merchant_expected_transfer_to_coin + EXECUTE format('CREATE TABLE %I.merchant_expected_transfer_to_coin (' + || ' deposit_serial bigint NOT NULL UNIQUE,' + || ' expected_credit_serial bigint NOT NULL,' + || ' offset_in_exchange_list bigint NOT NULL,' + || ' exchange_deposit_value merchant.taler_amount_currency NOT NULL,' + || ' exchange_deposit_fee merchant.taler_amount_currency NOT NULL' + || ')', s); + + -- merchant_expected_transfers + EXECUTE format('CREATE TABLE %I.merchant_expected_transfers (' + || ' expected_credit_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' exchange_url text NOT NULL,' + || ' wtid bytea NOT NULL,' + || ' expected_credit_amount merchant.taler_amount_currency,' + || ' wire_fee merchant.taler_amount_currency,' + || ' account_serial bigint NOT NULL,' + || ' expected_time bigint NOT NULL,' + || ' retry_time bigint DEFAULT 0 NOT NULL,' + || ' last_http_status integer,' + || ' last_ec integer,' + || ' last_detail text,' + || ' retry_needed boolean DEFAULT true NOT NULL,' + || ' signkey_serial bigint,' + || ' exchange_sig bytea,' + || ' h_details bytea,' + || ' confirmed boolean DEFAULT false NOT NULL,' + || ' CONSTRAINT merchant_expected_transfers_exchange_sig_check CHECK ((length(exchange_sig) = 64)),' + || ' CONSTRAINT merchant_expected_transfers_h_details_check CHECK ((length(h_details) = 64)),' + || ' CONSTRAINT merchant_expected_transfers_wtid_check CHECK ((length(wtid) = 32)),' + || ' UNIQUE (wtid, exchange_url, account_serial)' + || ')', s); + + -- merchant_inventory + EXECUTE format($DDL$CREATE TABLE %I.merchant_inventory ( + product_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + product_id text NOT NULL UNIQUE, + description text NOT NULL, + description_i18n jsonb NOT NULL, + unit text NOT NULL, + image text NOT NULL, + taxes jsonb NOT NULL, + total_stock bigint NOT NULL, + total_sold bigint DEFAULT 0 NOT NULL, + total_lost bigint DEFAULT 0 NOT NULL, + address jsonb NOT NULL, + next_restock bigint NOT NULL, + minimum_age integer DEFAULT 0 NOT NULL, + product_name text NOT NULL, + image_hash text, + price_array merchant.taler_amount_currency[] + DEFAULT ARRAY[]::merchant.taler_amount_currency[] NOT NULL, + total_stock_frac integer DEFAULT 0 NOT NULL, + total_sold_frac integer DEFAULT 0 NOT NULL, + total_lost_frac integer DEFAULT 0 NOT NULL, + allow_fractional_quantity boolean DEFAULT false NOT NULL, + fractional_precision_level integer DEFAULT 0 NOT NULL, + product_group_serial bigint, + money_pot_serial bigint, + price_is_net boolean DEFAULT false + )$DDL$, s); + + -- merchant_inventory_locks + EXECUTE format('CREATE TABLE %I.merchant_inventory_locks (' + || ' product_serial bigint NOT NULL,' + || ' lock_uuid bytea NOT NULL,' + || ' total_locked bigint NOT NULL,' + || ' expiration bigint NOT NULL,' + || ' total_locked_frac integer DEFAULT 0 NOT NULL,' + || ' CONSTRAINT merchant_inventory_locks_lock_uuid_check CHECK ((length(lock_uuid) = 16))' + || ')', s); + + -- merchant_issued_tokens + EXECUTE format('CREATE TABLE %I.merchant_issued_tokens (' + || ' issued_token_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' h_contract_terms bytea NOT NULL,' + || ' token_family_key_serial bigint,' + || ' blind_sig bytea NOT NULL,' + || ' CONSTRAINT merchant_issued_tokens_h_contract_terms_check CHECK ((length(h_contract_terms) = 64))' + || ')', s); + + -- merchant_keys (single-row per schema; merchant_priv becomes the PK) + EXECUTE format('CREATE TABLE %I.merchant_keys (' + || ' merchant_priv bytea NOT NULL PRIMARY KEY,' + || ' CONSTRAINT merchant_keys_merchant_priv_check CHECK ((length(merchant_priv) = 32))' + || ')', s); + + -- merchant_kyc (PK survives unchanged: (account_serial, exchange_url)) + EXECUTE format('CREATE TABLE %I.merchant_kyc (' + || ' kyc_serial_id bigint GENERATED BY DEFAULT AS IDENTITY UNIQUE,' + || ' kyc_timestamp bigint NOT NULL,' + || ' kyc_ok boolean DEFAULT false NOT NULL,' + || ' account_serial bigint NOT NULL,' + || ' exchange_url text NOT NULL,' + || ' access_token bytea,' + || ' exchange_http_status integer DEFAULT 0,' + || ' exchange_ec_code integer DEFAULT 0,' + || ' aml_review boolean DEFAULT false,' + || ' jaccount_limits jsonb,' + || ' last_rule_gen bigint DEFAULT 0 NOT NULL,' + || ' next_kyc_poll bigint DEFAULT 0 NOT NULL,' + || ' kyc_backoff bigint DEFAULT 0 NOT NULL,' + || ' CONSTRAINT access_token_length_check CHECK ((length(access_token) = 32)),' + || ' PRIMARY KEY (account_serial, exchange_url)' + || ')', s); + + -- merchant_login_tokens (note: serial is GENERATED ALWAYS) + EXECUTE format('CREATE TABLE %I.merchant_login_tokens (' + || ' token bytea NOT NULL UNIQUE,' + || ' creation_time bigint NOT NULL,' + || ' expiration_time bigint NOT NULL,' + || ' validity_scope integer NOT NULL,' + || ' description text NOT NULL,' + || ' serial bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,' + || ' CONSTRAINT merchant_login_tokens_token_check CHECK ((length(token) = 32))' + || ')', s); + + -- merchant_money_pots + EXECUTE format('CREATE TABLE %I.merchant_money_pots (' + || ' money_pot_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' money_pot_name text NOT NULL UNIQUE,' + || ' money_pot_description text NOT NULL,' + || ' pot_totals merchant.taler_amount_currency[]' + || ' DEFAULT ARRAY[]::merchant.taler_amount_currency[] NOT NULL' + || ')', s); + + -- merchant_order_locks + EXECUTE format('CREATE TABLE %I.merchant_order_locks (' + || ' product_serial bigint NOT NULL,' + || ' total_locked bigint NOT NULL,' + || ' order_serial bigint NOT NULL,' + || ' total_locked_frac integer DEFAULT 0 NOT NULL' + || ')', s); + + -- merchant_order_token_blinded_sigs + EXECUTE format('CREATE TABLE %I.merchant_order_token_blinded_sigs (' + || ' order_token_bs_serial bigint GENERATED BY DEFAULT AS IDENTITY,' + || ' order_serial bigint NOT NULL,' + || ' token_index integer NOT NULL,' + || ' token_blinded_signature bytea NOT NULL,' + || ' token_hash bytea NOT NULL,' + || ' CONSTRAINT merchant_order_token_blinded_sigs_token_hash_check CHECK ((length(token_hash) = 64)),' + || ' PRIMARY KEY (order_serial, token_index)' + || ')', s); + + -- merchant_orders + EXECUTE format($DDL$CREATE TABLE %I.merchant_orders ( + order_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + order_id text NOT NULL UNIQUE, + claim_token bytea NOT NULL, + h_post_data bytea NOT NULL, + pay_deadline bigint NOT NULL, + creation_time bigint NOT NULL, + contract_terms jsonb NOT NULL, + pos_key text, + pos_algorithm integer DEFAULT 0 NOT NULL, + fulfillment_url text, + session_id text DEFAULT ''::text NOT NULL, + CONSTRAINT merchant_orders_claim_token_check CHECK ((length(claim_token) = 16)), + CONSTRAINT merchant_orders_h_post_data_check CHECK ((length(h_post_data) = 64)) + )$DDL$, s); + + -- merchant_otp_devices + EXECUTE format('CREATE TABLE %I.merchant_otp_devices (' + || ' otp_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' otp_id text NOT NULL UNIQUE,' + || ' otp_description text NOT NULL,' + || ' otp_key text,' + || ' otp_algorithm integer DEFAULT 0 NOT NULL,' + || ' otp_ctr bigint DEFAULT 0 NOT NULL' + || ')', s); + + -- merchant_pending_webhooks + -- The original (merchant_serial, webhook_pending_serial) UNIQUE + -- collapses to just (webhook_pending_serial), which is already the + -- PK, so no extra UNIQUE constraint is needed. + EXECUTE format('CREATE TABLE %I.merchant_pending_webhooks (' + || ' webhook_pending_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' webhook_serial bigint NOT NULL,' + || ' next_attempt bigint DEFAULT 0 NOT NULL,' + || ' retries integer DEFAULT 0 NOT NULL,' + || ' url text NOT NULL,' + || ' http_method text NOT NULL,' + || ' header text,' + || ' body text' + || ')', s); + + -- merchant_product_categories + EXECUTE format('CREATE TABLE %I.merchant_product_categories (' + || ' category_serial bigint NOT NULL,' + || ' product_serial bigint NOT NULL' + || ')', s); + + -- merchant_product_groups + EXECUTE format('CREATE TABLE %I.merchant_product_groups (' + || ' product_group_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' product_group_name text NOT NULL UNIQUE,' + || ' product_group_description text NOT NULL' + || ')', s); + + -- merchant_refund_proofs + EXECUTE format('CREATE TABLE %I.merchant_refund_proofs (' + || ' refund_serial bigint NOT NULL PRIMARY KEY,' + || ' exchange_sig bytea NOT NULL,' + || ' signkey_serial bigint NOT NULL,' + || ' CONSTRAINT merchant_refund_proofs_exchange_sig_check CHECK ((length(exchange_sig) = 64))' + || ')', s); + + -- merchant_refunds + EXECUTE format('CREATE TABLE %I.merchant_refunds (' + || ' refund_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' order_serial bigint NOT NULL,' + || ' rtransaction_id bigint NOT NULL,' + || ' refund_timestamp bigint NOT NULL,' + || ' coin_pub bytea NOT NULL,' + || ' reason text NOT NULL,' + || ' refund_amount merchant.taler_amount_currency NOT NULL,' + || ' UNIQUE (order_serial, coin_pub, rtransaction_id)' + || ')', s); + + -- merchant_reports + EXECUTE format('CREATE TABLE %I.merchant_reports (' + || ' report_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' report_program_section text NOT NULL,' + || ' report_description text NOT NULL,' + || ' mime_type text NOT NULL,' + || ' report_token bytea NOT NULL,' + || ' data_source text NOT NULL,' + || ' target_address text NOT NULL,' + || ' frequency bigint NOT NULL,' + || ' frequency_shift bigint NOT NULL,' + || ' next_transmission bigint NOT NULL,' + || ' last_error_code integer,' + || ' last_error_detail text,' + || ' one_shot_hidden boolean DEFAULT false,' + || ' CONSTRAINT merchant_reports_report_token_check CHECK ((length(report_token) = 32))' + || ')', s); + + -- merchant_used_tokens + EXECUTE format('CREATE TABLE %I.merchant_used_tokens (' + || ' spent_token_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' h_contract_terms bytea NOT NULL,' + || ' token_family_key_serial bigint,' + || ' token_pub bytea NOT NULL UNIQUE,' + || ' token_sig bytea NOT NULL,' + || ' blind_sig bytea NOT NULL,' + || ' CONSTRAINT merchant_spent_tokens_h_contract_terms_check CHECK ((length(h_contract_terms) = 64)),' + || ' CONSTRAINT merchant_spent_tokens_token_pub_check CHECK ((length(token_pub) = 32)),' + || ' CONSTRAINT merchant_spent_tokens_token_sig_check CHECK ((length(token_sig) = 64))' + || ')', s); + + -- merchant_statistic_amount_event + EXECUTE format('CREATE TABLE %I.merchant_statistic_amount_event (' + || ' aevent_serial_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' imeta_serial_id bigint,' + || ' slot bigint NOT NULL,' + || ' delta_curr character varying(12) NOT NULL,' + || ' delta_value bigint NOT NULL,' + || ' delta_frac integer NOT NULL,' + || ' CONSTRAINT event_key UNIQUE (imeta_serial_id, delta_curr, slot)' + || ')', s); + + -- merchant_statistic_bucket_amount + EXECUTE format('CREATE TABLE %I.merchant_statistic_bucket_amount (' + || ' bmeta_serial_id bigint NOT NULL,' + || ' bucket_start bigint NOT NULL,' + || ' bucket_range merchant.statistic_range NOT NULL,' + || ' curr character varying(12) NOT NULL,' + || ' cumulative_value bigint NOT NULL,' + || ' cumulative_frac integer NOT NULL,' + || ' PRIMARY KEY (bmeta_serial_id, curr, bucket_start, bucket_range)' + || ')', s); + + -- merchant_statistic_bucket_counter + EXECUTE format('CREATE TABLE %I.merchant_statistic_bucket_counter (' + || ' bmeta_serial_id bigint NOT NULL,' + || ' bucket_start bigint NOT NULL,' + || ' bucket_range merchant.statistic_range NOT NULL,' + || ' cumulative_number bigint NOT NULL,' + || ' PRIMARY KEY (bmeta_serial_id, bucket_start, bucket_range)' + || ')', s); + + -- merchant_statistic_bucket_meta + EXECUTE format('CREATE TABLE %I.merchant_statistic_bucket_meta (' + || ' bmeta_serial_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' slug text NOT NULL,' + || ' description text NOT NULL,' + || ' stype merchant.statistic_type NOT NULL,' + || ' ranges merchant.statistic_range[] NOT NULL,' + || ' ages integer[] NOT NULL,' + || ' CONSTRAINT equal_array_length CHECK ((array_length(ranges, 1) = array_length(ages, 1))),' + || ' UNIQUE (slug, stype)' + || ')', s); + + -- merchant_statistic_counter_event + EXECUTE format('CREATE TABLE %I.merchant_statistic_counter_event (' + || ' nevent_serial_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' imeta_serial_id bigint,' + || ' slot bigint NOT NULL,' + || ' delta bigint NOT NULL,' + || ' UNIQUE (imeta_serial_id, slot)' + || ')', s); + + -- merchant_statistic_interval_amount + EXECUTE format('CREATE TABLE %I.merchant_statistic_interval_amount (' + || ' imeta_serial_id bigint NOT NULL,' + || ' event_delimiter bigint NOT NULL,' + || ' range bigint NOT NULL,' + || ' curr character varying(12) NOT NULL,' + || ' cumulative_value bigint NOT NULL,' + || ' cumulative_frac integer NOT NULL,' + || ' PRIMARY KEY (imeta_serial_id, curr, range)' + || ')', s); + + -- merchant_statistic_interval_counter + EXECUTE format('CREATE TABLE %I.merchant_statistic_interval_counter (' + || ' imeta_serial_id bigint NOT NULL,' + || ' range bigint NOT NULL,' + || ' event_delimiter bigint NOT NULL,' + || ' cumulative_number bigint NOT NULL,' + || ' PRIMARY KEY (imeta_serial_id, range)' + || ')', s); + + -- merchant_statistic_interval_meta + EXECUTE format('CREATE TABLE %I.merchant_statistic_interval_meta (' + || ' imeta_serial_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' slug text NOT NULL,' + || ' description text NOT NULL,' + || ' stype merchant.statistic_type NOT NULL,' + || ' ranges bigint[] NOT NULL,' + || ' precisions bigint[] NOT NULL,' + || ' CONSTRAINT equal_array_length CHECK ((array_length(ranges, 1) = array_length(precisions, 1))),' + || ' CONSTRAINT merchant_statistic_interval_meta_precisions_check CHECK ((array_length(precisions, 1) > 0)),' + || ' CONSTRAINT merchant_statistic_interval_meta_ranges_check CHECK ((array_length(ranges, 1) > 0)),' + || ' UNIQUE (slug, stype)' + || ')', s); + + -- merchant_template + EXECUTE format('CREATE TABLE %I.merchant_template (' + || ' template_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' template_id text NOT NULL UNIQUE,' + || ' template_description text NOT NULL,' + || ' otp_device_id bigint,' + || ' template_contract jsonb NOT NULL,' + || ' editable_defaults jsonb' + || ')', s); + + -- merchant_token_families + EXECUTE format($DDL$CREATE TABLE %I.merchant_token_families ( + token_family_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + slug text NOT NULL UNIQUE, + name text NOT NULL, + description text, + description_i18n jsonb NOT NULL, + valid_after bigint NOT NULL, + valid_before bigint NOT NULL, + duration bigint NOT NULL, + kind text NOT NULL, + issued bigint DEFAULT 0, + used bigint DEFAULT 0, + validity_granularity bigint DEFAULT '2592000000000'::bigint NOT NULL, + start_offset bigint DEFAULT 0 NOT NULL, + cipher_choice text DEFAULT 'rsa(2048)'::text NOT NULL, + extra_data jsonb, + CONSTRAINT merchant_token_families_kind_check + CHECK ((kind = ANY (ARRAY['subscription'::text, 'discount'::text]))), + CONSTRAINT merchant_token_families_validity_granularity_check + CHECK ((validity_granularity = ANY (ARRAY[(60000000)::bigint, + '3600000000'::bigint, '86400000000'::bigint, '604800000000'::bigint, + '2592000000000'::bigint, '7776000000000'::bigint, + '31536000000000'::bigint]))) + )$DDL$, s); + + -- merchant_token_family_keys + EXECUTE format('CREATE TABLE %I.merchant_token_family_keys (' + || ' token_family_key_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' token_family_serial bigint,' + || ' pub bytea NOT NULL,' + || ' h_pub bytea NOT NULL UNIQUE,' + || ' priv bytea,' + || ' cipher text NOT NULL,' + || ' signature_validity_start bigint DEFAULT 0 NOT NULL,' + || ' signature_validity_end bigint DEFAULT 0 NOT NULL,' + || ' private_key_deleted_at bigint DEFAULT 0 NOT NULL,' + || ' private_key_created_at bigint DEFAULT 0 NOT NULL,' + || ' CONSTRAINT h_pub_length_check CHECK ((length(h_pub) = 64)),' + || $DDL$ CONSTRAINT merchant_token_family_keys_cipher_check CHECK ((cipher = ANY (ARRAY['rsa'::text, 'cs'::text])))$DDL$ + || ')', s); + + -- merchant_transfer_signatures + EXECUTE format('CREATE TABLE %I.merchant_transfer_signatures (' + || ' expected_credit_serial bigint NOT NULL PRIMARY KEY,' + || ' signkey_serial bigint NOT NULL,' + || ' wire_fee merchant.taler_amount_currency NOT NULL,' + || ' credit_amount merchant.taler_amount_currency NOT NULL,' + || ' execution_time bigint NOT NULL,' + || ' exchange_sig bytea NOT NULL,' + || ' CONSTRAINT merchant_transfer_signatures_exchange_sig_check CHECK ((length(exchange_sig) = 64))' + || ')', s); + + -- merchant_transfers + EXECUTE format('CREATE TABLE %I.merchant_transfers (' + || ' credit_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' exchange_url text NOT NULL,' + || ' wtid bytea,' + || ' credit_amount merchant.taler_amount_currency NOT NULL,' + || ' account_serial bigint NOT NULL,' + || ' bank_serial_id bigint,' + || ' expected boolean DEFAULT false,' + || ' execution_time bigint DEFAULT 0,' + || ' CONSTRAINT merchant_transfers_wtid_check CHECK ((length(wtid) = 32)),' + || ' CONSTRAINT merchant_transfers_unique UNIQUE (wtid, exchange_url, account_serial, bank_serial_id)' + || ')', s); + + -- merchant_unclaim_signatures + EXECUTE format('CREATE TABLE %I.merchant_unclaim_signatures (' + || ' unclaim_serial bigint GENERATED BY DEFAULT AS IDENTITY UNIQUE,' + || ' h_contract_terms bytea NOT NULL,' + || ' unclaim_sig bytea NOT NULL PRIMARY KEY,' + || ' expiration_time bigint NOT NULL,' + || ' CONSTRAINT merchant_unclaim_signatures_h_contract_terms_check CHECK ((length(h_contract_terms) = 64)),' + || ' CONSTRAINT merchant_unclaim_signatures_unclaim_sig_check CHECK ((length(unclaim_sig) = 64))' + || ')', s); + + -- merchant_webhook + EXECUTE format('CREATE TABLE %I.merchant_webhook (' + || ' webhook_serial bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' + || ' webhook_id text NOT NULL UNIQUE,' + || ' event_type text NOT NULL,' + || ' url text NOT NULL,' + || ' http_method text NOT NULL,' + || ' header_template text,' + || ' body_template text' + || ')', s); + + -- tan_challenges + EXECUTE format('CREATE TABLE %I.tan_challenges (' + || ' challenge_id bigint GENERATED BY DEFAULT AS IDENTITY UNIQUE,' + || ' h_body bytea NOT NULL,' + || ' salt bytea NOT NULL,' + || ' op merchant.op_enum NOT NULL,' + || ' code text NOT NULL,' + || ' creation_date bigint NOT NULL,' + || ' expiration_date bigint NOT NULL,' + || ' retransmission_date bigint DEFAULT 0 NOT NULL,' + || ' confirmation_date bigint,' + || ' retry_counter integer NOT NULL,' + || ' tan_channel merchant.tan_enum NOT NULL,' + || ' required_address text NOT NULL,' + || ' CONSTRAINT tan_challenges_h_body_check CHECK ((length(h_body) = 32)),' + || ' CONSTRAINT tan_challenges_salt_check CHECK ((length(salt) = 16))' + || ')', s); + + -- ------------------------------------------------------------------- + -- 3. CREATE INDEX statements (per-instance only). + -- Index names are bare; PostgreSQL scopes them by schema, so the + -- same logical name in each instance schema is fine. + -- merchant_serial leading columns are dropped from the column list. + -- ------------------------------------------------------------------- + + -- merchant_product_categories + EXECUTE format('CREATE INDEX merchant_categories_by_category' + || ' ON %I.merchant_product_categories USING btree (category_serial)', s); + EXECUTE format('CREATE INDEX merchant_categories_by_product' + || ' ON %I.merchant_product_categories USING btree (product_serial)', s); + + -- merchant_contract_terms + EXECUTE format('CREATE INDEX merchant_contract_terms_by_expiration' + || ' ON %I.merchant_contract_terms USING btree (paid, pay_deadline)', s); + EXECUTE format('CREATE INDEX merchant_contract_terms_by_merchant_and_expiration' + || ' ON %I.merchant_contract_terms USING btree (pay_deadline)', s); + EXECUTE format('CREATE INDEX merchant_contract_terms_by_merchant_and_payment' + || ' ON %I.merchant_contract_terms USING btree (paid)', s); + EXECUTE format('CREATE INDEX merchant_contract_terms_by_merchant_and_session' + || ' ON %I.merchant_contract_terms USING btree (session_id)', s); + EXECUTE format('CREATE INDEX merchant_contract_terms_by_merchant_session_and_fulfillment' + || ' ON %I.merchant_contract_terms USING btree (fulfillment_url, session_id)', s); + + -- merchant_deposit_confirmations + EXECUTE format('CREATE INDEX merchant_deposit_confirmations_by_pending_wire' + || ' ON %I.merchant_deposit_confirmations USING btree (exchange_url, wire_transfer_deadline)' + || ' WHERE wire_pending', s); + + -- merchant_deposits + EXECUTE format('CREATE INDEX merchant_deposits_by_deposit_confirmation' + || ' ON %I.merchant_deposits USING btree (deposit_confirmation_serial)', s); + EXECUTE format('CREATE INDEX merchant_deposits_by_deposit_confirmation_serial' + || ' ON %I.merchant_deposits USING btree (deposit_confirmation_serial)', s); + EXECUTE format('CREATE INDEX merchant_deposits_by_settlement_open' + || ' ON %I.merchant_deposits USING btree (settlement_retry_time)' + || ' WHERE settlement_retry_needed', s); + + -- merchant_expected_transfers + EXECUTE format('CREATE INDEX merchant_expected_transfers_by_open' + || ' ON %I.merchant_expected_transfers USING btree (retry_time)' + || ' WHERE ((NOT confirmed) OR retry_needed)', s); + + -- merchant_inventory + EXECUTE format('CREATE INDEX merchant_inventory_by_image_hash' + || ' ON %I.merchant_inventory USING btree (image_hash)', s); + + -- merchant_inventory_locks + EXECUTE format('CREATE INDEX merchant_inventory_locks_by_expiration' + || ' ON %I.merchant_inventory_locks USING btree (expiration)', s); + EXECUTE format('CREATE INDEX merchant_inventory_locks_by_uuid' + || ' ON %I.merchant_inventory_locks USING btree (lock_uuid)', s); + + -- merchant_kyc + EXECUTE format('CREATE INDEX merchant_kyc_by_next_kyc_poll' + || ' ON %I.merchant_kyc USING btree (next_kyc_poll)', s); + + -- merchant_login_tokens + EXECUTE format('CREATE INDEX merchant_login_tokens_by_expiration' + || ' ON %I.merchant_login_tokens USING btree (expiration_time)', s); + + -- merchant_orders + EXECUTE format('CREATE INDEX merchant_orders_by_creation_time' + || ' ON %I.merchant_orders USING btree (creation_time)', s); + EXECUTE format('CREATE INDEX merchant_orders_by_expiration' + || ' ON %I.merchant_orders USING btree (pay_deadline)', s); + EXECUTE format('CREATE INDEX merchant_orders_by_merchant_and_fullfilment_and_session' + || ' ON %I.merchant_orders USING btree (fulfillment_url, session_id)', s); + EXECUTE format('CREATE INDEX merchant_orders_by_merchant_and_session' + || ' ON %I.merchant_orders USING btree (session_id)', s); + + -- merchant_order_locks + EXECUTE format('CREATE INDEX merchant_orders_locks_by_order_and_product' + || ' ON %I.merchant_order_locks USING btree (order_serial, product_serial)', s); + + -- merchant_refunds + EXECUTE format('CREATE INDEX merchant_refunds_by_coin_and_order' + || ' ON %I.merchant_refunds USING btree (coin_pub, order_serial)', s); + + -- merchant_expected_transfer_to_coin + EXECUTE format('CREATE INDEX merchant_transfers_by_credit' + || ' ON %I.merchant_expected_transfer_to_coin USING btree (expected_credit_serial)', s); + + -- merchant_unclaim_signatures + EXECUTE format('CREATE INDEX merchant_unclaim_signatures_by_expiration' + || ' ON %I.merchant_unclaim_signatures USING btree (expiration_time)', s); + + -- tan_challenges + EXECUTE format('CREATE INDEX tan_challenges_expiration_index' + || ' ON %I.tan_challenges USING btree (expiration_date)', s); + + -- trigram indexes + EXECUTE format('CREATE INDEX trgm_idx_categories_by_name' + || ' ON %I.merchant_categories USING gin (lower(category_name) public.gin_trgm_ops)', s); + EXECUTE format($DDL$CREATE INDEX trgm_idx_contract_summaries + ON %I.merchant_contract_terms USING gin (lower((contract_terms ->> 'summary'::text)) public.gin_trgm_ops)$DDL$, s); + EXECUTE format($DDL$CREATE INDEX trgm_idx_order_summaries + ON %I.merchant_orders USING gin (lower((contract_terms ->> 'summary'::text)) public.gin_trgm_ops)$DDL$, s); + EXECUTE format('CREATE INDEX trgm_idx_products_by_description' + || ' ON %I.merchant_inventory USING gin (lower(description) public.gin_trgm_ops)', s); + EXECUTE format('CREATE INDEX trgm_idx_products_by_name' + || ' ON %I.merchant_inventory USING gin (lower(product_name) public.gin_trgm_ops)', s); + + -- ------------------------------------------------------------------- + -- 4. ALTER TABLE for FKs *between per-instance tables* (intra-schema). + -- Both sides live in `s`; use format(..., s, s). + -- ------------------------------------------------------------------- + + -- merchant_deposit_confirmations -> merchant_accounts + EXECUTE format('ALTER TABLE %I.merchant_deposit_confirmations' + || ' ADD CONSTRAINT merchant_deposit_confirmations_account_serial_fkey' + || ' FOREIGN KEY (account_serial)' + || ' REFERENCES %I.merchant_accounts(account_serial) ON DELETE CASCADE', s, s); + + -- merchant_deposit_confirmations -> merchant_contract_terms + EXECUTE format('ALTER TABLE %I.merchant_deposit_confirmations' + || ' ADD CONSTRAINT merchant_deposit_confirmations_order_serial_fkey' + || ' FOREIGN KEY (order_serial)' + || ' REFERENCES %I.merchant_contract_terms(order_serial) ON DELETE CASCADE', s, s); + + -- merchant_deposits -> merchant_deposit_confirmations + EXECUTE format('ALTER TABLE %I.merchant_deposits' + || ' ADD CONSTRAINT merchant_deposits_deposit_confirmation_serial_fkey' + || ' FOREIGN KEY (deposit_confirmation_serial)' + || ' REFERENCES %I.merchant_deposit_confirmations(deposit_confirmation_serial) ON DELETE CASCADE', s, s); + + -- merchant_deposits -> merchant_expected_transfers (no ON DELETE clause originally) + EXECUTE format('ALTER TABLE %I.merchant_deposits' + || ' ADD CONSTRAINT merchant_deposits_settlement_expected_credit_serial_fkey' + || ' FOREIGN KEY (settlement_expected_credit_serial)' + || ' REFERENCES %I.merchant_expected_transfers(expected_credit_serial)', s, s); + + -- merchant_expected_transfer_to_coin -> merchant_deposits + EXECUTE format('ALTER TABLE %I.merchant_expected_transfer_to_coin' + || ' ADD CONSTRAINT merchant_expected_transfer_to_coin_deposit_serial_fkey' + || ' FOREIGN KEY (deposit_serial)' + || ' REFERENCES %I.merchant_deposits(deposit_serial) ON DELETE CASCADE', s, s); + + -- merchant_expected_transfer_to_coin -> merchant_expected_transfers + EXECUTE format('ALTER TABLE %I.merchant_expected_transfer_to_coin' + || ' ADD CONSTRAINT merchant_expected_transfer_to_coin_expected_credit_serial_fkey' + || ' FOREIGN KEY (expected_credit_serial)' + || ' REFERENCES %I.merchant_expected_transfers(expected_credit_serial) ON DELETE CASCADE', s, s); + + -- merchant_expected_transfers -> merchant_accounts + EXECUTE format('ALTER TABLE %I.merchant_expected_transfers' + || ' ADD CONSTRAINT merchant_expected_transfers_account_serial_fkey' + || ' FOREIGN KEY (account_serial)' + || ' REFERENCES %I.merchant_accounts(account_serial) ON DELETE CASCADE', s, s); + + -- merchant_inventory -> merchant_money_pots + EXECUTE format('ALTER TABLE %I.merchant_inventory' + || ' ADD CONSTRAINT merchant_inventory_money_pot_serial_fkey' + || ' FOREIGN KEY (money_pot_serial)' + || ' REFERENCES %I.merchant_money_pots(money_pot_serial) ON DELETE SET NULL', s, s); + + -- merchant_inventory -> merchant_product_groups + EXECUTE format('ALTER TABLE %I.merchant_inventory' + || ' ADD CONSTRAINT merchant_inventory_product_group_serial_fkey' + || ' FOREIGN KEY (product_group_serial)' + || ' REFERENCES %I.merchant_product_groups(product_group_serial) ON DELETE SET NULL', s, s); + + -- merchant_inventory_locks -> merchant_inventory + EXECUTE format('ALTER TABLE %I.merchant_inventory_locks' + || ' ADD CONSTRAINT merchant_inventory_locks_product_serial_fkey' + || ' FOREIGN KEY (product_serial)' + || ' REFERENCES %I.merchant_inventory(product_serial) ON DELETE CASCADE', s, s); + + -- merchant_issued_tokens -> merchant_token_family_keys + EXECUTE format('ALTER TABLE %I.merchant_issued_tokens' + || ' ADD CONSTRAINT merchant_issued_tokens_token_family_key_serial_fkey' + || ' FOREIGN KEY (token_family_key_serial)' + || ' REFERENCES %I.merchant_token_family_keys(token_family_key_serial) ON DELETE CASCADE', s, s); + + -- merchant_kyc -> merchant_accounts + EXECUTE format('ALTER TABLE %I.merchant_kyc' + || ' ADD CONSTRAINT merchant_kyc_account_serial_fkey' + || ' FOREIGN KEY (account_serial)' + || ' REFERENCES %I.merchant_accounts(account_serial) ON DELETE CASCADE', s, s); + + -- merchant_order_locks -> merchant_orders + EXECUTE format('ALTER TABLE %I.merchant_order_locks' + || ' ADD CONSTRAINT merchant_order_locks_order_serial_fkey' + || ' FOREIGN KEY (order_serial)' + || ' REFERENCES %I.merchant_orders(order_serial) ON DELETE CASCADE', s, s); + + -- merchant_order_locks -> merchant_inventory (no ON DELETE clause originally) + EXECUTE format('ALTER TABLE %I.merchant_order_locks' + || ' ADD CONSTRAINT merchant_order_locks_product_serial_fkey' + || ' FOREIGN KEY (product_serial)' + || ' REFERENCES %I.merchant_inventory(product_serial)', s, s); + + -- merchant_order_token_blinded_sigs -> merchant_contract_terms + EXECUTE format('ALTER TABLE %I.merchant_order_token_blinded_sigs' + || ' ADD CONSTRAINT merchant_order_token_blinded_sigs_order_serial_fkey' + || ' FOREIGN KEY (order_serial)' + || ' REFERENCES %I.merchant_contract_terms(order_serial) ON DELETE CASCADE', s, s); + + -- merchant_pending_webhooks -> merchant_webhook + EXECUTE format('ALTER TABLE %I.merchant_pending_webhooks' + || ' ADD CONSTRAINT merchant_pending_webhooks_webhook_serial_fkey' + || ' FOREIGN KEY (webhook_serial)' + || ' REFERENCES %I.merchant_webhook(webhook_serial) ON DELETE CASCADE', s, s); + + -- merchant_product_categories -> merchant_categories + EXECUTE format('ALTER TABLE %I.merchant_product_categories' + || ' ADD CONSTRAINT merchant_product_categories_category_serial_fkey' + || ' FOREIGN KEY (category_serial)' + || ' REFERENCES %I.merchant_categories(category_serial) ON DELETE CASCADE', s, s); + + -- merchant_product_categories -> merchant_inventory + EXECUTE format('ALTER TABLE %I.merchant_product_categories' + || ' ADD CONSTRAINT merchant_product_categories_product_serial_fkey' + || ' FOREIGN KEY (product_serial)' + || ' REFERENCES %I.merchant_inventory(product_serial) ON DELETE CASCADE', s, s); + + -- merchant_refund_proofs -> merchant_refunds + EXECUTE format('ALTER TABLE %I.merchant_refund_proofs' + || ' ADD CONSTRAINT merchant_refund_proofs_refund_serial_fkey' + || ' FOREIGN KEY (refund_serial)' + || ' REFERENCES %I.merchant_refunds(refund_serial) ON DELETE CASCADE', s, s); + + -- merchant_refunds -> merchant_contract_terms + EXECUTE format('ALTER TABLE %I.merchant_refunds' + || ' ADD CONSTRAINT merchant_refunds_order_serial_fkey' + || ' FOREIGN KEY (order_serial)' + || ' REFERENCES %I.merchant_contract_terms(order_serial) ON DELETE CASCADE', s, s); + + -- merchant_used_tokens -> merchant_token_family_keys + EXECUTE format('ALTER TABLE %I.merchant_used_tokens' + || ' ADD CONSTRAINT merchant_spent_tokens_token_family_key_serial_fkey' + || ' FOREIGN KEY (token_family_key_serial)' + || ' REFERENCES %I.merchant_token_family_keys(token_family_key_serial) ON DELETE CASCADE', s, s); + + -- merchant_statistic_amount_event -> merchant_statistic_interval_meta + EXECUTE format('ALTER TABLE %I.merchant_statistic_amount_event' + || ' ADD CONSTRAINT merchant_statistic_amount_event_imeta_serial_id_fkey' + || ' FOREIGN KEY (imeta_serial_id)' + || ' REFERENCES %I.merchant_statistic_interval_meta(imeta_serial_id) ON DELETE CASCADE', s, s); + + -- merchant_statistic_bucket_amount -> merchant_statistic_bucket_meta + EXECUTE format('ALTER TABLE %I.merchant_statistic_bucket_amount' + || ' ADD CONSTRAINT merchant_statistic_bucket_amount_bmeta_serial_id_fkey' + || ' FOREIGN KEY (bmeta_serial_id)' + || ' REFERENCES %I.merchant_statistic_bucket_meta(bmeta_serial_id) ON DELETE CASCADE', s, s); + + -- merchant_statistic_bucket_counter -> merchant_statistic_bucket_meta + EXECUTE format('ALTER TABLE %I.merchant_statistic_bucket_counter' + || ' ADD CONSTRAINT merchant_statistic_bucket_counter_bmeta_serial_id_fkey' + || ' FOREIGN KEY (bmeta_serial_id)' + || ' REFERENCES %I.merchant_statistic_bucket_meta(bmeta_serial_id) ON DELETE CASCADE', s, s); + + -- merchant_statistic_counter_event -> merchant_statistic_interval_meta + EXECUTE format('ALTER TABLE %I.merchant_statistic_counter_event' + || ' ADD CONSTRAINT merchant_statistic_counter_event_imeta_serial_id_fkey' + || ' FOREIGN KEY (imeta_serial_id)' + || ' REFERENCES %I.merchant_statistic_interval_meta(imeta_serial_id) ON DELETE CASCADE', s, s); + + -- merchant_statistic_interval_amount -> merchant_statistic_amount_event + EXECUTE format('ALTER TABLE %I.merchant_statistic_interval_amount' + || ' ADD CONSTRAINT merchant_statistic_interval_amount_event_delimiter_fkey' + || ' FOREIGN KEY (event_delimiter)' + || ' REFERENCES %I.merchant_statistic_amount_event(aevent_serial_id) ON DELETE RESTRICT', s, s); + + -- merchant_statistic_interval_amount -> merchant_statistic_interval_meta + EXECUTE format('ALTER TABLE %I.merchant_statistic_interval_amount' + || ' ADD CONSTRAINT merchant_statistic_interval_amount_imeta_serial_id_fkey' + || ' FOREIGN KEY (imeta_serial_id)' + || ' REFERENCES %I.merchant_statistic_interval_meta(imeta_serial_id) ON DELETE CASCADE', s, s); + + -- merchant_statistic_interval_counter -> merchant_statistic_counter_event + EXECUTE format('ALTER TABLE %I.merchant_statistic_interval_counter' + || ' ADD CONSTRAINT merchant_statistic_interval_counter_event_delimiter_fkey' + || ' FOREIGN KEY (event_delimiter)' + || ' REFERENCES %I.merchant_statistic_counter_event(nevent_serial_id) ON DELETE RESTRICT', s, s); + + -- merchant_statistic_interval_counter -> merchant_statistic_interval_meta + EXECUTE format('ALTER TABLE %I.merchant_statistic_interval_counter' + || ' ADD CONSTRAINT merchant_statistic_interval_counter_imeta_serial_id_fkey' + || ' FOREIGN KEY (imeta_serial_id)' + || ' REFERENCES %I.merchant_statistic_interval_meta(imeta_serial_id) ON DELETE CASCADE', s, s); + + -- merchant_template -> merchant_otp_devices + EXECUTE format('ALTER TABLE %I.merchant_template' + || ' ADD CONSTRAINT merchant_template_otp_device_id_fkey' + || ' FOREIGN KEY (otp_device_id)' + || ' REFERENCES %I.merchant_otp_devices(otp_serial) ON DELETE SET NULL', s, s); + + -- merchant_token_family_keys -> merchant_token_families + EXECUTE format('ALTER TABLE %I.merchant_token_family_keys' + || ' ADD CONSTRAINT merchant_token_family_keys_token_family_serial_fkey' + || ' FOREIGN KEY (token_family_serial)' + || ' REFERENCES %I.merchant_token_families(token_family_serial) ON DELETE CASCADE', s, s); + + -- merchant_transfer_signatures -> merchant_expected_transfers + EXECUTE format('ALTER TABLE %I.merchant_transfer_signatures' + || ' ADD CONSTRAINT merchant_transfer_signatures_expected_credit_serial_fkey' + || ' FOREIGN KEY (expected_credit_serial)' + || ' REFERENCES %I.merchant_expected_transfers(expected_credit_serial) ON DELETE CASCADE', s, s); + + -- merchant_transfers -> merchant_accounts + EXECUTE format('ALTER TABLE %I.merchant_transfers' + || ' ADD CONSTRAINT merchant_transfers_account_serial_fkey' + || ' FOREIGN KEY (account_serial)' + || ' REFERENCES %I.merchant_accounts(account_serial) ON DELETE CASCADE', s, s); + + -- ------------------------------------------------------------------- + -- 5. ALTER TABLE for FKs to *global* merchant.* tables. + -- Right-hand side keeps `merchant.` qualification. + -- ------------------------------------------------------------------- + + -- merchant_builtin_unit_overrides -> merchant.merchant_builtin_units + EXECUTE format('ALTER TABLE %I.merchant_builtin_unit_overrides' + || ' ADD CONSTRAINT merchant_builtin_unit_overrides_builtin_unit_serial_fkey' + || ' FOREIGN KEY (builtin_unit_serial)' + || ' REFERENCES merchant.merchant_builtin_units(unit_serial) ON DELETE CASCADE', s); + + -- merchant_deposit_confirmations -> merchant.merchant_exchange_signing_keys + EXECUTE format('ALTER TABLE %I.merchant_deposit_confirmations' + || ' ADD CONSTRAINT merchant_deposit_confirmations_signkey_serial_fkey' + || ' FOREIGN KEY (signkey_serial)' + || ' REFERENCES merchant.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE', s); + + -- merchant_deposits -> merchant.merchant_exchange_signing_keys + EXECUTE format('ALTER TABLE %I.merchant_deposits' + || ' ADD CONSTRAINT merchant_deposits_signkey_serial_fkey' + || ' FOREIGN KEY (signkey_serial)' + || ' REFERENCES merchant.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE', s); + + -- merchant_donau_instances -> merchant.merchant_donau_keys (FK on donau_url) + EXECUTE format('ALTER TABLE %I.merchant_donau_instances' + || ' ADD CONSTRAINT merchant_donau_instances_donau_url_fkey' + || ' FOREIGN KEY (donau_url)' + || ' REFERENCES merchant.merchant_donau_keys(donau_url) ON DELETE CASCADE', s); + + -- merchant_expected_transfers -> merchant.merchant_exchange_signing_keys + EXECUTE format('ALTER TABLE %I.merchant_expected_transfers' + || ' ADD CONSTRAINT merchant_expected_transfers_signkey_serial_fkey' + || ' FOREIGN KEY (signkey_serial)' + || ' REFERENCES merchant.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE', s); + + -- merchant_refund_proofs -> merchant.merchant_exchange_signing_keys + EXECUTE format('ALTER TABLE %I.merchant_refund_proofs' + || ' ADD CONSTRAINT merchant_refund_proofs_signkey_serial_fkey' + || ' FOREIGN KEY (signkey_serial)' + || ' REFERENCES merchant.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE', s); + + -- merchant_transfer_signatures -> merchant.merchant_exchange_signing_keys + EXECUTE format('ALTER TABLE %I.merchant_transfer_signatures' + || ' ADD CONSTRAINT merchant_transfer_signatures_signkey_serial_fkey' + || ' FOREIGN KEY (signkey_serial)' + || ' REFERENCES merchant.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE', s); + + -- ===================================================================== + -- (FKs back to merchant.merchant_instances are intentionally dropped: + -- the existence of the per-instance schema implies that relationship.) + -- ===================================================================== diff --git a/src/backenddb/pg_create_instance_schema_triggers.sql.fragment b/src/backenddb/pg_create_instance_schema_triggers.sql.fragment @@ -0,0 +1,736 @@ +-- ===================================================================== +-- Per-instance schema constructor: triggers and trigger functions. +-- +-- Embedded inside merchant.create_instance_schema(BIGINT); local var +-- `s TEXT` holds the per-instance schema name. All DDL is via +-- EXECUTE format(...) with %I quoting. +-- +-- The merchant_serial column has been dropped from every per-instance +-- table; references to it inside the original trigger bodies are +-- removed here. Helper functions (replace_placeholder, ...) and +-- bump_*_stat procedures live in `merchant.` and stay qualified. +-- ===================================================================== + + -- ------------------------------------------------------------------- + -- 1. CREATE FUNCTION for each per-instance trigger (17 functions). + -- ------------------------------------------------------------------- + + -- handle_category_changes + EXECUTE format($OUTER$ + CREATE FUNCTION %I.handle_category_changes() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + DECLARE + resolved_body TEXT; + webhook RECORD; + BEGIN + IF TG_OP = 'INSERT' THEN + FOR webhook IN + SELECT webhook_serial, + url, + http_method, + body_template + FROM merchant_webhook + WHERE event_type = 'category_added' + LOOP + resolved_body := webhook.body_template; + resolved_body := merchant.replace_placeholder(resolved_body, + 'webhook_type', + 'category_added'); + resolved_body := merchant.replace_placeholder(resolved_body, + 'category_serial', + NEW.category_serial::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'category_name', + NEW.category_name); + INSERT INTO merchant_pending_webhooks + (webhook_serial, url, http_method, body) + VALUES + (webhook.webhook_serial, + webhook.url, + webhook.http_method, + resolved_body); + END LOOP; + NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG; + END IF; + IF TG_OP = 'UPDATE' THEN + FOR webhook IN + SELECT webhook_serial, + url, + http_method, + body_template + FROM merchant_webhook + WHERE event_type = 'category_updated' + LOOP + resolved_body := webhook.body_template; + resolved_body := merchant.replace_placeholder(resolved_body, + 'webhook_type', + 'category_updated'); + resolved_body := merchant.replace_placeholder(resolved_body, + 'category_serial', + NEW.category_serial::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_category_name', + OLD.category_name); + resolved_body := merchant.replace_placeholder(resolved_body, + 'category_name', + NEW.category_name); + resolved_body := merchant.replace_placeholder(resolved_body, + 'category_name_i18n', + NEW.category_name_i18n::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_category_name_i18n', + OLD.category_name_i18n::TEXT); + INSERT INTO merchant_pending_webhooks + (webhook_serial, url, http_method, body) + VALUES + (webhook.webhook_serial, + webhook.url, + webhook.http_method, + resolved_body); + END LOOP; + NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG; + END IF; + IF TG_OP = 'DELETE' THEN + FOR webhook IN + SELECT webhook_serial, + url, + http_method, + body_template + FROM merchant_webhook + WHERE event_type = 'category_deleted' + LOOP + resolved_body := webhook.body_template; + resolved_body := merchant.replace_placeholder(resolved_body, + 'webhook_type', + 'category_deleted'); + resolved_body := merchant.replace_placeholder(resolved_body, + 'category_serial', + OLD.category_serial::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'category_name', + OLD.category_name); + INSERT INTO merchant_pending_webhooks + (webhook_serial, url, http_method, body) + VALUES + (webhook.webhook_serial, + webhook.url, + webhook.http_method, + resolved_body); + END LOOP; + NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG; + END IF; + RETURN NULL; + END; + $BODY$ + $OUTER$, s); + + -- handle_inventory_changes + EXECUTE format($OUTER$ + CREATE FUNCTION %I.handle_inventory_changes() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + DECLARE + resolved_body TEXT; + webhook RECORD; + BEGIN + IF TG_OP = 'INSERT' THEN + FOR webhook IN + SELECT webhook_serial, + url, + http_method, + body_template + FROM merchant_webhook + WHERE event_type = 'inventory_added' + LOOP + resolved_body := webhook.body_template; + resolved_body := merchant.replace_placeholder(resolved_body, + 'webhook_type', + 'inventory_added'); + resolved_body := merchant.replace_placeholder(resolved_body, + 'product_serial', + NEW.product_serial::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'product_id', + NEW.product_id); + resolved_body := merchant.replace_placeholder(resolved_body, + 'description', + NEW.description); + resolved_body := merchant.replace_placeholder(resolved_body, + 'description_i18n', + NEW.description_i18n::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'unit', + NEW.unit); + resolved_body := merchant.replace_placeholder(resolved_body, + 'image', + NEW.image); + resolved_body := merchant.replace_placeholder(resolved_body, + 'taxes', + NEW.taxes::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'price', + NEW.price_array[1]::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'unit_price', + NEW.price_array::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'total_stock', + NEW.total_stock::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'total_sold', + NEW.total_sold::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'total_lost', + NEW.total_lost::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'address', + NEW.address::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'next_restock', + NEW.next_restock::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'minimum_age', + NEW.minimum_age::TEXT); + INSERT INTO merchant_pending_webhooks + (webhook_serial, url, http_method, body) + VALUES + (webhook.webhook_serial, + webhook.url, + webhook.http_method, + resolved_body); + END LOOP; + NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG; + END IF; + IF TG_OP = 'UPDATE' THEN + FOR webhook IN + SELECT webhook_serial, + url, + http_method, + body_template + FROM merchant_webhook + WHERE event_type = 'inventory_updated' + LOOP + resolved_body := webhook.body_template; + resolved_body := merchant.replace_placeholder(resolved_body, + 'webhook_type', + 'inventory_updated'); + resolved_body := merchant.replace_placeholder(resolved_body, + 'product_serial', + NEW.product_serial::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'product_id', + NEW.product_id); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_description', + OLD.description); + resolved_body := merchant.replace_placeholder(resolved_body, + 'description', + NEW.description); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_description_i18n', + OLD.description_i18n::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'description_i18n', + NEW.description_i18n::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_unit', + OLD.unit); + resolved_body := merchant.replace_placeholder(resolved_body, + 'unit', + NEW.unit); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_image', + OLD.image); + resolved_body := merchant.replace_placeholder(resolved_body, + 'image', + NEW.image); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_taxes', + OLD.taxes::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'taxes', + NEW.taxes::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_price', + OLD.price_array[1]::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_unit_price', + OLD.price_array::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'price', + NEW.price_array[1]::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'unit_price', + NEW.price_array::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_total_stock', + OLD.total_stock::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'total_stock', + NEW.total_stock::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_total_sold', + OLD.total_sold::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'total_sold', + NEW.total_sold::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_total_lost', + OLD.total_lost::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'total_lost', + NEW.total_lost::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_address', + OLD.address::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'address', + NEW.address::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_next_restock', + OLD.next_restock::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'next_restock', + NEW.next_restock::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'old_minimum_age', + OLD.minimum_age::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'minimum_age', + NEW.minimum_age::TEXT); + INSERT INTO merchant_pending_webhooks + (webhook_serial, url, http_method, body) + VALUES + (webhook.webhook_serial, + webhook.url, + webhook.http_method, + resolved_body); + END LOOP; + NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG; + END IF; + IF TG_OP = 'DELETE' THEN + FOR webhook IN + SELECT webhook_serial, + url, + http_method, + body_template + FROM merchant_webhook + WHERE event_type = 'inventory_deleted' + LOOP + resolved_body := webhook.body_template; + resolved_body := merchant.replace_placeholder(resolved_body, + 'webhook_type', + 'inventory_deleted'); + resolved_body := merchant.replace_placeholder(resolved_body, + 'product_serial', + OLD.product_serial::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'product_id', + OLD.product_id); + resolved_body := merchant.replace_placeholder(resolved_body, + 'description', + OLD.description); + resolved_body := merchant.replace_placeholder(resolved_body, + 'description_i18n', + OLD.description_i18n::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'unit', + OLD.unit); + resolved_body := merchant.replace_placeholder(resolved_body, + 'image', + OLD.image); + resolved_body := merchant.replace_placeholder(resolved_body, + 'taxes', + OLD.taxes::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'price', + OLD.price_array[1]::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'unit_price', + OLD.price_array::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'total_stock', + OLD.total_stock::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'total_sold', + OLD.total_sold::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'total_lost', + OLD.total_lost::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'address', + OLD.address::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'next_restock', + OLD.next_restock::TEXT); + resolved_body := merchant.replace_placeholder(resolved_body, + 'minimum_age', + OLD.minimum_age::TEXT); + INSERT INTO merchant_pending_webhooks + (webhook_serial, url, http_method, body) + VALUES + (webhook.webhook_serial, + webhook.url, + webhook.http_method, + resolved_body); + END LOOP; + NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG; + END IF; + RETURN NULL; + END; + $BODY$ + $OUTER$, s); + + -- merchant_contract_terms_insert_statistics_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_contract_terms_insert_statistics_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + BEGIN + CALL merchant_do_bump_number_stat + ('orders-claimed' + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,1); + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- merchant_contract_terms_update_statistics_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_contract_terms_update_statistics_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + DECLARE + my_rec RECORD; + BEGIN + IF (NEW.wired AND NOT OLD.wired) + THEN + CALL merchant_do_bump_number_stat + ('orders-settled' + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,1); + END IF; + IF (NEW.paid AND NOT OLD.paid) + THEN + CALL merchant_do_bump_number_stat + ('orders-paid' + ,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' + ,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' + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,my_rec.deposit_fee); + END LOOP; + END IF; + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- merchant_deposits_insert_statistics_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_deposits_insert_statistics_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + BEGIN + CALL merchant_do_bump_amount_stat + ('deposits-received' + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,NEW.amount_with_fee); + CALL merchant_do_bump_amount_stat + ('deposits-fees-paid' + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,NEW.deposit_fee); + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- merchant_expected_transfers_insert_statistics_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_expected_transfers_insert_statistics_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + BEGIN + IF NEW.wire_fee IS NOT NULL + THEN + CALL merchant_do_bump_amount_stat + ('wire-fees-paid' + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,NEW.wire_fee); + END IF; + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- merchant_expected_transfers_insert_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_expected_transfers_insert_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + BEGIN + UPDATE merchant_transfers + SET expected = TRUE + WHERE wtid = NEW.wtid + AND exchange_url = NEW.exchange_url + AND credit_amount = NEW.expected_credit_amount; + NEW.confirmed = FOUND; + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- merchant_expected_transfers_update_statistics_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_expected_transfers_update_statistics_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + BEGIN + IF NEW.wire_fee IS NOT NULL AND OLD.wire_fee IS NULL + THEN + CALL merchant_do_bump_amount_stat + ('wire-fees-paid' + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,NEW.wire_fee); + END IF; + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- merchant_expected_transfers_update_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_expected_transfers_update_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + BEGIN + UPDATE merchant_transfers + SET expected = TRUE + WHERE wtid = NEW.wtid + AND exchange_url = NEW.exchange_url + AND credit_amount = NEW.expected_credit_amount + AND NOT expected; + NEW.confirmed = NEW.confirmed OR FOUND; + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- merchant_issued_tokens_insert_statistics_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_issued_tokens_insert_statistics_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + BEGIN + CALL merchant_do_bump_number_stat + ('tokens-issued' + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,1); + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- merchant_kyc_insert_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_kyc_insert_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + BEGIN + CALL merchant.merchant_send_kyc_notification(NEW.account_serial, + NEW.exchange_url); + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- merchant_kyc_update_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_kyc_update_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + BEGIN + IF (OLD.kyc_ok + ,OLD.last_rule_gen + ,OLD.aml_review + ,OLD.jaccount_limits) + IS DISTINCT FROM + (NEW.kyc_ok + ,NEW.last_rule_gen + ,NEW.aml_review + ,NEW.jaccount_limits) + THEN + CALL merchant.merchant_send_kyc_notification(NEW.account_serial, + NEW.exchange_url); + END IF; + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- merchant_orders_insert_statistics_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_orders_insert_statistics_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + BEGIN + CALL merchant_do_bump_number_stat + ('orders-created' + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,1); + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- merchant_refunds_insert_statistics_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_refunds_insert_statistics_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + BEGIN + CALL merchant_do_bump_amount_stat + ('refunds-granted' + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,NEW.refund_amount); + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- merchant_transfer_signatures_insert_statistics_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_transfer_signatures_insert_statistics_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + BEGIN + CALL merchant_do_bump_amount_stat + ('wire-fees-paid' + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,NEW.wire_fee); + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- merchant_transfers_insert_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_transfers_insert_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + BEGIN + UPDATE merchant_expected_transfers + SET confirmed = TRUE + WHERE wtid = NEW.wtid + AND exchange_url = NEW.exchange_url + AND expected_credit_amount = NEW.credit_amount; + NEW.expected = FOUND; + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- merchant_used_tokens_insert_statistics_trigger + EXECUTE format($OUTER$ + CREATE FUNCTION %I.merchant_used_tokens_insert_statistics_trigger() RETURNS trigger + LANGUAGE plpgsql + AS $BODY$ + BEGIN + CALL merchant_do_bump_number_stat + ('tokens-used' + ,CURRENT_TIMESTAMP(0)::TIMESTAMP + ,1); + RETURN NEW; + END $BODY$ + $OUTER$, s); + + -- ------------------------------------------------------------------- + -- 2. CREATE TRIGGER attachments (16 triggers). + -- ------------------------------------------------------------------- + + EXECUTE format('CREATE TRIGGER merchant_contract_terms_on_insert_statistic' + || ' AFTER INSERT ON %I.merchant_contract_terms' + || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_contract_terms_insert_statistics_trigger()', + s, s); + + EXECUTE format('CREATE TRIGGER merchant_contract_terms_on_update_statistic' + || ' AFTER UPDATE ON %I.merchant_contract_terms' + || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_contract_terms_update_statistics_trigger()', + s, s); + + EXECUTE format('CREATE TRIGGER merchant_deposits_on_insert_statistic' + || ' AFTER INSERT ON %I.merchant_deposits' + || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_deposits_insert_statistics_trigger()', + s, s); + + EXECUTE format('CREATE TRIGGER merchant_expected_transfers_on_insert' + || ' BEFORE INSERT ON %I.merchant_expected_transfers' + || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_expected_transfers_insert_trigger()', + s, s); + + EXECUTE format('CREATE TRIGGER merchant_expected_transfers_on_insert_statistic' + || ' AFTER INSERT ON %I.merchant_expected_transfers' + || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_expected_transfers_insert_statistics_trigger()', + s, s); + + EXECUTE format('CREATE TRIGGER merchant_expected_transfers_on_update' + || ' BEFORE UPDATE ON %I.merchant_expected_transfers' + || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_expected_transfers_update_trigger()', + s, s); + + EXECUTE format('CREATE TRIGGER merchant_expected_transfers_on_update_statistic' + || ' AFTER INSERT ON %I.merchant_expected_transfers' + || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_expected_transfers_update_statistics_trigger()', + s, s); + + EXECUTE format('CREATE TRIGGER merchant_issued_tokens_on_insert_statistic' + || ' AFTER INSERT ON %I.merchant_issued_tokens' + || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_issued_tokens_insert_statistics_trigger()', + s, s); + + EXECUTE format('CREATE TRIGGER merchant_kyc_on_insert' + || ' AFTER INSERT ON %I.merchant_kyc' + || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_kyc_insert_trigger()', + s, s); + + EXECUTE format('CREATE TRIGGER merchant_kyc_on_update' + || ' AFTER UPDATE ON %I.merchant_kyc' + || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_kyc_update_trigger()', + s, s); + + EXECUTE format('CREATE TRIGGER merchant_orders_on_insert_statistic' + || ' AFTER INSERT ON %I.merchant_orders' + || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_orders_insert_statistics_trigger()', + s, s); + + EXECUTE format('CREATE TRIGGER merchant_refunds_on_insert_statistic' + || ' AFTER INSERT ON %I.merchant_refunds' + || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_refunds_insert_statistics_trigger()', + s, s); + + EXECUTE format('CREATE TRIGGER merchant_transfers_on_insert' + || ' BEFORE INSERT ON %I.merchant_transfers' + || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_transfers_insert_trigger()', + s, s); + + EXECUTE format('CREATE TRIGGER merchant_used_tokens_on_insert_statistic' + || ' AFTER INSERT ON %I.merchant_used_tokens' + || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_used_tokens_insert_statistics_trigger()', + s, s); + + EXECUTE format('CREATE TRIGGER trigger_category_changes' + || ' AFTER INSERT OR DELETE OR UPDATE ON %I.merchant_categories' + || ' FOR EACH ROW EXECUTE FUNCTION %I.handle_category_changes()', + s, s); + + EXECUTE format('CREATE TRIGGER trigger_inventory_changes' + || ' AFTER INSERT OR DELETE OR UPDATE ON %I.merchant_inventory' + || ' FOR EACH ROW EXECUTE FUNCTION %I.handle_inventory_changes()', + s, s); diff --git a/src/backenddb/pg_create_instance_trigger.sql b/src/backenddb/pg_create_instance_trigger.sql @@ -0,0 +1,57 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +DROP FUNCTION IF EXISTS merchant.merchant_instances_after_insert_trigger() CASCADE; +CREATE FUNCTION merchant.merchant_instances_after_insert_trigger() + RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + PERFORM merchant.create_instance_schema(NEW.merchant_serial); + RETURN NULL; +END $$; +COMMENT ON FUNCTION merchant.merchant_instances_after_insert_trigger() + IS 'Builds the per-instance schema merchant_instance_<merchant_serial>' + ' for the newly inserted row.'; + +DROP TRIGGER IF EXISTS merchant_instances_on_insert + ON merchant.merchant_instances; +CREATE TRIGGER merchant_instances_on_insert + AFTER INSERT ON merchant.merchant_instances + FOR EACH ROW + EXECUTE FUNCTION merchant.merchant_instances_after_insert_trigger(); + + +DROP FUNCTION IF EXISTS merchant.merchant_instances_after_delete_trigger() CASCADE; +CREATE FUNCTION merchant.merchant_instances_after_delete_trigger() + RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + EXECUTE format('DROP SCHEMA IF EXISTS %I CASCADE', + 'merchant_instance_' || OLD.merchant_serial::TEXT); + RETURN NULL; +END $$; +COMMENT ON FUNCTION merchant.merchant_instances_after_delete_trigger() + IS 'Drops the per-instance schema merchant_instance_<merchant_serial>' + ' when its row in merchant.merchant_instances is removed.'; + +DROP TRIGGER IF EXISTS merchant_instances_on_delete + ON merchant.merchant_instances; +CREATE TRIGGER merchant_instances_on_delete + AFTER DELETE ON merchant.merchant_instances + FOR EACH ROW + EXECUTE FUNCTION merchant.merchant_instances_after_delete_trigger(); diff --git a/src/backenddb/pg_expire_locks.sql b/src/backenddb/pg_expire_locks.sql @@ -0,0 +1,54 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +DROP FUNCTION IF EXISTS merchant.expire_locks(INT8); +CREATE FUNCTION merchant.expire_locks(IN p_now INT8) +RETURNS INT8 +LANGUAGE plpgsql +AS $FN$ +DECLARE + rec RECORD; + s TEXT; + total INT8 := 0; + affected INT8; +BEGIN + FOR rec IN + SELECT merchant_serial FROM merchant.merchant_instances + LOOP + s := 'merchant_instance_' || rec.merchant_serial::TEXT; + EXECUTE format('DELETE FROM %I.merchant_inventory_locks' + ' WHERE expiration < $1', s) USING p_now; + GET DIAGNOSTICS affected = ROW_COUNT; + total := total + affected; + + EXECUTE format('DELETE FROM %I.merchant_orders' + ' WHERE pay_deadline < $1', s) USING p_now; + GET DIAGNOSTICS affected = ROW_COUNT; + total := total + affected; + + EXECUTE format('DELETE FROM %I.merchant_contract_terms' + ' WHERE NOT paid' + ' AND pay_deadline < $1', s) USING p_now; + GET DIAGNOSTICS affected = ROW_COUNT; + total := total + affected; + END LOOP; + RETURN total; +END +$FN$; +COMMENT ON FUNCTION merchant.expire_locks(INT8) + IS 'Loops over all instance schemas and DELETEs expired inventory locks,' + ' unpaid orders past their pay_deadline, and unpaid contracts past their' + ' pay_deadline. Returns the total number of rows deleted.'; diff --git a/src/backenddb/pg_lookup_instances.sql b/src/backenddb/pg_lookup_instances.sql @@ -0,0 +1,95 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +DROP FUNCTION IF EXISTS merchant.lookup_instances(BOOLEAN, TEXT); +CREATE FUNCTION merchant.lookup_instances( + IN p_active_only BOOLEAN, + IN p_merchant_id TEXT) +RETURNS TABLE( + out_merchant_serial BIGINT, + out_merchant_pub BYTEA, + out_auth_hash BYTEA, + out_auth_salt BYTEA, + out_merchant_priv BYTEA, + out_merchant_id TEXT, + out_merchant_name TEXT, + out_address JSONB, + out_jurisdiction JSONB, + out_use_stefan BOOLEAN, + out_phone_validated BOOLEAN, + out_email_validated BOOLEAN, + out_default_wire_transfer_delay INT8, + out_default_pay_delay INT8, + out_default_refund_delay INT8, + out_website TEXT, + out_email TEXT, + out_phone_number TEXT, + out_logo BYTEA, + out_default_wire_transfer_rounding_interval time_rounder_interval) +LANGUAGE plpgsql +AS $FN$ +DECLARE + rec RECORD; + pkey BYTEA; +BEGIN + FOR rec IN + SELECT mi.* + FROM merchant.merchant_instances mi + WHERE p_merchant_id IS NULL + OR mi.merchant_id = p_merchant_id + LOOP + pkey := NULL; + BEGIN + EXECUTE format('SELECT merchant_priv FROM %I.merchant_keys', + 'merchant_instance_' || rec.merchant_serial::TEXT) + INTO pkey; + EXCEPTION + WHEN undefined_table THEN + pkey := NULL; + END; + IF p_active_only AND pkey IS NULL THEN + CONTINUE; + END IF; + out_merchant_serial := rec.merchant_serial; + out_merchant_pub := rec.merchant_pub; + out_auth_hash := rec.auth_hash; + out_auth_salt := rec.auth_salt; + out_merchant_priv := pkey; + out_merchant_id := rec.merchant_id; + out_merchant_name := rec.merchant_name; + out_address := rec.address; + out_jurisdiction := rec.jurisdiction; + out_use_stefan := rec.use_stefan; + out_phone_validated := rec.phone_validated; + out_email_validated := rec.email_validated; + out_default_wire_transfer_delay := rec.default_wire_transfer_delay; + out_default_pay_delay := rec.default_pay_delay; + out_default_refund_delay := rec.default_refund_delay; + out_website := rec.website; + out_email := rec.email; + out_phone_number := rec.phone_number; + out_logo := rec.logo; + out_default_wire_transfer_rounding_interval := + rec.default_wire_transfer_rounding_interval; + RETURN NEXT; + END LOOP; +END +$FN$; +COMMENT ON FUNCTION merchant.lookup_instances(BOOLEAN, TEXT) + IS 'Returns one row per matching instance, joining merchant.merchant_instances' + ' with the per-instance merchant_keys table (merchant_priv NULL if absent).' + ' If p_active_only is true, instances without a private key are skipped.' + ' If p_merchant_id is non-NULL, only the matching instance is returned.'; diff --git a/src/backenddb/pg_lookup_pending_deposits.sql b/src/backenddb/pg_lookup_pending_deposits.sql @@ -0,0 +1,230 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +DROP FUNCTION IF EXISTS merchant.lookup_pending_deposits(TEXT, INT8, INT8, BOOLEAN); +CREATE FUNCTION merchant.lookup_pending_deposits( + IN p_exchange_url TEXT, + IN p_now INT8, + IN p_limit INT8, + IN p_allow_future BOOLEAN) +RETURNS TABLE( + out_deposit_serial INT8, + out_h_contract_terms BYTEA, + out_merchant_priv BYTEA, + out_merchant_id TEXT, + out_wire_transfer_deadline INT8, + out_retry_time INT8, + out_h_wire BYTEA, + out_amount_with_fee taler_amount_currency, + out_deposit_fee taler_amount_currency, + out_coin_pub BYTEA) +LANGUAGE plpgsql +AS $FN$ +DECLARE + rec RECORD; + s TEXT; + pkey BYTEA; + inner_rec RECORD; + remaining INT8 := p_limit; +BEGIN + FOR rec IN + SELECT merchant_serial, merchant_id FROM merchant.merchant_instances + LOOP + EXIT WHEN remaining <= 0; + s := 'merchant_instance_' || rec.merchant_serial::TEXT; + pkey := NULL; + BEGIN + EXECUTE format('SELECT merchant_priv FROM %I.merchant_keys', s) + INTO pkey; + EXCEPTION + WHEN undefined_table THEN + pkey := NULL; + END; + BEGIN + FOR inner_rec IN + EXECUTE format( + 'SELECT' + ' md.deposit_serial AS deposit_serial' + ' ,mct.h_contract_terms AS h_contract_terms' + ' ,mdc.wire_transfer_deadline AS wire_transfer_deadline' + ' ,md.settlement_retry_time AS retry_time' + ' ,ma.h_wire AS h_wire' + ' ,md.amount_with_fee AS amount_with_fee' + ' ,md.deposit_fee AS deposit_fee' + ' ,md.coin_pub AS coin_pub' + ' FROM %I.merchant_deposits md' + ' JOIN %I.merchant_deposit_confirmations mdc' + ' USING (deposit_confirmation_serial)' + ' JOIN %I.merchant_contract_terms mct' + ' ON (mct.order_serial=mdc.order_serial)' + ' JOIN %I.merchant_accounts ma' + ' ON (mdc.account_serial=ma.account_serial)' + ' LEFT JOIN %I.merchant_kyc kyc' + ' ON (mdc.account_serial=kyc.account_serial)' + ' WHERE (mdc.exchange_url=$1)' + ' AND md.settlement_retry_needed' + ' AND ($4 OR (md.settlement_retry_time < $2))' + ' AND ( (kyc.kyc_ok IS NULL) OR kyc.kyc_ok)' + ' ORDER BY md.settlement_retry_time ASC' + ' LIMIT $3', + s, s, s, s, s) + USING p_exchange_url, p_now, remaining, p_allow_future + LOOP + out_deposit_serial := inner_rec.deposit_serial; + out_h_contract_terms := inner_rec.h_contract_terms; + out_merchant_priv := pkey; + out_merchant_id := rec.merchant_id; + out_wire_transfer_deadline := inner_rec.wire_transfer_deadline; + out_retry_time := inner_rec.retry_time; + out_h_wire := inner_rec.h_wire; + out_amount_with_fee := inner_rec.amount_with_fee; + out_deposit_fee := inner_rec.deposit_fee; + out_coin_pub := inner_rec.coin_pub; + remaining := remaining - 1; + RETURN NEXT; + EXIT WHEN remaining <= 0; + END LOOP; + EXCEPTION + WHEN undefined_table THEN + NULL; + END; + END LOOP; +END +$FN$; +COMMENT ON FUNCTION merchant.lookup_pending_deposits(TEXT, INT8, INT8, BOOLEAN) + IS 'Returns up to p_limit pending-settlement deposit rows for the given exchange' + ' across all instance schemas, joined with the per-instance merchant_keys.'; + + +DROP FUNCTION IF EXISTS merchant.lookup_reports_pending(); +CREATE FUNCTION merchant.lookup_reports_pending() +RETURNS TABLE( + out_merchant_id TEXT, + out_report_serial INT8, + out_report_program_section TEXT, + out_report_description TEXT, + out_mime_type TEXT, + out_report_token BYTEA, + out_target_address TEXT, + out_frequency INT8, + out_frequency_shift INT8, + out_next_transmission INT8, + out_one_shot_hidden BOOLEAN) +LANGUAGE plpgsql +AS $FN$ +DECLARE + rec RECORD; + s TEXT; + inner_rec RECORD; + found BOOLEAN := FALSE; +BEGIN + FOR rec IN + SELECT merchant_serial, merchant_id FROM merchant.merchant_instances + LOOP + s := 'merchant_instance_' || rec.merchant_serial::TEXT; + BEGIN + EXECUTE format( + 'SELECT' + ' report_serial AS rs' + ' ,report_program_section AS rps' + ' ,report_description AS rd' + ' ,mime_type AS mt' + ' ,report_token AS rt' + ' ,target_address AS ta' + ' ,frequency AS f' + ' ,frequency_shift AS fs' + ' ,next_transmission AS nt' + ' ,one_shot_hidden AS osh' + ' FROM %I.merchant_reports' + ' ORDER BY next_transmission ASC LIMIT 1', s) + INTO inner_rec; + IF inner_rec IS NULL THEN + CONTINUE; + END IF; + IF (NOT found) OR (inner_rec.nt < out_next_transmission) THEN + out_merchant_id := rec.merchant_id; + out_report_serial := inner_rec.rs; + out_report_program_section := inner_rec.rps; + out_report_description := inner_rec.rd; + out_mime_type := inner_rec.mt; + out_report_token := inner_rec.rt; + out_target_address := inner_rec.ta; + out_frequency := inner_rec.f; + out_frequency_shift := inner_rec.fs; + out_next_transmission := inner_rec.nt; + out_one_shot_hidden := inner_rec.osh; + found := TRUE; + END IF; + EXCEPTION + WHEN undefined_table THEN + NULL; + END; + END LOOP; + IF found THEN + RETURN NEXT; + END IF; +END +$FN$; +COMMENT ON FUNCTION merchant.lookup_reports_pending() + IS 'Returns the single next-due report (smallest next_transmission)' + ' across all instance schemas, or no row if no reports exist.'; + + +DROP FUNCTION IF EXISTS merchant.check_report(INT8, BYTEA, TEXT); +CREATE FUNCTION merchant.check_report( + IN p_report_id INT8, + IN p_report_token BYTEA, + IN p_mime_type TEXT) +RETURNS TABLE( + out_merchant_id TEXT, + out_data_source TEXT) +LANGUAGE plpgsql +AS $FN$ +DECLARE + rec RECORD; + s TEXT; + inner_rec RECORD; +BEGIN + FOR rec IN + SELECT merchant_serial, merchant_id FROM merchant.merchant_instances + LOOP + s := 'merchant_instance_' || rec.merchant_serial::TEXT; + BEGIN + EXECUTE format( + 'SELECT data_source AS ds' + ' FROM %I.merchant_reports' + ' WHERE report_serial=$1' + ' AND report_token=$2' + ' AND mime_type=$3', s) + USING p_report_id, p_report_token, p_mime_type + INTO inner_rec; + IF inner_rec IS NOT NULL THEN + out_merchant_id := rec.merchant_id; + out_data_source := inner_rec.ds; + RETURN NEXT; + RETURN; + END IF; + EXCEPTION + WHEN undefined_table THEN + NULL; + END; + END LOOP; +END +$FN$; +COMMENT ON FUNCTION merchant.check_report(INT8, BYTEA, TEXT) + IS 'Searches all instance schemas for a report matching the given' + ' (report_serial, report_token, mime_type). Returns at most one row' + ' (the first matching instance encountered).'; diff --git a/src/backenddb/pg_lookup_pending_webhooks.sql b/src/backenddb/pg_lookup_pending_webhooks.sql @@ -0,0 +1,133 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +DROP FUNCTION IF EXISTS merchant.lookup_pending_webhooks(INT8); +CREATE FUNCTION merchant.lookup_pending_webhooks(IN p_now INT8) +RETURNS TABLE( + out_webhook_pending_serial INT8, + out_next_attempt INT8, + out_retries INT4, + out_url TEXT, + out_http_method TEXT, + out_header TEXT, + out_body TEXT) +LANGUAGE plpgsql +AS $FN$ +DECLARE + rec RECORD; + s TEXT; + inner_rec RECORD; +BEGIN + FOR rec IN + SELECT merchant_serial FROM merchant.merchant_instances + LOOP + s := 'merchant_instance_' || rec.merchant_serial::TEXT; + BEGIN + FOR inner_rec IN + EXECUTE format('SELECT' + ' webhook_pending_serial AS wps' + ',next_attempt AS na' + ',retries AS r' + ',url AS u' + ',http_method AS hm' + ',header AS h' + ',body AS b' + ' FROM %I.merchant_pending_webhooks' + ' WHERE next_attempt <= $1' + ' ORDER BY next_attempt ASC', s) + USING p_now + LOOP + out_webhook_pending_serial := inner_rec.wps; + out_next_attempt := inner_rec.na; + out_retries := inner_rec.r; + out_url := inner_rec.u; + out_http_method := inner_rec.hm; + out_header := inner_rec.h; + out_body := inner_rec.b; + RETURN NEXT; + END LOOP; + EXCEPTION + WHEN undefined_table THEN + NULL; + END; + END LOOP; +END +$FN$; +COMMENT ON FUNCTION merchant.lookup_pending_webhooks(INT8) + IS 'Returns one row per pending webhook (next_attempt <= p_now) across all' + ' instance schemas, ordered per-instance by next_attempt ASC.'; + + +DROP FUNCTION IF EXISTS merchant.lookup_future_webhook(); +CREATE FUNCTION merchant.lookup_future_webhook() +RETURNS TABLE( + out_webhook_pending_serial INT8, + out_next_attempt INT8, + out_retries INT4, + out_url TEXT, + out_http_method TEXT, + out_header TEXT, + out_body TEXT) +LANGUAGE plpgsql +AS $FN$ +DECLARE + rec RECORD; + s TEXT; + best RECORD; + found BOOLEAN := FALSE; +BEGIN + best := NULL; + FOR rec IN + SELECT merchant_serial FROM merchant.merchant_instances + LOOP + s := 'merchant_instance_' || rec.merchant_serial::TEXT; + BEGIN + EXECUTE format('SELECT' + ' webhook_pending_serial AS wps' + ',next_attempt AS na' + ',retries AS r' + ',url AS u' + ',http_method AS hm' + ',header AS h' + ',body AS b' + ' FROM %I.merchant_pending_webhooks' + ' ORDER BY next_attempt ASC LIMIT 1', s) + INTO best; + IF best IS NOT NULL + AND ((NOT found) + OR (best.na < out_next_attempt)) THEN + out_webhook_pending_serial := best.wps; + out_next_attempt := best.na; + out_retries := best.r; + out_url := best.u; + out_http_method := best.hm; + out_header := best.h; + out_body := best.b; + found := TRUE; + END IF; + EXCEPTION + WHEN undefined_table THEN + NULL; + END; + END LOOP; + IF found THEN + RETURN NEXT; + END IF; +END +$FN$; +COMMENT ON FUNCTION merchant.lookup_future_webhook() + IS 'Returns the single soonest-due pending webhook across all instances,' + ' or no row if no instance has any pending webhook.'; diff --git a/src/backenddb/pg_select_accounts.sql b/src/backenddb/pg_select_accounts.sql @@ -0,0 +1,84 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +DROP FUNCTION IF EXISTS merchant.select_accounts(TEXT); +CREATE FUNCTION merchant.select_accounts( + IN p_merchant_id TEXT) +RETURNS TABLE( + out_merchant_id TEXT, + out_merchant_priv BYTEA, + out_h_wire BYTEA, + out_salt BYTEA, + out_payto_uri TEXT, + out_credit_facade_url TEXT, + out_credit_facade_credentials JSONB, + out_extra_wire_subject_metadata TEXT, + out_active BOOLEAN) +LANGUAGE plpgsql +AS $FN$ +DECLARE + rec RECORD; + pkey BYTEA; +BEGIN + FOR rec IN + SELECT mi.merchant_serial, mi.merchant_id + FROM merchant.merchant_instances mi + WHERE p_merchant_id IS NULL + OR mi.merchant_id = p_merchant_id + LOOP + pkey := NULL; + BEGIN + EXECUTE format('SELECT merchant_priv FROM %I.merchant_keys', + 'merchant_instance_' || rec.merchant_serial::TEXT) + INTO pkey; + EXCEPTION + WHEN undefined_table THEN + pkey := NULL; + END; + BEGIN + FOR out_h_wire, + out_salt, + out_payto_uri, + out_credit_facade_url, + out_credit_facade_credentials, + out_extra_wire_subject_metadata, + out_active + IN EXECUTE format('SELECT' + ' h_wire' + ',salt' + ',payto_uri' + ',credit_facade_url' + ',credit_facade_credentials' + ',extra_wire_subject_metadata' + ',active' + ' FROM %I.merchant_accounts', + 'merchant_instance_' || rec.merchant_serial::TEXT) + LOOP + out_merchant_id := rec.merchant_id; + out_merchant_priv := pkey; + RETURN NEXT; + END LOOP; + EXCEPTION + WHEN undefined_table THEN + NULL; + END; + END LOOP; +END +$FN$; +COMMENT ON FUNCTION merchant.select_accounts(TEXT) + IS 'Returns one row per merchant_account across all (or one) instance schemas,' + ' joined with the per-instance merchant_keys.merchant_priv (NULL if absent).' + ' If p_merchant_id is non-NULL, only that instance is scanned.'; diff --git a/src/backenddb/pg_select_all_donau_instances.sql b/src/backenddb/pg_select_all_donau_instances.sql @@ -0,0 +1,116 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +DROP FUNCTION IF EXISTS merchant.select_all_donau_instances(); +CREATE FUNCTION merchant.select_all_donau_instances() +RETURNS TABLE( + out_donau_instances_serial INT8, + out_donau_url TEXT, + out_charity_name TEXT, + out_charity_pub_key BYTEA, + out_charity_id INT8, + out_charity_max_per_year taler_amount_currency, + out_charity_receipts_to_date taler_amount_currency, + out_current_year INT8, + out_keys_json JSONB) +LANGUAGE plpgsql +AS $FN$ +DECLARE + rec RECORD; + s TEXT; + inner_rec RECORD; +BEGIN + FOR rec IN + SELECT merchant_serial, merchant_pub FROM merchant.merchant_instances + LOOP + s := 'merchant_instance_' || rec.merchant_serial::TEXT; + BEGIN + FOR inner_rec IN + EXECUTE format( + 'SELECT' + ' di.donau_instances_serial AS dis' + ' ,di.donau_url AS du' + ' ,di.charity_name AS cn' + ' ,di.charity_id AS ci' + ' ,di.charity_max_per_year AS cmp' + ' ,di.charity_receipts_to_date AS crt' + ' ,di.current_year AS cy' + ' ,dk.keys_json AS kj' + ' FROM %I.merchant_donau_instances di' + ' LEFT JOIN merchant.merchant_donau_keys dk' + ' ON di.donau_url = dk.donau_url', s) + LOOP + out_donau_instances_serial := inner_rec.dis; + out_donau_url := inner_rec.du; + out_charity_name := inner_rec.cn; + out_charity_pub_key := rec.merchant_pub; + out_charity_id := inner_rec.ci; + out_charity_max_per_year := inner_rec.cmp; + out_charity_receipts_to_date := inner_rec.crt; + out_current_year := inner_rec.cy; + out_keys_json := inner_rec.kj; + RETURN NEXT; + END LOOP; + EXCEPTION + WHEN undefined_table THEN + NULL; + END; + END LOOP; +END +$FN$; +COMMENT ON FUNCTION merchant.select_all_donau_instances() + IS 'Returns all donau-instance configurations across every per-instance' + ' schema, joined with merchant.merchant_donau_keys for the keys json' + ' and with merchant.merchant_instances for the charity public key.'; + + +DROP FUNCTION IF EXISTS merchant.select_donau_instances_filtered(TEXT); +CREATE FUNCTION merchant.select_donau_instances_filtered(IN p_currency TEXT) +RETURNS TABLE( + out_donau_url TEXT) +LANGUAGE plpgsql +AS $FN$ +DECLARE + rec RECORD; + s TEXT; + inner_rec RECORD; +BEGIN + FOR rec IN + SELECT merchant_serial FROM merchant.merchant_instances + LOOP + s := 'merchant_instance_' || rec.merchant_serial::TEXT; + BEGIN + FOR inner_rec IN + EXECUTE format( + 'SELECT donau_url AS du' + ' FROM %I.merchant_donau_instances' + ' WHERE (charity_max_per_year).curr = $1', s) + USING p_currency + LOOP + out_donau_url := inner_rec.du; + RETURN NEXT; + END LOOP; + EXCEPTION + WHEN undefined_table THEN + NULL; + END; + END LOOP; +END +$FN$; +COMMENT ON FUNCTION merchant.select_donau_instances_filtered(TEXT) + IS 'Returns the donau_url of every donau-instance configuration whose' + ' charity_max_per_year currency matches p_currency, scanning across' + ' all per-instance schemas.'; diff --git a/src/backenddb/pg_select_open_transfers.sql b/src/backenddb/pg_select_open_transfers.sql @@ -0,0 +1,124 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +DROP FUNCTION IF EXISTS merchant.select_open_transfers(INT8); +CREATE FUNCTION merchant.select_open_transfers(IN p_limit INT8) +RETURNS TABLE( + out_expected_credit_serial INT8, + out_instance_id TEXT, + out_exchange_url TEXT, + out_payto_uri TEXT, + out_wtid BYTEA, + out_retry_time INT8) +LANGUAGE plpgsql +AS $FN$ +DECLARE + rec RECORD; + s TEXT; + inner_rec RECORD; + remaining INT8 := p_limit; +BEGIN + FOR rec IN + SELECT merchant_serial, merchant_id FROM merchant.merchant_instances + LOOP + EXIT WHEN remaining <= 0; + s := 'merchant_instance_' || rec.merchant_serial::TEXT; + BEGIN + FOR inner_rec IN + EXECUTE format( + 'SELECT' + ' met.expected_credit_serial AS ecs' + ' ,met.exchange_url AS exu' + ' ,ma.payto_uri AS pu' + ' ,met.wtid AS wt' + ' ,met.retry_time AS rt' + ' FROM %I.merchant_expected_transfers met' + ' JOIN %I.merchant_accounts ma USING (account_serial)' + ' WHERE retry_needed' + ' ORDER BY retry_time ASC' + ' LIMIT $1', s, s) + USING remaining + LOOP + out_expected_credit_serial := inner_rec.ecs; + out_instance_id := rec.merchant_id; + out_exchange_url := inner_rec.exu; + out_payto_uri := inner_rec.pu; + out_wtid := inner_rec.wt; + out_retry_time := inner_rec.rt; + remaining := remaining - 1; + RETURN NEXT; + EXIT WHEN remaining <= 0; + END LOOP; + EXCEPTION + WHEN undefined_table THEN + NULL; + END; + END LOOP; +END +$FN$; +COMMENT ON FUNCTION merchant.select_open_transfers(INT8) + IS 'Returns up to p_limit retry-needed expected_transfer rows across all' + ' instance schemas, joined with the per-instance merchant_accounts.'; + + +DROP FUNCTION IF EXISTS merchant.select_wirewatch_accounts(); +CREATE FUNCTION merchant.select_wirewatch_accounts() +RETURNS TABLE( + out_merchant_id TEXT, + out_payto_uri TEXT, + out_credit_facade_url TEXT, + out_credit_facade_credentials JSONB, + out_last_bank_serial INT8) +LANGUAGE plpgsql +AS $FN$ +DECLARE + rec RECORD; + s TEXT; + inner_rec RECORD; +BEGIN + FOR rec IN + SELECT merchant_serial, merchant_id FROM merchant.merchant_instances + LOOP + s := 'merchant_instance_' || rec.merchant_serial::TEXT; + BEGIN + FOR inner_rec IN + EXECUTE format( + 'SELECT' + ' payto_uri AS pu' + ' ,credit_facade_url AS cfu' + ' ,credit_facade_credentials AS cfc' + ' ,last_bank_serial AS lbs' + ' FROM %I.merchant_accounts' + ' WHERE active' + ' AND credit_facade_url IS NOT NULL', s) + LOOP + out_merchant_id := rec.merchant_id; + out_payto_uri := inner_rec.pu; + out_credit_facade_url := inner_rec.cfu; + out_credit_facade_credentials := inner_rec.cfc; + out_last_bank_serial := inner_rec.lbs; + RETURN NEXT; + END LOOP; + EXCEPTION + WHEN undefined_table THEN + NULL; + END; + END LOOP; +END +$FN$; +COMMENT ON FUNCTION merchant.select_wirewatch_accounts() + IS 'Returns one row per active credit-facade-enabled merchant_account' + ' across all instance schemas.'; diff --git a/src/backenddb/purge_instance.c b/src/backenddb/purge_instance.c @@ -41,8 +41,25 @@ TALER_MERCHANTDB_purge_instance (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_query_param_string (merchant_id), GNUNET_PQ_query_param_end }; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute ("SET search_path TO merchant"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; check_connection (pg); + /* The AFTER DELETE trigger will DROP the per-instance schema. Reset + search_path off it first so subsequent queries on this connection + don't reference the about-to-be-dropped schema. */ + if (GNUNET_OK != + GNUNET_PQ_exec_statements (pg->conn, + es)) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; + } + GNUNET_free (pg->current_merchant_id); + pg->current_merchant_id = NULL; + pg->current_merchant_serial = 0; PREPARE (pg, "purge_instance", "DELETE FROM merchant_instances" diff --git a/src/backenddb/refund_coin.c b/src/backenddb/refund_coin.c @@ -34,43 +34,44 @@ TALER_MERCHANTDB_refund_coin (struct TALER_MERCHANTDB_PostgresContext *pg, const char *reason) { 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_timestamp (&refund_timestamp), GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_string (reason), GNUNET_PQ_query_param_end }; - PREPARE (pg, - "refund_coin", - "INSERT INTO merchant_refunds" - "(order_serial" - ",rtransaction_id" - ",refund_timestamp" - ",coin_pub" - ",reason" - ",refund_amount" - ") " - "SELECT " - " dcon.order_serial" - ",0" /* rtransaction_id always 0 for /abort */ - ",$3" - ",dep.coin_pub" - ",$5" - ",dep.amount_with_fee" - " FROM merchant_deposits dep" - " JOIN merchant_deposit_confirmations dcon" - " USING (deposit_confirmation_serial)" - " WHERE dep.coin_pub=$4" - " AND dcon.order_serial=" - " (SELECT order_serial" - " FROM merchant_contract_terms" - " WHERE h_contract_terms=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1))"); + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); + PREPARE_INSTANCE (pg, + stmt, + "refund_coin", + "INSERT INTO merchant_refunds" + "(order_serial" + ",rtransaction_id" + ",refund_timestamp" + ",coin_pub" + ",reason" + ",refund_amount" + ") " + "SELECT " + " dcon.order_serial" + ",0" /* rtransaction_id always 0 for /abort */ + ",$2" + ",dep.coin_pub" + ",$4" + ",dep.amount_with_fee" + " FROM merchant_deposits dep" + " JOIN merchant_deposit_confirmations dcon" + " USING (deposit_confirmation_serial)" + " WHERE dep.coin_pub=$3" + " AND dcon.order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " WHERE h_contract_terms=$1)"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "refund_coin", + stmt, params); } diff --git a/src/backenddb/select_account.c b/src/backenddb/select_account.c @@ -33,7 +33,6 @@ TALER_MERCHANTDB_select_account (struct TALER_MERCHANTDB_PostgresContext *pg, struct TALER_MERCHANTDB_AccountDetails *ad) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (id), GNUNET_PQ_query_param_auto_from_type (h_wire), GNUNET_PQ_query_param_end }; @@ -58,27 +57,28 @@ TALER_MERCHANTDB_select_account (struct TALER_MERCHANTDB_PostgresContext *pg, NULL), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (id, + pg->current_merchant_id)); ad->h_wire = *h_wire; ad->instance_id = id; check_connection (pg); - PREPARE (pg, - "select_account", - "SELECT" - " salt" - ",payto_uri" - ",credit_facade_url" - ",credit_facade_credentials::TEXT" - ",active" - ",extra_wire_subject_metadata" - " FROM merchant_accounts" - " WHERE merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1) " - " AND (h_wire=$2);"); + PREPARE_INSTANCE (pg, + stmt, + "select_account", + "SELECT" + " salt" + ",payto_uri" + ",credit_facade_url" + ",credit_facade_credentials::TEXT" + ",active" + ",extra_wire_subject_metadata" + " FROM merchant_accounts" + " WHERE h_wire=$1;"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "select_account", + stmt, params, rs); } diff --git a/src/backenddb/select_account_by_uri.c b/src/backenddb/select_account_by_uri.c @@ -33,7 +33,6 @@ TALER_MERCHANTDB_select_account_by_uri (struct TALER_MERCHANTDB_PostgresContext struct TALER_MERCHANTDB_AccountDetails *ad) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (id), GNUNET_PQ_query_param_string (payto_uri.full_payto), GNUNET_PQ_query_param_end }; @@ -54,29 +53,30 @@ TALER_MERCHANTDB_select_account_by_uri (struct TALER_MERCHANTDB_PostgresContext &ad->active), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (id, + pg->current_merchant_id)); ad->credit_facade_url = NULL; ad->credit_facade_credentials = NULL; ad->payto_uri.full_payto = GNUNET_strdup (payto_uri.full_payto); ad->instance_id = id; check_connection (pg); - PREPARE (pg, - "select_account_by_uri", - "SELECT" - " salt" - ",h_wire" - ",credit_facade_url" - ",credit_facade_credentials::TEXT" - ",active" - " FROM merchant_accounts" - " WHERE merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND payto_uri = $2"); + PREPARE_INSTANCE (pg, + stmt, + "select_account_by_uri", + "SELECT" + " salt" + ",h_wire" + ",credit_facade_url" + ",credit_facade_credentials::TEXT" + ",active" + " FROM merchant_accounts" + " WHERE payto_uri = $1"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "select_account_by_uri", + stmt, params, rs); } diff --git a/src/backenddb/select_accounts.c b/src/backenddb/select_accounts.c @@ -80,30 +80,30 @@ select_account_cb (void *cls, struct TALER_MerchantPrivateKeyP merchant_priv; bool no_priv; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("h_wire", + GNUNET_PQ_result_spec_auto_from_type ("out_h_wire", &acc.h_wire), - GNUNET_PQ_result_spec_auto_from_type ("salt", + GNUNET_PQ_result_spec_auto_from_type ("out_salt", &acc.salt), - GNUNET_PQ_result_spec_string ("payto_uri", + GNUNET_PQ_result_spec_string ("out_payto_uri", &payto.full_payto), - GNUNET_PQ_result_spec_string ("merchant_id", + GNUNET_PQ_result_spec_string ("out_merchant_id", &instance_id), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("credit_facade_url", + GNUNET_PQ_result_spec_string ("out_credit_facade_url", &facade_url), NULL), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("extra_wire_subject_metadata", + GNUNET_PQ_result_spec_string ("out_extra_wire_subject_metadata", &extra_wire_subject_metadata), NULL), GNUNET_PQ_result_spec_allow_null ( - TALER_PQ_result_spec_json ("credit_facade_credentials", + TALER_PQ_result_spec_json ("out_credit_facade_credentials", &credential), NULL), - GNUNET_PQ_result_spec_bool ("active", + GNUNET_PQ_result_spec_bool ("out_active", &acc.active), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_auto_from_type ("merchant_priv", + GNUNET_PQ_result_spec_auto_from_type ("out_merchant_priv", &merchant_priv), &no_priv), GNUNET_PQ_result_spec_end @@ -156,26 +156,16 @@ TALER_MERCHANTDB_select_accounts ( PREPARE (pg, "select_accounts", "SELECT" - " ma.h_wire" - ",ma.salt" - ",ma.payto_uri" - ",ma.credit_facade_url" - ",ma.credit_facade_credentials::TEXT" - ",ma.extra_wire_subject_metadata" - ",ma.active" - ",mk.merchant_priv" - ",mi.merchant_id" - " FROM merchant_accounts ma" - " JOIN merchant_instances mi" - " ON (mi.merchant_serial=ma.merchant_serial)" - " LEFT JOIN merchant_keys mk" - " ON (mk.merchant_serial=ma.merchant_serial)" - " WHERE" - " ($1::TEXT IS NULL) OR" - " (ma.merchant_serial=" - " (SELECT merchant_serial " - " FROM merchant_instances" - " WHERE merchant_id=$1));"); + " out_merchant_id" + " ,out_merchant_priv" + " ,out_h_wire" + " ,out_salt" + " ,out_payto_uri" + " ,out_credit_facade_url" + " ,out_credit_facade_credentials::TEXT" + " ,out_extra_wire_subject_metadata" + " ,out_active" + " FROM merchant.select_accounts($1)"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "select_accounts", params, diff --git a/src/backenddb/select_all_donau_instances.c b/src/backenddb/select_all_donau_instances.c @@ -76,24 +76,24 @@ select_donau_instance_cb (void *cls, int64_t current_year; json_t *donau_keys_json = NULL; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("donau_instances_serial", + GNUNET_PQ_result_spec_uint64 ("out_donau_instances_serial", &donau_instance_serial), - GNUNET_PQ_result_spec_string ("donau_url", + GNUNET_PQ_result_spec_string ("out_donau_url", &donau_url), - GNUNET_PQ_result_spec_string ("charity_name", + GNUNET_PQ_result_spec_string ("out_charity_name", &charity_name), - GNUNET_PQ_result_spec_auto_from_type ("charity_pub_key", + GNUNET_PQ_result_spec_auto_from_type ("out_charity_pub_key", &charity_pub_key), - GNUNET_PQ_result_spec_uint64 ("charity_id", + GNUNET_PQ_result_spec_uint64 ("out_charity_id", &charity_id), - TALER_PQ_result_spec_amount_with_currency ("charity_max_per_year", + TALER_PQ_result_spec_amount_with_currency ("out_charity_max_per_year", &charity_max_per_year), - TALER_PQ_result_spec_amount_with_currency ("charity_receipts_to_date", + TALER_PQ_result_spec_amount_with_currency ("out_charity_receipts_to_date", &charity_receipts_to_date), - GNUNET_PQ_result_spec_int64 ("current_year", + GNUNET_PQ_result_spec_int64 ("out_current_year", &current_year), GNUNET_PQ_result_spec_allow_null ( - TALER_PQ_result_spec_json ("keys_json", + TALER_PQ_result_spec_json ("out_keys_json", &donau_keys_json), NULL), GNUNET_PQ_result_spec_end @@ -144,20 +144,16 @@ TALER_MERCHANTDB_select_all_donau_instances ( PREPARE (pg, "select_all_donau_instances", "SELECT" - " di.donau_instances_serial" - ",di.donau_url" - ",di.charity_name" - ",mi.merchant_pub AS charity_pub_key" - ",di.charity_id" - ",di.charity_max_per_year" - ",di.charity_receipts_to_date" - ",di.current_year" - ",dk.keys_json::TEXT" - " FROM merchant_donau_instances di" - " LEFT JOIN merchant_donau_keys dk" - " ON di.donau_url = dk.donau_url" - " JOIN merchant_instances mi" - " ON di.merchant_instance_serial = mi.merchant_serial"); + " out_donau_instances_serial" + " ,out_donau_url" + " ,out_charity_name" + " ,out_charity_pub_key" + " ,out_charity_id" + " ,out_charity_max_per_year" + " ,out_charity_receipts_to_date" + " ,out_current_year" + " ,out_keys_json::TEXT" + " FROM merchant.select_all_donau_instances()"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "select_all_donau_instances", params, diff --git a/src/backenddb/select_category.c b/src/backenddb/select_category.c @@ -35,21 +35,43 @@ TALER_MERCHANTDB_select_category (struct TALER_MERCHANTDB_PostgresContext *pg, char **products) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&category_id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); + check_connection (pg); + PREPARE_INSTANCE (pg, + stmt, + "select_category", + "SELECT" + " category_name" + ",category_name_i18n::TEXT" + ",t.product_array AS products" + " FROM merchant_categories mc" + ",LATERAL (" + " SELECT ARRAY (" + " SELECT " + " mi.product_id AS product_id" + " FROM merchant_product_categories mpc" + " JOIN merchant_inventory mi" + " USING (product_serial)" + " WHERE mpc.category_serial = mc.category_serial" + " ) AS product_array" + " ) t" + " WHERE mc.category_serial=$1"); if (NULL == cd) { struct GNUNET_PQ_ResultSpec rs_null[] = { GNUNET_PQ_result_spec_end }; - check_connection (pg); return GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, - "select_category", + stmt, params, rs_null); } @@ -67,31 +89,9 @@ TALER_MERCHANTDB_select_category (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_result_spec_end }; - PREPARE (pg, - "select_category", - "SELECT" - " category_name" - ",category_name_i18n::TEXT" - ",t.product_array AS products" - " FROM merchant_categories mc" - " JOIN merchant_instances inst" - " USING (merchant_serial)" - ",LATERAL (" - " SELECT ARRAY (" - " SELECT " - " mi.product_id AS product_id" - " FROM merchant_product_categories mpc" - " JOIN merchant_inventory mi" - " USING (product_serial)" - " WHERE mpc.category_serial = mc.category_serial" - " ) AS product_array" - " ) t" - " WHERE inst.merchant_id=$1" - " AND mc.category_serial=$2"); - check_connection (pg); return GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, - "select_category", + stmt, params, rs); } diff --git a/src/backenddb/select_category_by_name.c b/src/backenddb/select_category_by_name.c @@ -34,7 +34,6 @@ TALER_MERCHANTDB_select_category_by_name (struct TALER_MERCHANTDB_PostgresContex uint64_t *category_id) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (category_name), GNUNET_PQ_query_param_end }; @@ -45,21 +44,23 @@ TALER_MERCHANTDB_select_category_by_name (struct TALER_MERCHANTDB_PostgresContex name_i18n), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "select_category_by_name", - "SELECT" - " category_serial" - ",category_name_i18n::TEXT" - " FROM merchant_categories mc" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " WHERE mi.merchant_id=$1" - " AND mc.category_name=$2"); + PREPARE_INSTANCE (pg, + stmt, + "select_category_by_name", + "SELECT" + " category_serial" + ",category_name_i18n::TEXT" + " FROM merchant_categories" + " WHERE category_name=$1"); return GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, - "select_category_by_name", + stmt, params, rs); } diff --git a/src/backenddb/select_donau_instance_by_serial.c b/src/backenddb/select_donau_instance_by_serial.c @@ -45,17 +45,21 @@ TALER_MERCHANTDB_select_donau_instance_by_serial (struct TALER_MERCHANTDB_Postgr GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "select_donau_instance_by_serial", - "SELECT donau_url" - " ,charity_id" - " FROM merchant_donau_instances" - " WHERE donau_instances_serial = $1"); + PREPARE_INSTANCE (pg, + stmt, + "select_donau_instance_by_serial", + "SELECT donau_url" + " ,charity_id" + " FROM merchant_donau_instances" + " WHERE donau_instances_serial = $1"); return GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, - "select_donau_instance_by_serial", + stmt, params, rs); } diff --git a/src/backenddb/select_donau_instances.c b/src/backenddb/select_donau_instances.c @@ -137,32 +137,36 @@ TALER_MERCHANTDB_select_donau_instances ( .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (id), + GNUNET_PQ_query_param_uint64 (&pg->current_merchant_serial), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "select_donau_instances", - "SELECT" - " di.donau_instances_serial" - ",di.donau_url" - ",di.charity_name" - ",mi.merchant_pub AS charity_pub_key" - ",di.charity_id" - ",di.charity_max_per_year" - ",di.charity_receipts_to_date" - ",di.current_year" - ",dk.keys_json::TEXT" - " FROM merchant_donau_instances di" - " LEFT JOIN merchant_donau_keys dk" - " ON di.donau_url = dk.donau_url" - " JOIN merchant_instances mi" - " ON di.merchant_instance_serial = mi.merchant_serial" - " WHERE mi.merchant_id = $1"); + PREPARE_INSTANCE (pg, + stmt, + "select_donau_instances", + "SELECT" + " di.donau_instances_serial" + ",di.donau_url" + ",di.charity_name" + ",mi.merchant_pub AS charity_pub_key" + ",di.charity_id" + ",di.charity_max_per_year" + ",di.charity_receipts_to_date" + ",di.current_year" + ",dk.keys_json::TEXT" + " FROM merchant_donau_instances di" + " LEFT JOIN merchant_donau_keys dk" + " ON di.donau_url = dk.donau_url" + " JOIN merchant.merchant_instances mi" + " ON mi.merchant_serial = $1"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "select_donau_instances", + stmt, params, &select_donau_instance_cb, &sdc); diff --git a/src/backenddb/select_donau_instances_filtered.c b/src/backenddb/select_donau_instances_filtered.c @@ -67,7 +67,7 @@ select_donau_instance_cb (void *cls, { char *donau_url; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_string ("donau_url", + GNUNET_PQ_result_spec_string ("out_donau_url", &donau_url), GNUNET_PQ_result_spec_end }; @@ -112,9 +112,8 @@ TALER_MERCHANTDB_select_donau_instances_filtered ( PREPARE (pg, "select_donau_instances_filtered", "SELECT" - " donau_url" - " FROM merchant_donau_instances" - " WHERE (charity_max_per_year).curr = $1"); + " out_donau_url" + " FROM merchant.select_donau_instances_filtered($1)"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "select_donau_instances_filtered", diff --git a/src/backenddb/select_login_token.c b/src/backenddb/select_login_token.c @@ -34,7 +34,6 @@ TALER_MERCHANTDB_select_login_token (struct TALER_MERCHANTDB_PostgresContext *pg uint32_t *validity_scope) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (id), GNUNET_PQ_query_param_auto_from_type (token), GNUNET_PQ_query_param_end }; @@ -45,21 +44,22 @@ TALER_MERCHANTDB_select_login_token (struct TALER_MERCHANTDB_PostgresContext *pg validity_scope), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "select_login_token", - "SELECT" - " expiration_time" - ",validity_scope" - " FROM merchant_login_tokens" - " WHERE token=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"); + PREPARE_INSTANCE (pg, + stmt, + "select_login_token", + "SELECT" + " expiration_time" + ",validity_scope" + " FROM merchant_login_tokens" + " WHERE token=$1"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "select_login_token", + stmt, params, rs); } diff --git a/src/backenddb/select_money_pot.c b/src/backenddb/select_money_pot.c @@ -36,7 +36,6 @@ TALER_MERCHANTDB_select_money_pot (struct TALER_MERCHANTDB_PostgresContext *pg, struct TALER_Amount **pot_totals) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&money_pot_id), GNUNET_PQ_query_param_end }; @@ -51,22 +50,24 @@ TALER_MERCHANTDB_select_money_pot (struct TALER_MERCHANTDB_PostgresContext *pg, pot_totals), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "select_money_pot", - "SELECT" - " mp.money_pot_name" - " ,mp.money_pot_description" - " ,mp.pot_totals" - " FROM merchant_money_pots mp" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " WHERE merchant_id=$1" - " AND money_pot_serial=$2;"); + PREPARE_INSTANCE (pg, + stmt, + "select_money_pot", + "SELECT" + " money_pot_name" + " ,money_pot_description" + " ,pot_totals" + " FROM merchant_money_pots" + " WHERE money_pot_serial=$1;"); return GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, - "select_money_pot", + stmt, params, rs); } diff --git a/src/backenddb/select_money_pots.c b/src/backenddb/select_money_pots.c @@ -122,45 +122,45 @@ TALER_MERCHANTDB_select_money_pots (struct TALER_MERCHANTDB_PostgresContext *pg, .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&offset), GNUNET_PQ_query_param_uint64 (&plimit), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt_asc[PG_PREP_INSTANCE_NAME_MAX]; + char stmt_desc[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_money_pots_asc", - "SELECT" - " money_pot_serial" - " ,money_pot_name" - " ,pot_totals" - " FROM merchant_money_pots" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND money_pot_serial > $2" - " ORDER BY money_pot_serial ASC" - " LIMIT $3"); - PREPARE (pg, - "lookup_money_pots_desc", - "SELECT" - " money_pot_serial" - " ,money_pot_name" - " ,pot_totals" - " FROM merchant_money_pots" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND money_pot_serial < $2" - " ORDER BY money_pot_serial DESC" - " LIMIT $3"); + PREPARE_INSTANCE (pg, + stmt_asc, + "lookup_money_pots_asc", + "SELECT" + " money_pot_serial" + " ,money_pot_name" + " ,pot_totals" + " FROM merchant_money_pots" + " WHERE money_pot_serial > $1" + " ORDER BY money_pot_serial ASC" + " LIMIT $2"); + PREPARE_INSTANCE (pg, + stmt_desc, + "lookup_money_pots_desc", + "SELECT" + " money_pot_serial" + " ,money_pot_name" + " ,pot_totals" + " FROM merchant_money_pots" + " WHERE money_pot_serial < $1" + " ORDER BY money_pot_serial DESC" + " LIMIT $2"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, (limit > 0) - ? "lookup_money_pots_asc" - : "lookup_money_pots_desc", + ? stmt_asc + : stmt_desc, params, &lookup_money_pots_cb, &plc); diff --git a/src/backenddb/select_open_transfers.c b/src/backenddb/select_open_transfers.c @@ -77,17 +77,17 @@ open_transfers_cb (void *cls, struct TALER_WireTransferIdentifierRawP wtid; struct GNUNET_TIME_Absolute retry_time; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("expected_credit_serial", + GNUNET_PQ_result_spec_uint64 ("out_expected_credit_serial", &rowid), - GNUNET_PQ_result_spec_string ("instance_id", + GNUNET_PQ_result_spec_string ("out_instance_id", &instance_id), - GNUNET_PQ_result_spec_string ("exchange_url", + GNUNET_PQ_result_spec_string ("out_exchange_url", &exchange_url), - GNUNET_PQ_result_spec_string ("payto_uri", + GNUNET_PQ_result_spec_string ("out_payto_uri", &payto_uri.full_payto), - GNUNET_PQ_result_spec_auto_from_type ("wtid", + GNUNET_PQ_result_spec_auto_from_type ("out_wtid", &wtid), - GNUNET_PQ_result_spec_absolute_time ("retry_time", + GNUNET_PQ_result_spec_absolute_time ("out_retry_time", &retry_time), GNUNET_PQ_result_spec_end }; @@ -134,20 +134,13 @@ TALER_MERCHANTDB_select_open_transfers ( PREPARE (pg, "select_open_transfers", "SELECT" - " met.expected_credit_serial" - ",mi.merchant_id AS instance_id" - ",met.exchange_url" - ",ma.payto_uri" - ",met.wtid" - ",met.retry_time" - " FROM merchant_expected_transfers met" - " JOIN merchant_accounts ma" - " USING (account_serial)" - " JOIN merchant_instances mi" - " ON (ma.merchant_serial=mi.merchant_serial)" - " WHERE retry_needed" - " ORDER BY retry_time ASC" - " LIMIT $1;"); + " out_expected_credit_serial" + " ,out_instance_id" + " ,out_exchange_url" + " ,out_payto_uri" + " ,out_wtid" + " ,out_retry_time" + " FROM merchant.select_open_transfers($1)"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, "select_open_transfers", diff --git a/src/backenddb/select_order_blinded_sigs.c b/src/backenddb/select_order_blinded_sigs.c @@ -106,19 +106,23 @@ TALER_MERCHANTDB_select_order_blinded_sigs (struct TALER_MERCHANTDB_PostgresCont GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "select_blinded_sigs", - "SELECT" - " motbs.token_blinded_signature" - " ,motbs.token_hash" - " FROM merchant_order_token_blinded_sigs AS motbs" - " JOIN merchant_contract_terms AS mct USING (order_serial)" - " WHERE mct.order_id = $1" - " ORDER BY motbs.token_index ASC"); + PREPARE_INSTANCE (pg, + stmt, + "select_blinded_sigs", + "SELECT" + " motbs.token_blinded_signature" + " ,motbs.token_hash" + " FROM merchant_order_token_blinded_sigs AS motbs" + " JOIN merchant_contract_terms AS mct USING (order_serial)" + " WHERE mct.order_id = $1" + " ORDER BY motbs.token_index ASC"); return GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "select_blinded_sigs", + stmt, params, &restore_sig_cb, &ctx); diff --git a/src/backenddb/select_otp.c b/src/backenddb/select_otp.c @@ -33,32 +33,33 @@ TALER_MERCHANTDB_select_otp (struct TALER_MERCHANTDB_PostgresContext *pg, struct TALER_MERCHANTDB_OtpDeviceDetails *td) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (otp_id), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; - PREPARE (pg, - "select_otp", - "SELECT" - " otp_description" - ",otp_ctr" - ",otp_key" - ",otp_algorithm" - " FROM merchant_otp_devices" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND merchant_otp_devices.otp_id=$2"); + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); + check_connection (pg); + PREPARE_INSTANCE (pg, + stmt, + "select_otp", + "SELECT" + " otp_description" + ",otp_ctr" + ",otp_key" + ",otp_algorithm" + " FROM merchant_otp_devices" + " WHERE otp_id=$1"); if (NULL == td) { struct GNUNET_PQ_ResultSpec rs_null[] = { GNUNET_PQ_result_spec_end }; - check_connection (pg); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "select_otp", + stmt, params, rs_null); } @@ -78,9 +79,8 @@ TALER_MERCHANTDB_select_otp (struct TALER_MERCHANTDB_PostgresContext *pg, }; enum GNUNET_DB_QueryStatus qs; - check_connection (pg); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "select_otp", + stmt, params, rs); td->otp_algorithm = (enum TALER_MerchantConfirmationAlgorithm) pos32; diff --git a/src/backenddb/select_otp_serial.c b/src/backenddb/select_otp_serial.c @@ -33,7 +33,6 @@ TALER_MERCHANTDB_select_otp_serial (struct TALER_MERCHANTDB_PostgresContext *pg, uint64_t *serial) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (otp_id), GNUNET_PQ_query_param_end }; @@ -42,19 +41,21 @@ TALER_MERCHANTDB_select_otp_serial (struct TALER_MERCHANTDB_PostgresContext *pg, serial), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "select_otp_serial", - "SELECT" - " otp_serial" - " FROM merchant_otp_devices" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND merchant_otp_devices.otp_id=$2"); + PREPARE_INSTANCE (pg, + stmt, + "select_otp_serial", + "SELECT" + " otp_serial" + " FROM merchant_otp_devices" + " WHERE otp_id=$1"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "select_otp_serial", + stmt, params, rs); } diff --git a/src/backenddb/select_product_groups.c b/src/backenddb/select_product_groups.c @@ -112,45 +112,45 @@ TALER_MERCHANTDB_select_product_groups (struct TALER_MERCHANTDB_PostgresContext .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&offset), GNUNET_PQ_query_param_uint64 (&plimit), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt_asc[PG_PREP_INSTANCE_NAME_MAX]; + char stmt_desc[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "lookup_product_groups_asc", - "SELECT" - " product_group_serial" - " ,product_group_name" - " ,product_group_description" - " FROM merchant_product_groups" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND product_group_serial > $2" - " ORDER BY product_group_serial ASC" - " LIMIT $3"); - PREPARE (pg, - "lookup_product_groups_desc", - "SELECT" - " product_group_serial" - " ,product_group_name" - " ,product_group_description" - " FROM merchant_product_groups" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND product_group_serial < $2" - " ORDER BY product_group_serial DESC" - " LIMIT $3"); + PREPARE_INSTANCE (pg, + stmt_asc, + "lookup_product_groups_asc", + "SELECT" + " product_group_serial" + " ,product_group_name" + " ,product_group_description" + " FROM merchant_product_groups" + " WHERE product_group_serial > $1" + " ORDER BY product_group_serial ASC" + " LIMIT $2"); + PREPARE_INSTANCE (pg, + stmt_desc, + "lookup_product_groups_desc", + "SELECT" + " product_group_serial" + " ,product_group_name" + " ,product_group_description" + " FROM merchant_product_groups" + " WHERE product_group_serial < $1" + " ORDER BY product_group_serial DESC" + " LIMIT $2"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, (limit > 0) - ? "lookup_product_groups_asc" - : "lookup_product_groups_desc", + ? stmt_asc + : stmt_desc, params, &lookup_product_groups_cb, &plc); diff --git a/src/backenddb/select_report.c b/src/backenddb/select_report.c @@ -41,7 +41,6 @@ TALER_MERCHANTDB_select_report (struct TALER_MERCHANTDB_PostgresContext *pg, char **last_error_detail) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&report_id), GNUNET_PQ_query_param_end }; @@ -74,31 +73,33 @@ TALER_MERCHANTDB_select_report (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); *last_error_detail = NULL; code = TALER_EC_NONE; check_connection (pg); - PREPARE (pg, - "select_report", - "SELECT" - " mr.report_program_section" - " ,mr.report_description" - " ,mr.mime_type" - " ,mr.data_source" - " ,mr.target_address" - " ,mr.frequency" - " ,mr.frequency_shift" - " ,mr.next_transmission" - " ,mr.last_error_code" - " ,mr.last_error_detail" - " FROM merchant_reports mr" - " JOIN merchant_instances mi" - " USING (merchant_serial)" - " WHERE merchant_id=$1" - " AND report_serial=$2;"); + PREPARE_INSTANCE (pg, + stmt, + "select_report", + "SELECT" + " report_program_section" + " ,report_description" + " ,mime_type" + " ,data_source" + " ,target_address" + " ,frequency" + " ,frequency_shift" + " ,next_transmission" + " ,last_error_code" + " ,last_error_detail" + " FROM merchant_reports" + " WHERE report_serial=$1;"); qs = GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, - "select_report", + stmt, params, rs); *last_error_code = (enum TALER_ErrorCode) code; diff --git a/src/backenddb/select_reports.c b/src/backenddb/select_reports.c @@ -113,47 +113,47 @@ TALER_MERCHANTDB_select_reports ( .extract_failed = false, }; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&offset), GNUNET_PQ_query_param_uint64 (&plimit), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt_asc[PG_PREP_INSTANCE_NAME_MAX]; + char stmt_desc[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "select_reports_asc", - "SELECT" - " report_serial" - " ,report_description" - " ,frequency" - " FROM merchant_reports" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND report_serial > $2" - " AND NOT one_shot_hidden" - " ORDER BY report_serial ASC" - " LIMIT $3"); - PREPARE (pg, - "select_reports_desc", - "SELECT" - " report_serial" - " ,report_description" - " ,frequency" - " FROM merchant_reports" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" - " AND report_serial < $2" - " AND NOT one_shot_hidden" - " ORDER BY report_serial DESC" - " LIMIT $3"); + PREPARE_INSTANCE (pg, + stmt_asc, + "select_reports_asc", + "SELECT" + " report_serial" + " ,report_description" + " ,frequency" + " FROM merchant_reports" + " WHERE report_serial > $1" + " AND NOT one_shot_hidden" + " ORDER BY report_serial ASC" + " LIMIT $2"); + PREPARE_INSTANCE (pg, + stmt_desc, + "select_reports_desc", + "SELECT" + " report_serial" + " ,report_description" + " ,frequency" + " FROM merchant_reports" + " WHERE report_serial < $1" + " AND NOT one_shot_hidden" + " ORDER BY report_serial DESC" + " LIMIT $2"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, (limit > 0) - ? "select_reports_asc" - : "select_reports_desc", + ? stmt_asc + : stmt_desc, params, &select_reports_cb, &plc); diff --git a/src/backenddb/select_unit.c b/src/backenddb/select_unit.c @@ -34,23 +34,66 @@ TALER_MERCHANTDB_select_unit (struct TALER_MERCHANTDB_PostgresContext *pg, { enum GNUNET_DB_QueryStatus qs; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (unit_id), GNUNET_PQ_query_param_end }; + char stmt_custom[PG_PREP_INSTANCE_NAME_MAX]; + char stmt_builtin[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); + check_connection (pg); + PREPARE_INSTANCE (pg, + stmt_custom, + "select_unit_custom", + "SELECT" + " cu.unit_serial" + " ,cu.unit" + " ,cu.unit_name_long" + " ,cu.unit_name_short" + " ,cu.unit_name_long_i18n" + " ,cu.unit_name_short_i18n" + " ,cu.unit_allow_fraction" + " ,cu.unit_precision_level" + " ,cu.unit_active" + " ,FALSE AS unit_builtin" + " FROM merchant_custom_units cu" + " WHERE cu.unit=$1"); + PREPARE_INSTANCE (pg, + stmt_builtin, + "select_unit_builtin", + "SELECT" + " bu.unit_serial" + " ,bu.unit" + " ,bu.unit_name_long" + " ,bu.unit_name_short" + " ,bu.unit_name_long_i18n" + " ,bu.unit_name_short_i18n" + " ,COALESCE(bo.override_allow_fraction, bu.unit_allow_fraction)" + " ,COALESCE(bo.override_precision_level, bu.unit_precision_level)" + " ,COALESCE(bo.override_active, bu.unit_active)" + " ,TRUE AS unit_builtin" + " FROM merchant_builtin_units bu" + " LEFT JOIN merchant_builtin_unit_overrides bo" + " ON bo.builtin_unit_serial = bu.unit_serial" + " WHERE bu.unit=$1"); if (NULL == ud) { struct GNUNET_PQ_ResultSpec rs_null[] = { GNUNET_PQ_result_spec_end }; - check_connection (pg); - return GNUNET_PQ_eval_prepared_singleton_select ( - pg->conn, - "select_unit", - params, - rs_null); + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + stmt_custom, + params, + rs_null); + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs) + return qs; + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + stmt_builtin, + params, + rs_null); } else { @@ -78,56 +121,14 @@ TALER_MERCHANTDB_select_unit (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_result_spec_end }; - check_connection (pg); - - PREPARE (pg, - "select_unit_custom", - "SELECT" - " cu.unit_serial" - " ,cu.unit" - " ,cu.unit_name_long" - " ,cu.unit_name_short" - " ,cu.unit_name_long_i18n" - " ,cu.unit_name_short_i18n" - " ,cu.unit_allow_fraction" - " ,cu.unit_precision_level" - " ,cu.unit_active" - " ,FALSE AS unit_builtin" - " FROM merchant_custom_units cu" - " JOIN merchant_instances inst" - " USING (merchant_serial)" - " WHERE inst.merchant_id=$1" - " AND cu.unit=$2"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "select_unit_custom", + stmt_custom, params, rs); if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs) return qs; - - PREPARE (pg, - "select_unit_builtin", - "SELECT" - " bu.unit_serial" - " ,bu.unit" - " ,bu.unit_name_long" - " ,bu.unit_name_short" - " ,bu.unit_name_long_i18n" - " ,bu.unit_name_short_i18n" - " ,COALESCE(bo.override_allow_fraction, bu.unit_allow_fraction)" - " ,COALESCE(bo.override_precision_level, bu.unit_precision_level)" - " ,COALESCE(bo.override_active, bu.unit_active)" - " ,TRUE AS unit_builtin" - " FROM merchant_builtin_units bu" - " JOIN merchant_instances inst" - " ON TRUE" - " LEFT JOIN merchant_builtin_unit_overrides bo" - " ON bo.builtin_unit_serial = bu.unit_serial" - " AND bo.merchant_serial = inst.merchant_serial" - " WHERE inst.merchant_id=$1" - " AND bu.unit=$2"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "select_unit_builtin", + stmt_builtin, params, rs); } diff --git a/src/backenddb/select_wirewatch_accounts.c b/src/backenddb/select_wirewatch_accounts.c @@ -71,17 +71,17 @@ handle_results (void *cls, json_t *credential; uint64_t last_serial; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_string ("merchant_id", + GNUNET_PQ_result_spec_string ("out_merchant_id", &instance), - GNUNET_PQ_result_spec_string ("payto_uri", + GNUNET_PQ_result_spec_string ("out_payto_uri", &payto_uri.full_payto), - GNUNET_PQ_result_spec_string ("credit_facade_url", + GNUNET_PQ_result_spec_string ("out_credit_facade_url", &facade_url), GNUNET_PQ_result_spec_allow_null ( - TALER_PQ_result_spec_json ("credit_facade_credentials", + TALER_PQ_result_spec_json ("out_credit_facade_credentials", &credential), NULL), - GNUNET_PQ_result_spec_uint64 ("last_bank_serial", + GNUNET_PQ_result_spec_uint64 ("out_last_bank_serial", &last_serial), GNUNET_PQ_result_spec_end }; @@ -124,16 +124,12 @@ TALER_MERCHANTDB_select_wirewatch_accounts ( PREPARE (pg, "select_wirewatch_progress", "SELECT" - " last_bank_serial" - ",merchant_id" - ",payto_uri" - ",credit_facade_url" - ",credit_facade_credentials::TEXT" - " FROM merchant_accounts" - " JOIN merchant_instances" - " USING (merchant_serial)" - " WHERE active" - " AND credit_facade_url IS NOT NULL"); + " out_last_bank_serial" + " ,out_merchant_id" + " ,out_payto_uri" + " ,out_credit_facade_url" + " ,out_credit_facade_credentials::TEXT" + " FROM merchant.select_wirewatch_accounts()"); check_connection (pg); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "select_wirewatch_progress", diff --git a/src/backenddb/set_instance.c b/src/backenddb/set_instance.c @@ -0,0 +1,82 @@ +/* + This file is part of TALER + (C) 2026 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser 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 src/backenddb/set_instance.c + * @brief Implementation of TALER_MERCHANTDB_set_instance + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_pq_lib.h> +#include <taler/taler_pq_lib.h> +#include "merchant-database/set_instance.h" +#include "helper.h" + + +enum GNUNET_DB_QueryStatus +TALER_MERCHANTDB_set_instance ( + struct TALER_MERCHANTDB_PostgresContext *pg, + const char *instance_id) +{ + uint64_t serial; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("merchant_serial", + &serial), + GNUNET_PQ_result_spec_end + }; + enum GNUNET_DB_QueryStatus qs; + char sp_sql[128]; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute (sp_sql), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + + if ( (NULL != pg->current_merchant_id) && + (0 == strcmp (pg->current_merchant_id, + instance_id)) ) + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; + check_connection (pg); + PREPARE (pg, + "set_instance_lookup_serial", + "SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1"); + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "set_instance_lookup_serial", + params, + rs); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + return qs; + GNUNET_snprintf (sp_sql, + sizeof (sp_sql), + "SET search_path TO merchant_instance_%llu, merchant", + (unsigned long long) serial); + if (GNUNET_OK != + GNUNET_PQ_exec_statements (pg->conn, + es)) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; + } + GNUNET_free (pg->current_merchant_id); + pg->current_merchant_id = GNUNET_strdup (instance_id); + pg->current_merchant_serial = serial; + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; +} diff --git a/src/backenddb/solve_mfa_challenge.c b/src/backenddb/solve_mfa_challenge.c @@ -55,20 +55,23 @@ TALER_MERCHANTDB_solve_mfa_challenge ( GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); /* conservatively set security-relevant return values to safe values, even though these should not be used with qs <= 0 */ *solved = false; *retry_counter = 0; - PREPARE (pg, - "solve_mfa_challenge", - "SELECT" - " out_solved" - " ,out_retry_counter" - " FROM merchant_do_solve_mfa_challenge" - " ($1, $2, $3, $4);"); + PREPARE_INSTANCE (pg, + stmt, + "solve_mfa_challenge", + "SELECT" + " out_solved" + " ,out_retry_counter" + " FROM merchant_do_solve_mfa_challenge" + " ($1, $2, $3, $4);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "solve_mfa_challenge", + stmt, params, rs); if (qs <= 0) diff --git a/src/backenddb/sql-schema/gen-procedures.sh b/src/backenddb/sql-schema/gen-procedures.sh @@ -46,6 +46,7 @@ SET search_path TO merchant; EOF + # Output procedures, stripping comments for x in $@; do @@ -62,21 +63,48 @@ DROP PROCEDURE IF EXISTS merchant_do_gc; CREATE PROCEDURE merchant_do_gc(in_now INT8) LANGUAGE plpgsql AS $$ +DECLARE + rec RECORD; + s TEXT; BEGIN - DELETE FROM merchant_instances + -- Drop validation-pending instances that never confirmed in time. The + -- AFTER DELETE trigger on merchant.merchant_instances will DROP the + -- per-instance schema for each removed row. + DELETE FROM merchant.merchant_instances WHERE validation_needed AND validation_expiration < in_now; - CALL merchant_statistic_amount_gc (); - CALL merchant_statistic_bucket_gc (); - CALL merchant_statistic_counter_gc (); - - DELETE FROM tan_challenges - WHERE expiration_date < in_now; - DELETE FROM merchant_unclaim_signatures - WHERE expiration_time < in_now; + + -- Per-instance GC: loop over all surviving instances and run the + -- per-instance GC helpers + targeted DELETEs in each schema. + FOR rec IN SELECT merchant_serial FROM merchant.merchant_instances + LOOP + s := 'merchant_instance_' || rec.merchant_serial::TEXT; + BEGIN + EXECUTE format('SET LOCAL search_path TO %I, merchant', s); + CALL merchant_statistic_amount_gc (); + CALL merchant_statistic_bucket_gc (); + CALL merchant_statistic_counter_gc (); + EXECUTE format('DELETE FROM %I.tan_challenges' + ' WHERE expiration_date < $1', s) USING in_now; + EXECUTE format('DELETE FROM %I.merchant_unclaim_signatures' + ' WHERE expiration_time < $1', s) USING in_now; + EXCEPTION + WHEN undefined_table THEN + NULL; + WHEN undefined_function THEN + NULL; + END; + END LOOP; + -- Restore the global search_path so subsequent statements on this + -- session don't leak into the last visited instance schema. + SET LOCAL search_path TO merchant; END $$; COMMENT ON PROCEDURE merchant_do_gc - IS 'calls all other garbage collection subroutines'; + IS 'Calls per-instance garbage collection subroutines across every instance.' + ' Removes expired pending-validation instances first (whose ON DELETE' + ' trigger drops the entire per-instance schema), then for each surviving' + ' instance runs merchant_statistic_*_gc and DELETEs expired tan_challenges' + ' / merchant_unclaim_signatures.'; COMMIT; EOF diff --git a/src/backenddb/sql-schema/merchant-0036-copy.sql.fragment b/src/backenddb/sql-schema/merchant-0036-copy.sql.fragment @@ -0,0 +1,504 @@ + -- ================================================================= + -- Copy per-instance row data from merchant.* into the new schema. + -- Tables are listed in FK-dependency order so plain INSERT works + -- (no DEFERRABLE games, no WITH ORDINALITY). $1 is bound to + -- rec.merchant_serial via USING. The merchant_serial column is + -- dropped from every column list — the schema name is the + -- discriminator. All target tables use GENERATED BY DEFAULT AS + -- IDENTITY (so we can keep the source serial values), except + -- merchant_login_tokens whose `serial` is GENERATED ALWAYS — that + -- one needs OVERRIDING SYSTEM VALUE. + -- + -- Two stat-meta tables (merchant_statistic_bucket_meta, + -- merchant_statistic_interval_meta) have no merchant_serial in the + -- source schema — they are global slug catalogs and are copied + -- verbatim into every per-instance schema. + -- ================================================================= + + -- ---------------- direct merchant_serial (no JOIN) -------------- + + EXECUTE format('INSERT INTO %I.merchant_accounts' + || ' (account_serial, h_wire, salt, credit_facade_url,' + || ' credit_facade_credentials, last_bank_serial, payto_uri,' + || ' active, extra_wire_subject_metadata)' + || ' SELECT account_serial, h_wire, salt, credit_facade_url,' + || ' credit_facade_credentials, last_bank_serial, payto_uri,' + || ' active, extra_wire_subject_metadata' + || ' FROM merchant.merchant_accounts' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_categories' + || ' (category_serial, category_name, category_name_i18n)' + || ' SELECT category_serial, category_name, category_name_i18n' + || ' FROM merchant.merchant_categories' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_contract_terms' + || ' (order_serial, order_id, contract_terms, wallet_data,' + || ' h_contract_terms, creation_time, pay_deadline, refund_deadline,' + || ' paid, wired, fulfillment_url, session_id, pos_key, pos_algorithm,' + || ' claim_token, choice_index)' + || ' SELECT order_serial, order_id, contract_terms, wallet_data,' + || ' h_contract_terms, creation_time, pay_deadline, refund_deadline,' + || ' paid, wired, fulfillment_url, session_id, pos_key, pos_algorithm,' + || ' claim_token, choice_index' + || ' FROM merchant.merchant_contract_terms' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_custom_units' + || ' (unit_serial, unit, unit_name_long, unit_name_short,' + || ' unit_name_long_i18n, unit_name_short_i18n,' + || ' unit_allow_fraction, unit_precision_level, unit_active)' + || ' SELECT unit_serial, unit, unit_name_long, unit_name_short,' + || ' unit_name_long_i18n, unit_name_short_i18n,' + || ' unit_allow_fraction, unit_precision_level, unit_active' + || ' FROM merchant.merchant_custom_units' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + -- merchant_donau_instances: source filter column is named + -- merchant_instance_serial (not merchant_serial). + EXECUTE format('INSERT INTO %I.merchant_donau_instances' + || ' (donau_instances_serial, donau_url, charity_name, charity_id,' + || ' charity_max_per_year, charity_receipts_to_date, current_year)' + || ' SELECT donau_instances_serial, donau_url, charity_name, charity_id,' + || ' charity_max_per_year, charity_receipts_to_date, current_year' + || ' FROM merchant.merchant_donau_instances' + || ' WHERE merchant_instance_serial = $1', s) + USING rec.merchant_serial; + + -- merchant_login_tokens: target `serial` column is GENERATED ALWAYS + -- → must use OVERRIDING SYSTEM VALUE to preserve serial values. + EXECUTE format('INSERT INTO %I.merchant_login_tokens' + || ' (token, creation_time, expiration_time, validity_scope,' + || ' description, serial)' + || ' OVERRIDING SYSTEM VALUE' + || ' SELECT token, creation_time, expiration_time, validity_scope,' + || ' description, serial' + || ' FROM merchant.merchant_login_tokens' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_money_pots' + || ' (money_pot_serial, money_pot_name, money_pot_description, pot_totals)' + || ' SELECT money_pot_serial, money_pot_name, money_pot_description, pot_totals' + || ' FROM merchant.merchant_money_pots' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_otp_devices' + || ' (otp_serial, otp_id, otp_description, otp_key, otp_algorithm, otp_ctr)' + || ' SELECT otp_serial, otp_id, otp_description, otp_key, otp_algorithm, otp_ctr' + || ' FROM merchant.merchant_otp_devices' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_orders' + || ' (order_serial, order_id, claim_token, h_post_data, pay_deadline,' + || ' creation_time, contract_terms, pos_key, pos_algorithm,' + || ' fulfillment_url, session_id)' + || ' SELECT order_serial, order_id, claim_token, h_post_data, pay_deadline,' + || ' creation_time, contract_terms, pos_key, pos_algorithm,' + || ' fulfillment_url, session_id' + || ' FROM merchant.merchant_orders' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_product_groups' + || ' (product_group_serial, product_group_name, product_group_description)' + || ' SELECT product_group_serial, product_group_name, product_group_description' + || ' FROM merchant.merchant_product_groups' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + -- merchant_inventory references merchant_product_groups and merchant_money_pots + -- (already copied above). + EXECUTE format('INSERT INTO %I.merchant_inventory' + || ' (product_serial, product_id, description, description_i18n,' + || ' unit, image, taxes, total_stock, total_sold, total_lost,' + || ' address, next_restock, minimum_age, product_name, image_hash,' + || ' price_array, total_stock_frac, total_sold_frac, total_lost_frac,' + || ' allow_fractional_quantity, fractional_precision_level,' + || ' product_group_serial, money_pot_serial, price_is_net)' + || ' SELECT product_serial, product_id, description, description_i18n,' + || ' unit, image, taxes, total_stock, total_sold, total_lost,' + || ' address, next_restock, minimum_age, product_name, image_hash,' + || ' price_array, total_stock_frac, total_sold_frac, total_lost_frac,' + || ' allow_fractional_quantity, fractional_precision_level,' + || ' product_group_serial, money_pot_serial, price_is_net' + || ' FROM merchant.merchant_inventory' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_reports' + || ' (report_serial, report_program_section, report_description,' + || ' mime_type, report_token, data_source, target_address,' + || ' frequency, frequency_shift, next_transmission,' + || ' last_error_code, last_error_detail, one_shot_hidden)' + || ' SELECT report_serial, report_program_section, report_description,' + || ' mime_type, report_token, data_source, target_address,' + || ' frequency, frequency_shift, next_transmission,' + || ' last_error_code, last_error_detail, one_shot_hidden' + || ' FROM merchant.merchant_reports' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + -- merchant_template references merchant_otp_devices (already copied). + EXECUTE format('INSERT INTO %I.merchant_template' + || ' (template_serial, template_id, template_description,' + || ' otp_device_id, template_contract, editable_defaults)' + || ' SELECT template_serial, template_id, template_description,' + || ' otp_device_id, template_contract, editable_defaults' + || ' FROM merchant.merchant_template' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_token_families' + || ' (token_family_serial, slug, name, description, description_i18n,' + || ' valid_after, valid_before, duration, kind, issued, used,' + || ' validity_granularity, start_offset, cipher_choice, extra_data)' + || ' SELECT token_family_serial, slug, name, description, description_i18n,' + || ' valid_after, valid_before, duration, kind, issued, used,' + || ' validity_granularity, start_offset, cipher_choice, extra_data' + || ' FROM merchant.merchant_token_families' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_webhook' + || ' (webhook_serial, webhook_id, event_type, url, http_method,' + || ' header_template, body_template)' + || ' SELECT webhook_serial, webhook_id, event_type, url, http_method,' + || ' header_template, body_template' + || ' FROM merchant.merchant_webhook' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + -- merchant_pending_webhooks references merchant_webhook (above) via webhook_serial. + EXECUTE format('INSERT INTO %I.merchant_pending_webhooks' + || ' (webhook_pending_serial, webhook_serial, next_attempt, retries,' + || ' url, http_method, header, body)' + || ' SELECT webhook_pending_serial, webhook_serial, next_attempt, retries,' + || ' url, http_method, header, body' + || ' FROM merchant.merchant_pending_webhooks' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.tan_challenges' + || ' (challenge_id, h_body, salt, op, code, creation_date,' + || ' expiration_date, retransmission_date, confirmation_date,' + || ' retry_counter, tan_channel, required_address)' + || ' SELECT challenge_id, h_body, salt, op, code, creation_date,' + || ' expiration_date, retransmission_date, confirmation_date,' + || ' retry_counter, tan_channel, required_address' + || ' FROM merchant.tan_challenges' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_keys' + || ' (merchant_priv)' + || ' SELECT merchant_priv' + || ' FROM merchant.merchant_keys' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_builtin_unit_overrides' + || ' (builtin_unit_serial, override_allow_fraction,' + || ' override_precision_level, override_active)' + || ' SELECT builtin_unit_serial, override_allow_fraction,' + || ' override_precision_level, override_active' + || ' FROM merchant.merchant_builtin_unit_overrides' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + -- ---------------- statistics: meta tables are GLOBAL ------------ + -- Copied unfiltered (every instance gets a full slug catalog). + + EXECUTE format('INSERT INTO %I.merchant_statistic_bucket_meta' + || ' (bmeta_serial_id, slug, description, stype, ranges, ages)' + || ' SELECT bmeta_serial_id, slug, description, stype, ranges, ages' + || ' FROM merchant.merchant_statistic_bucket_meta', s); + + EXECUTE format('INSERT INTO %I.merchant_statistic_interval_meta' + || ' (imeta_serial_id, slug, description, stype, ranges, precisions)' + || ' SELECT imeta_serial_id, slug, description, stype, ranges, precisions' + || ' FROM merchant.merchant_statistic_interval_meta', s); + + -- ---------------- statistics: per-instance event/bucket tables -- + + EXECUTE format('INSERT INTO %I.merchant_statistic_amount_event' + || ' (aevent_serial_id, imeta_serial_id, slot,' + || ' delta_curr, delta_value, delta_frac)' + || ' SELECT aevent_serial_id, imeta_serial_id, slot,' + || ' delta_curr, delta_value, delta_frac' + || ' FROM merchant.merchant_statistic_amount_event' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_statistic_counter_event' + || ' (nevent_serial_id, imeta_serial_id, slot, delta)' + || ' SELECT nevent_serial_id, imeta_serial_id, slot, delta' + || ' FROM merchant.merchant_statistic_counter_event' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_statistic_bucket_amount' + || ' (bmeta_serial_id, bucket_start, bucket_range, curr,' + || ' cumulative_value, cumulative_frac)' + || ' SELECT bmeta_serial_id, bucket_start, bucket_range, curr,' + || ' cumulative_value, cumulative_frac' + || ' FROM merchant.merchant_statistic_bucket_amount' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_statistic_bucket_counter' + || ' (bmeta_serial_id, bucket_start, bucket_range, cumulative_number)' + || ' SELECT bmeta_serial_id, bucket_start, bucket_range, cumulative_number' + || ' FROM merchant.merchant_statistic_bucket_counter' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_statistic_interval_amount' + || ' (imeta_serial_id, event_delimiter, range, curr,' + || ' cumulative_value, cumulative_frac)' + || ' SELECT imeta_serial_id, event_delimiter, range, curr,' + || ' cumulative_value, cumulative_frac' + || ' FROM merchant.merchant_statistic_interval_amount' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_statistic_interval_counter' + || ' (imeta_serial_id, range, event_delimiter, cumulative_number)' + || ' SELECT imeta_serial_id, range, event_delimiter, cumulative_number' + || ' FROM merchant.merchant_statistic_interval_counter' + || ' WHERE merchant_serial = $1', s) + USING rec.merchant_serial; + + -- ---------------- account-linked (JOIN via account_serial) ------ + + EXECUTE format('INSERT INTO %I.merchant_kyc' + || ' (kyc_serial_id, kyc_timestamp, kyc_ok, account_serial,' + || ' exchange_url, access_token, exchange_http_status,' + || ' exchange_ec_code, aml_review, jaccount_limits,' + || ' last_rule_gen, next_kyc_poll, kyc_backoff)' + || ' SELECT k.kyc_serial_id, k.kyc_timestamp, k.kyc_ok, k.account_serial,' + || ' k.exchange_url, k.access_token, k.exchange_http_status,' + || ' k.exchange_ec_code, k.aml_review, k.jaccount_limits,' + || ' k.last_rule_gen, k.next_kyc_poll, k.kyc_backoff' + || ' FROM merchant.merchant_kyc k' + || ' JOIN merchant.merchant_accounts a' + || ' ON k.account_serial = a.account_serial' + || ' WHERE a.merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_deposit_confirmations' + || ' (deposit_confirmation_serial, order_serial, deposit_timestamp,' + || ' exchange_url, total_without_fee, wire_fee, signkey_serial,' + || ' exchange_sig, account_serial, wire_transfer_deadline,' + || ' wire_pending, exchange_failure, retry_backoff)' + || ' SELECT dc.deposit_confirmation_serial, dc.order_serial, dc.deposit_timestamp,' + || ' dc.exchange_url, dc.total_without_fee, dc.wire_fee, dc.signkey_serial,' + || ' dc.exchange_sig, dc.account_serial, dc.wire_transfer_deadline,' + || ' dc.wire_pending, dc.exchange_failure, dc.retry_backoff' + || ' FROM merchant.merchant_deposit_confirmations dc' + || ' JOIN merchant.merchant_accounts a' + || ' ON dc.account_serial = a.account_serial' + || ' WHERE a.merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_expected_transfers' + || ' (expected_credit_serial, exchange_url, wtid, expected_credit_amount,' + || ' wire_fee, account_serial, expected_time, retry_time, last_http_status,' + || ' last_ec, last_detail, retry_needed, signkey_serial, exchange_sig,' + || ' h_details, confirmed)' + || ' SELECT et.expected_credit_serial, et.exchange_url, et.wtid, et.expected_credit_amount,' + || ' et.wire_fee, et.account_serial, et.expected_time, et.retry_time, et.last_http_status,' + || ' et.last_ec, et.last_detail, et.retry_needed, et.signkey_serial, et.exchange_sig,' + || ' et.h_details, et.confirmed' + || ' FROM merchant.merchant_expected_transfers et' + || ' JOIN merchant.merchant_accounts a' + || ' ON et.account_serial = a.account_serial' + || ' WHERE a.merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_transfers' + || ' (credit_serial, exchange_url, wtid, credit_amount, account_serial,' + || ' bank_serial_id, expected, execution_time)' + || ' SELECT t.credit_serial, t.exchange_url, t.wtid, t.credit_amount, t.account_serial,' + || ' t.bank_serial_id, t.expected, t.execution_time' + || ' FROM merchant.merchant_transfers t' + || ' JOIN merchant.merchant_accounts a' + || ' ON t.account_serial = a.account_serial' + || ' WHERE a.merchant_serial = $1', s) + USING rec.merchant_serial; + + -- ---------------- deposits: deeper join (dc → accounts) --------- + + EXECUTE format('INSERT INTO %I.merchant_deposits' + || ' (deposit_serial, coin_offset, deposit_confirmation_serial,' + || ' coin_pub, coin_sig, amount_with_fee, deposit_fee, refund_fee,' + || ' settlement_retry_needed, settlement_retry_time,' + || ' settlement_last_http_status, settlement_last_ec,' + || ' settlement_last_detail, settlement_wtid,' + || ' settlement_coin_contribution, settlement_expected_credit_serial,' + || ' signkey_serial, settlement_exchange_sig)' + || ' SELECT d.deposit_serial, d.coin_offset, d.deposit_confirmation_serial,' + || ' d.coin_pub, d.coin_sig, d.amount_with_fee, d.deposit_fee, d.refund_fee,' + || ' d.settlement_retry_needed, d.settlement_retry_time,' + || ' d.settlement_last_http_status, d.settlement_last_ec,' + || ' d.settlement_last_detail, d.settlement_wtid,' + || ' d.settlement_coin_contribution, d.settlement_expected_credit_serial,' + || ' d.signkey_serial, d.settlement_exchange_sig' + || ' FROM merchant.merchant_deposits d' + || ' JOIN merchant.merchant_deposit_confirmations dc' + || ' ON d.deposit_confirmation_serial = dc.deposit_confirmation_serial' + || ' JOIN merchant.merchant_accounts a' + || ' ON dc.account_serial = a.account_serial' + || ' WHERE a.merchant_serial = $1', s) + USING rec.merchant_serial; + + -- expected_transfer_to_coin: deposit_serial → deposits → dc → accounts. + EXECUTE format('INSERT INTO %I.merchant_expected_transfer_to_coin' + || ' (deposit_serial, expected_credit_serial, offset_in_exchange_list,' + || ' exchange_deposit_value, exchange_deposit_fee)' + || ' SELECT ettc.deposit_serial, ettc.expected_credit_serial, ettc.offset_in_exchange_list,' + || ' ettc.exchange_deposit_value, ettc.exchange_deposit_fee' + || ' FROM merchant.merchant_expected_transfer_to_coin ettc' + || ' JOIN merchant.merchant_deposits d' + || ' ON ettc.deposit_serial = d.deposit_serial' + || ' JOIN merchant.merchant_deposit_confirmations dc' + || ' ON d.deposit_confirmation_serial = dc.deposit_confirmation_serial' + || ' JOIN merchant.merchant_accounts a' + || ' ON dc.account_serial = a.account_serial' + || ' WHERE a.merchant_serial = $1', s) + USING rec.merchant_serial; + + -- transfer_signatures: expected_credit_serial → expected_transfers → accounts. + EXECUTE format('INSERT INTO %I.merchant_transfer_signatures' + || ' (expected_credit_serial, signkey_serial, wire_fee, credit_amount,' + || ' execution_time, exchange_sig)' + || ' SELECT ts.expected_credit_serial, ts.signkey_serial, ts.wire_fee, ts.credit_amount,' + || ' ts.execution_time, ts.exchange_sig' + || ' FROM merchant.merchant_transfer_signatures ts' + || ' JOIN merchant.merchant_expected_transfers et' + || ' ON ts.expected_credit_serial = et.expected_credit_serial' + || ' JOIN merchant.merchant_accounts a' + || ' ON et.account_serial = a.account_serial' + || ' WHERE a.merchant_serial = $1', s) + USING rec.merchant_serial; + + -- ---------------- order/contract-linked (JOIN via order_serial) --- + + EXECUTE format('INSERT INTO %I.merchant_refunds' + || ' (refund_serial, order_serial, rtransaction_id, refund_timestamp,' + || ' coin_pub, reason, refund_amount)' + || ' SELECT r.refund_serial, r.order_serial, r.rtransaction_id, r.refund_timestamp,' + || ' r.coin_pub, r.reason, r.refund_amount' + || ' FROM merchant.merchant_refunds r' + || ' JOIN merchant.merchant_contract_terms ct' + || ' ON r.order_serial = ct.order_serial' + || ' WHERE ct.merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_refund_proofs' + || ' (refund_serial, exchange_sig, signkey_serial)' + || ' SELECT rp.refund_serial, rp.exchange_sig, rp.signkey_serial' + || ' FROM merchant.merchant_refund_proofs rp' + || ' JOIN merchant.merchant_refunds r' + || ' ON rp.refund_serial = r.refund_serial' + || ' JOIN merchant.merchant_contract_terms ct' + || ' ON r.order_serial = ct.order_serial' + || ' WHERE ct.merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_order_token_blinded_sigs' + || ' (order_token_bs_serial, order_serial, token_index,' + || ' token_blinded_signature, token_hash)' + || ' SELECT otbs.order_token_bs_serial, otbs.order_serial, otbs.token_index,' + || ' otbs.token_blinded_signature, otbs.token_hash' + || ' FROM merchant.merchant_order_token_blinded_sigs otbs' + || ' JOIN merchant.merchant_contract_terms ct' + || ' ON otbs.order_serial = ct.order_serial' + || ' WHERE ct.merchant_serial = $1', s) + USING rec.merchant_serial; + + -- unclaim_signatures: linked via h_contract_terms. + EXECUTE format('INSERT INTO %I.merchant_unclaim_signatures' + || ' (unclaim_serial, h_contract_terms, unclaim_sig, expiration_time)' + || ' SELECT us.unclaim_serial, us.h_contract_terms, us.unclaim_sig, us.expiration_time' + || ' FROM merchant.merchant_unclaim_signatures us' + || ' JOIN merchant.merchant_contract_terms ct' + || ' ON us.h_contract_terms = ct.h_contract_terms' + || ' WHERE ct.merchant_serial = $1', s) + USING rec.merchant_serial; + + -- order_locks: order_serial → contract_terms. + EXECUTE format('INSERT INTO %I.merchant_order_locks' + || ' (product_serial, total_locked, order_serial, total_locked_frac)' + || ' SELECT ol.product_serial, ol.total_locked, ol.order_serial, ol.total_locked_frac' + || ' FROM merchant.merchant_order_locks ol' + || ' JOIN merchant.merchant_contract_terms ct' + || ' ON ol.order_serial = ct.order_serial' + || ' WHERE ct.merchant_serial = $1', s) + USING rec.merchant_serial; + + -- inventory_locks: product_serial → inventory. + EXECUTE format('INSERT INTO %I.merchant_inventory_locks' + || ' (product_serial, lock_uuid, total_locked, expiration, total_locked_frac)' + || ' SELECT il.product_serial, il.lock_uuid, il.total_locked, il.expiration, il.total_locked_frac' + || ' FROM merchant.merchant_inventory_locks il' + || ' JOIN merchant.merchant_inventory i' + || ' ON il.product_serial = i.product_serial' + || ' WHERE i.merchant_serial = $1', s) + USING rec.merchant_serial; + + -- product_categories: junction; pivot on product_serial → inventory. + EXECUTE format('INSERT INTO %I.merchant_product_categories' + || ' (category_serial, product_serial)' + || ' SELECT pc.category_serial, pc.product_serial' + || ' FROM merchant.merchant_product_categories pc' + || ' JOIN merchant.merchant_inventory i' + || ' ON pc.product_serial = i.product_serial' + || ' WHERE i.merchant_serial = $1', s) + USING rec.merchant_serial; + + -- ---------------- token chain (token_families → keys → tokens) -- + + EXECUTE format('INSERT INTO %I.merchant_token_family_keys' + || ' (token_family_key_serial, token_family_serial, pub, h_pub,' + || ' priv, cipher, signature_validity_start, signature_validity_end,' + || ' private_key_deleted_at, private_key_created_at)' + || ' SELECT tfk.token_family_key_serial, tfk.token_family_serial, tfk.pub, tfk.h_pub,' + || ' tfk.priv, tfk.cipher, tfk.signature_validity_start, tfk.signature_validity_end,' + || ' tfk.private_key_deleted_at, tfk.private_key_created_at' + || ' FROM merchant.merchant_token_family_keys tfk' + || ' JOIN merchant.merchant_token_families tf' + || ' ON tfk.token_family_serial = tf.token_family_serial' + || ' WHERE tf.merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_issued_tokens' + || ' (issued_token_serial, h_contract_terms, token_family_key_serial, blind_sig)' + || ' SELECT it.issued_token_serial, it.h_contract_terms, it.token_family_key_serial, it.blind_sig' + || ' FROM merchant.merchant_issued_tokens it' + || ' JOIN merchant.merchant_token_family_keys tfk' + || ' ON it.token_family_key_serial = tfk.token_family_key_serial' + || ' JOIN merchant.merchant_token_families tf' + || ' ON tfk.token_family_serial = tf.token_family_serial' + || ' WHERE tf.merchant_serial = $1', s) + USING rec.merchant_serial; + + EXECUTE format('INSERT INTO %I.merchant_used_tokens' + || ' (spent_token_serial, h_contract_terms, token_family_key_serial,' + || ' token_pub, token_sig, blind_sig)' + || ' SELECT ut.spent_token_serial, ut.h_contract_terms, ut.token_family_key_serial,' + || ' ut.token_pub, ut.token_sig, ut.blind_sig' + || ' FROM merchant.merchant_used_tokens ut' + || ' JOIN merchant.merchant_token_family_keys tfk' + || ' ON ut.token_family_key_serial = tfk.token_family_key_serial' + || ' JOIN merchant.merchant_token_families tf' + || ' ON tfk.token_family_serial = tf.token_family_serial' + || ' WHERE tf.merchant_serial = $1', s) + USING rec.merchant_serial; diff --git a/src/backenddb/sql-schema/merchant-0036-drop.sql.fragment b/src/backenddb/sql-schema/merchant-0036-drop.sql.fragment @@ -0,0 +1,50 @@ +-- Drop the now-empty per-instance tables from the merchant schema. +-- CASCADE removes the (now obsolete) trigger attachments along with them. +-- The per-instance trigger functions in merchant.* (handle_category_changes, +-- merchant_*_statistics_trigger, ...) are kept around — they will be replaced +-- by the procedures.sql reload after the patch finishes. + +DROP TABLE merchant.merchant_unclaim_signatures CASCADE; +DROP TABLE merchant.merchant_used_tokens CASCADE; +DROP TABLE merchant.merchant_issued_tokens CASCADE; +DROP TABLE merchant.merchant_token_family_keys CASCADE; +DROP TABLE merchant.merchant_token_families CASCADE; +DROP TABLE merchant.merchant_pending_webhooks CASCADE; +DROP TABLE merchant.merchant_webhook CASCADE; +DROP TABLE merchant.merchant_transfer_signatures CASCADE; +DROP TABLE merchant.merchant_expected_transfer_to_coin CASCADE; +DROP TABLE merchant.merchant_transfers CASCADE; +DROP TABLE merchant.merchant_expected_transfers CASCADE; +DROP TABLE merchant.merchant_kyc CASCADE; +DROP TABLE merchant.merchant_deposits CASCADE; +DROP TABLE merchant.merchant_deposit_confirmations CASCADE; +DROP TABLE merchant.merchant_refund_proofs CASCADE; +DROP TABLE merchant.merchant_refunds CASCADE; +DROP TABLE merchant.merchant_order_token_blinded_sigs CASCADE; +DROP TABLE merchant.merchant_order_locks CASCADE; +DROP TABLE merchant.merchant_inventory_locks CASCADE; +DROP TABLE merchant.merchant_product_categories CASCADE; +DROP TABLE merchant.merchant_template CASCADE; +DROP TABLE merchant.merchant_otp_devices CASCADE; +DROP TABLE merchant.merchant_inventory CASCADE; +DROP TABLE merchant.merchant_product_groups CASCADE; +DROP TABLE merchant.merchant_money_pots CASCADE; +DROP TABLE merchant.merchant_categories CASCADE; +DROP TABLE merchant.merchant_contract_terms CASCADE; +DROP TABLE merchant.merchant_orders CASCADE; +DROP TABLE merchant.merchant_custom_units CASCADE; +DROP TABLE merchant.merchant_builtin_unit_overrides CASCADE; +DROP TABLE merchant.merchant_login_tokens CASCADE; +DROP TABLE merchant.merchant_donau_instances CASCADE; +DROP TABLE merchant.merchant_reports CASCADE; +DROP TABLE merchant.merchant_keys CASCADE; +DROP TABLE merchant.merchant_accounts CASCADE; +DROP TABLE merchant.tan_challenges CASCADE; +DROP TABLE merchant.merchant_statistic_amount_event CASCADE; +DROP TABLE merchant.merchant_statistic_counter_event CASCADE; +DROP TABLE merchant.merchant_statistic_interval_amount CASCADE; +DROP TABLE merchant.merchant_statistic_interval_counter CASCADE; +DROP TABLE merchant.merchant_statistic_interval_meta CASCADE; +DROP TABLE merchant.merchant_statistic_bucket_amount CASCADE; +DROP TABLE merchant.merchant_statistic_bucket_counter CASCADE; +DROP TABLE merchant.merchant_statistic_bucket_meta CASCADE; diff --git a/src/backenddb/sql-schema/merchant-0036-setval.sql.fragment b/src/backenddb/sql-schema/merchant-0036-setval.sql.fragment @@ -0,0 +1,35 @@ + -- Advance every IDENTITY sequence in the new instance schema past the + -- maximum value just copied so future inserts do not collide. + PERFORM setval(pg_get_serial_sequence(s || '.merchant_accounts', 'account_serial'), COALESCE((SELECT MAX(account_serial) FROM merchant.merchant_accounts WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_categories', 'category_serial'), COALESCE((SELECT MAX(category_serial) FROM merchant.merchant_categories WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_contract_terms', 'order_serial'), COALESCE((SELECT MAX(order_serial) FROM merchant.merchant_contract_terms WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_custom_units', 'unit_serial'), COALESCE((SELECT MAX(unit_serial) FROM merchant.merchant_custom_units WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_donau_instances', 'donau_instances_serial'), COALESCE((SELECT MAX(donau_instances_serial) FROM merchant.merchant_donau_instances WHERE merchant_instance_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_inventory', 'product_serial'), COALESCE((SELECT MAX(product_serial) FROM merchant.merchant_inventory WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_login_tokens', 'serial'), COALESCE((SELECT MAX(serial) FROM merchant.merchant_login_tokens WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_money_pots', 'money_pot_serial'), COALESCE((SELECT MAX(money_pot_serial) FROM merchant.merchant_money_pots WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_orders', 'order_serial'), COALESCE((SELECT MAX(order_serial) FROM merchant.merchant_orders WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_otp_devices', 'otp_serial'), COALESCE((SELECT MAX(otp_serial) FROM merchant.merchant_otp_devices WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_pending_webhooks', 'webhook_pending_serial'), COALESCE((SELECT MAX(webhook_pending_serial) FROM merchant.merchant_pending_webhooks WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_product_groups', 'product_group_serial'), COALESCE((SELECT MAX(product_group_serial) FROM merchant.merchant_product_groups WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_reports', 'report_serial'), COALESCE((SELECT MAX(report_serial) FROM merchant.merchant_reports WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_statistic_amount_event', 'aevent_serial_id'), COALESCE((SELECT MAX(aevent_serial_id) FROM merchant.merchant_statistic_amount_event WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_statistic_bucket_meta', 'bmeta_serial_id'), COALESCE((SELECT MAX(bmeta_serial_id) FROM merchant.merchant_statistic_bucket_meta), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_statistic_counter_event', 'nevent_serial_id'), COALESCE((SELECT MAX(nevent_serial_id) FROM merchant.merchant_statistic_counter_event WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_statistic_interval_meta', 'imeta_serial_id'), COALESCE((SELECT MAX(imeta_serial_id) FROM merchant.merchant_statistic_interval_meta), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_template', 'template_serial'), COALESCE((SELECT MAX(template_serial) FROM merchant.merchant_template WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_token_families', 'token_family_serial'), COALESCE((SELECT MAX(token_family_serial) FROM merchant.merchant_token_families WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_webhook', 'webhook_serial'), COALESCE((SELECT MAX(webhook_serial) FROM merchant.merchant_webhook WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.tan_challenges', 'challenge_id'), COALESCE((SELECT MAX(challenge_id) FROM merchant.tan_challenges WHERE merchant_serial = rec.merchant_serial), 0) + 1, false); + -- Indirect-FK tables (owning instance via JOIN) + PERFORM setval(pg_get_serial_sequence(s || '.merchant_kyc', 'kyc_serial_id'), COALESCE((SELECT MAX(k.kyc_serial_id) FROM merchant.merchant_kyc k JOIN merchant.merchant_accounts a ON k.account_serial = a.account_serial WHERE a.merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_deposit_confirmations', 'deposit_confirmation_serial'), COALESCE((SELECT MAX(dc.deposit_confirmation_serial) FROM merchant.merchant_deposit_confirmations dc JOIN merchant.merchant_accounts a ON dc.account_serial = a.account_serial WHERE a.merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_expected_transfers', 'expected_credit_serial'), COALESCE((SELECT MAX(et.expected_credit_serial) FROM merchant.merchant_expected_transfers et JOIN merchant.merchant_accounts a ON et.account_serial = a.account_serial WHERE a.merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_transfers', 'credit_serial'), COALESCE((SELECT MAX(t.credit_serial) FROM merchant.merchant_transfers t JOIN merchant.merchant_accounts a ON t.account_serial = a.account_serial WHERE a.merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_deposits', 'deposit_serial'), COALESCE((SELECT MAX(d.deposit_serial) FROM merchant.merchant_deposits d JOIN merchant.merchant_deposit_confirmations dc ON d.deposit_confirmation_serial = dc.deposit_confirmation_serial JOIN merchant.merchant_accounts a ON dc.account_serial = a.account_serial WHERE a.merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_refunds', 'refund_serial'), COALESCE((SELECT MAX(r.refund_serial) FROM merchant.merchant_refunds r JOIN merchant.merchant_contract_terms ct ON r.order_serial = ct.order_serial WHERE ct.merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_token_family_keys', 'token_family_key_serial'), COALESCE((SELECT MAX(tfk.token_family_key_serial) FROM merchant.merchant_token_family_keys tfk JOIN merchant.merchant_token_families tf ON tfk.token_family_serial = tf.token_family_serial WHERE tf.merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_issued_tokens', 'issued_token_serial'), COALESCE((SELECT MAX(it.issued_token_serial) FROM merchant.merchant_issued_tokens it JOIN merchant.merchant_token_family_keys tfk ON it.token_family_key_serial = tfk.token_family_key_serial JOIN merchant.merchant_token_families tf ON tfk.token_family_serial = tf.token_family_serial WHERE tf.merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_used_tokens', 'spent_token_serial'), COALESCE((SELECT MAX(ut.spent_token_serial) FROM merchant.merchant_used_tokens ut JOIN merchant.merchant_token_family_keys tfk ON ut.token_family_key_serial = tfk.token_family_key_serial JOIN merchant.merchant_token_families tf ON tfk.token_family_serial = tf.token_family_serial WHERE tf.merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_unclaim_signatures', 'unclaim_serial'), COALESCE((SELECT MAX(us.unclaim_serial) FROM merchant.merchant_unclaim_signatures us JOIN merchant.merchant_contract_terms ct ON us.h_contract_terms = ct.h_contract_terms WHERE ct.merchant_serial = rec.merchant_serial), 0) + 1, false); + PERFORM setval(pg_get_serial_sequence(s || '.merchant_order_token_blinded_sigs','order_token_bs_serial'), COALESCE((SELECT MAX(otbs.order_token_bs_serial) FROM merchant.merchant_order_token_blinded_sigs otbs JOIN merchant.merchant_contract_terms ct ON otbs.order_serial = ct.order_serial WHERE ct.merchant_serial = rec.merchant_serial), 0) + 1, false); diff --git a/src/backenddb/sql-schema/merchant-0036.sql.in b/src/backenddb/sql-schema/merchant-0036.sql.in @@ -0,0 +1,88 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + +-- @file merchant-0036.sql +-- @brief Move per-instance tables into per-instance schemas +-- merchant_instance_<merchant_serial>. Each existing row of +-- merchant_instances is converted by calling +-- merchant.create_instance_schema(serial), copying the rows of every +-- per-instance table into the new schema (dropping the merchant_serial +-- column), and finally dropping the now-empty merchant.<table>. +-- @author Christian Grothoff + +BEGIN; + +SELECT _v.register_patch('merchant-0036', NULL, NULL); + +SET search_path TO merchant; + +-- --------------------------------------------------------------------- +-- Step 1: Install the schema constructor. +-- procedures.sql will re-install the same definition (DROP IF EXISTS), +-- so installing it here is harmless and lets the migration call it. +-- --------------------------------------------------------------------- +#include "../pg_create_instance_schema.sql" + +-- --------------------------------------------------------------------- +-- Step 2: Build a per-instance schema for every existing instance. +-- --------------------------------------------------------------------- +DO $MIG$ +DECLARE + rec RECORD; +BEGIN + FOR rec IN SELECT merchant_serial FROM merchant.merchant_instances LOOP + PERFORM merchant.create_instance_schema(rec.merchant_serial); + END LOOP; +END $MIG$; + +-- --------------------------------------------------------------------- +-- Step 3: Migrate row data per instance. +-- Tables are copied in FK-dependency order. Every copy drops the +-- merchant_serial column. After each table is copied, its IDENTITY +-- sequence (if any) is advanced past MAX(serial). +-- Indirect-FK tables (merchant_kyc, merchant_deposits, ...) are copied +-- by JOINing back to the table that owns the merchant_serial. +-- --------------------------------------------------------------------- +-- Suppress USER triggers during the bulk copy: per-instance triggers +-- (e.g. merchant_kyc_insert_trigger) would otherwise fire on every +-- inserted row and CALL into per-instance procedures that may rely on +-- runtime invariants we do not have during migration. This setting is +-- transaction-local and does not affect normal callers. +SET session_replication_role = replica; + +DO $MIG$ +DECLARE + rec RECORD; + s TEXT; +BEGIN + FOR rec IN SELECT merchant_serial FROM merchant.merchant_instances LOOP + s := 'merchant_instance_' || rec.merchant_serial::TEXT; + +#include "merchant-0036-copy.sql.fragment" + +#include "merchant-0036-setval.sql.fragment" + + END LOOP; +END $MIG$; + +SET session_replication_role = origin; + +-- --------------------------------------------------------------------- +-- Step 4: Drop old per-instance tables from the merchant schema, in +-- reverse FK-dependency order so CASCADE is not needed. +-- --------------------------------------------------------------------- +#include "merchant-0036-drop.sql.fragment" + +COMMIT; diff --git a/src/backenddb/sql-schema/meson.build b/src/backenddb/sql-schema/meson.build @@ -66,6 +66,7 @@ sqlfiles = [ 'merchant-0033.sql', 'merchant-0034.sql', 'merchant-0035.sql', + 'merchant-0036.sql', 'versioning.sql', ] diff --git a/src/backenddb/unlock_inventory.c b/src/backenddb/unlock_inventory.c @@ -34,13 +34,17 @@ TALER_MERCHANTDB_unlock_inventory (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "unlock_inventory", - "DELETE" - " FROM merchant_inventory_locks" - " WHERE lock_uuid=$1"); + PREPARE_INSTANCE (pg, + stmt, + "unlock_inventory", + "DELETE" + " FROM merchant_inventory_locks" + " WHERE lock_uuid=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "unlock_inventory", + stmt, params); } diff --git a/src/backenddb/update_account.c b/src/backenddb/update_account.c @@ -35,7 +35,6 @@ TALER_MERCHANTDB_update_account (struct TALER_MERCHANTDB_PostgresContext *pg, const json_t *credit_facade_credentials) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (id), GNUNET_PQ_query_param_auto_from_type (h_wire), NULL == credit_facade_url ? GNUNET_PQ_query_param_null () @@ -48,21 +47,22 @@ TALER_MERCHANTDB_update_account (struct TALER_MERCHANTDB_PostgresContext *pg, : GNUNET_PQ_query_param_string (extra_wire_subject_metadata), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "update_account", - "UPDATE merchant_accounts SET" - " credit_facade_url=$3" - ",credit_facade_credentials=" - " COALESCE($4::TEXT::JSONB, credit_facade_credentials)" - ",extra_wire_subject_metadata=$5" - " WHERE h_wire=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1);"); + PREPARE_INSTANCE (pg, + stmt, + "update_account", + "UPDATE merchant_accounts SET" + " credit_facade_url=$2" + ",credit_facade_credentials=" + " COALESCE($3::TEXT::JSONB, credit_facade_credentials)" + ",extra_wire_subject_metadata=$4" + " WHERE h_wire=$1;"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_account", + stmt, params); } diff --git a/src/backenddb/update_category.c b/src/backenddb/update_category.c @@ -34,25 +34,25 @@ TALER_MERCHANTDB_update_category (struct TALER_MERCHANTDB_PostgresContext *pg, const json_t *category_name_i18n) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&category_id), GNUNET_PQ_query_param_string (category_name), TALER_PQ_query_param_json (category_name_i18n), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "update_category", - "UPDATE merchant_categories SET" - " category_name=$3" - ",category_name_i18n=$4::TEXT::JSONB" - " WHERE merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND category_serial=$2"); + PREPARE_INSTANCE (pg, + stmt, + "update_category", + "UPDATE merchant_categories SET" + " category_name=$2" + ",category_name_i18n=$3::TEXT::JSONB" + " WHERE category_serial=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_category", + stmt, params); } diff --git a/src/backenddb/update_contract_session.c b/src/backenddb/update_contract_session.c @@ -35,7 +35,6 @@ TALER_MERCHANTDB_update_contract_session (struct TALER_MERCHANTDB_PostgresContex bool *refunded) { 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), GNUNET_PQ_query_param_end @@ -49,9 +48,13 @@ TALER_MERCHANTDB_update_contract_session (struct TALER_MERCHANTDB_PostgresContex refunded), GNUNET_PQ_result_spec_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; /* Session ID must always be given by the caller. */ GNUNET_assert (NULL != session_id); + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); /* no preflight check here, run in transaction by caller! */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, @@ -59,24 +62,21 @@ TALER_MERCHANTDB_update_contract_session (struct TALER_MERCHANTDB_PostgresContex GNUNET_h2s (&h_contract_terms->hash), instance_id, session_id); - PREPARE (pg, - "update_contract_session", - "UPDATE merchant_contract_terms mct SET" - " session_id=$3" - " WHERE h_contract_terms=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " RETURNING" - " contract_terms->>'fulfillment_url' AS fulfillment_url" - " ,EXISTS (SELECT 1" - " FROM merchant_refunds mr" - " WHERE order_serial=mct.order_serial" - " ) AS refunded"); + PREPARE_INSTANCE (pg, + stmt, + "update_contract_session", + "UPDATE merchant_contract_terms mct SET" + " session_id=$2" + " WHERE h_contract_terms=$1" + " RETURNING" + " contract_terms->>'fulfillment_url' AS fulfillment_url" + " ,EXISTS (SELECT 1" + " FROM merchant_refunds mr" + " WHERE order_serial=mct.order_serial" + " ) AS refunded"); *fulfillment_url = NULL; return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "update_contract_session", + stmt, params, rs); } diff --git a/src/backenddb/update_contract_terms.c b/src/backenddb/update_contract_terms.c @@ -71,10 +71,12 @@ TALER_MERCHANTDB_update_contract_terms (struct TALER_MERCHANTDB_PostgresContext } } + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (order_id), TALER_PQ_query_param_json (contract_terms), GNUNET_PQ_query_param_auto_from_type (&h_contract_terms), @@ -85,21 +87,20 @@ TALER_MERCHANTDB_update_contract_terms (struct TALER_MERCHANTDB_PostgresContext : GNUNET_PQ_query_param_string (fulfillment_url), GNUNET_PQ_query_param_end }; - PREPARE (pg, - "update_contract_terms", - "UPDATE merchant_contract_terms SET" - " contract_terms=$3::TEXT::JSONB" - ",h_contract_terms=$4" - ",pay_deadline=$5" - ",refund_deadline=$6" - ",fulfillment_url=$7" - " WHERE order_id=$2" - " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"); + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + PREPARE_INSTANCE (pg, + stmt, + "update_contract_terms", + "UPDATE merchant_contract_terms SET" + " contract_terms=$2::TEXT::JSONB" + ",h_contract_terms=$3" + ",pay_deadline=$4" + ",refund_deadline=$5" + ",fulfillment_url=$6" + " WHERE order_id=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_contract_terms", + stmt, params); } } diff --git a/src/backenddb/update_deposit_confirmation_status.c b/src/backenddb/update_deposit_confirmation_status.c @@ -48,18 +48,22 @@ TALER_MERCHANTDB_update_deposit_confirmation_status (struct TALER_MERCHANTDB_Pos GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "update_deposit_confirmation_status", - "UPDATE merchant_deposits SET" - " settlement_retry_needed=$2" - ",settlement_retry_time=$3" - ",settlement_last_http_status=$4" - ",settlement_last_ec=$5" - ",settlement_last_detail=$6" - " WHERE deposit_serial=$1;"); + PREPARE_INSTANCE (pg, + stmt, + "update_deposit_confirmation_status", + "UPDATE merchant_deposits SET" + " settlement_retry_needed=$2" + ",settlement_retry_time=$3" + ",settlement_last_http_status=$4" + ",settlement_last_ec=$5" + ",settlement_last_detail=$6" + " WHERE deposit_serial=$1;"); return GNUNET_PQ_eval_prepared_non_select ( pg->conn, - "update_deposit_confirmation_status", + stmt, params); } diff --git a/src/backenddb/update_donau_instance.c b/src/backenddb/update_donau_instance.c @@ -37,7 +37,6 @@ TALER_MERCHANTDB_update_donau_instance (struct TALER_MERCHANTDB_PostgresContext struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (donau_url), GNUNET_PQ_query_param_string (charity->name), - GNUNET_PQ_query_param_auto_from_type (&charity->charity_pub), GNUNET_PQ_query_param_uint64 (&charity_id), TALER_PQ_query_param_amount_with_currency (pg->conn, &charity->max_per_year), @@ -46,24 +45,23 @@ TALER_MERCHANTDB_update_donau_instance (struct TALER_MERCHANTDB_PostgresContext GNUNET_PQ_query_param_uint64 (&charity->current_year), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "update_existing_donau_instance", - "UPDATE merchant_donau_instances SET" - " charity_name = $2," - " charity_max_per_year = $5," - " charity_receipts_to_date = $6," - " current_year = $7" - " WHERE charity_id = $4" - " AND merchant_instance_serial " - " = (SELECT merchant_serial" - " FROM merchant_instances mi" - " WHERE mi.merchant_pub = $3)" - " AND donau_url = $1;"); + PREPARE_INSTANCE (pg, + stmt, + "update_existing_donau_instance", + "UPDATE merchant_donau_instances SET" + " charity_name = $2," + " charity_max_per_year = $4," + " charity_receipts_to_date = $5," + " current_year = $6" + " WHERE charity_id = $3" + " AND donau_url = $1;"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_existing_donau_instance", + stmt, params); } diff --git a/src/backenddb/update_donau_instance_receipts_amount.c b/src/backenddb/update_donau_instance_receipts_amount.c @@ -40,15 +40,19 @@ TALER_MERCHANTDB_update_donau_instance_receipts_amount (struct TALER_MERCHANTDB_ GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "update_donau_instance_receipts", - "UPDATE merchant_donau_instances " - "SET charity_receipts_to_date = $2 " - "WHERE donau_instances_serial = $1;"); + PREPARE_INSTANCE (pg, + stmt, + "update_donau_instance_receipts", + "UPDATE merchant_donau_instances " + "SET charity_receipts_to_date = $2 " + "WHERE donau_instances_serial = $1;"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_donau_instance_receipts", + stmt, params); } diff --git a/src/backenddb/update_mfa_challenge.c b/src/backenddb/update_mfa_challenge.c @@ -42,17 +42,20 @@ TALER_MERCHANTDB_update_mfa_challenge (struct TALER_MERCHANTDB_PostgresContext * GNUNET_PQ_query_param_absolute_time (&retransmission_date), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; - PREPARE (pg, - "update_mfa_challenge", - "UPDATE tan_challenges" - " SET" - " code=$2" - " ,retry_counter=$3" - " ,expiration_date=$4" - " ,retransmission_date=$5" - " WHERE challenge_id = $1;"); + GNUNET_assert (NULL != pg->current_merchant_id); + PREPARE_INSTANCE (pg, + stmt, + "update_mfa_challenge", + "UPDATE tan_challenges" + " SET" + " code=$2" + " ,retry_counter=$3" + " ,expiration_date=$4" + " ,retransmission_date=$5" + " WHERE challenge_id = $1;"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_mfa_challenge", + stmt, params); } diff --git a/src/backenddb/update_money_pot.c b/src/backenddb/update_money_pot.c @@ -40,7 +40,6 @@ TALER_MERCHANTDB_update_money_pot (struct TALER_MERCHANTDB_PostgresContext *pg, bool *conflict_name) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&money_pot_id), GNUNET_PQ_query_param_string (name), GNUNET_PQ_query_param_string (description), @@ -67,18 +66,23 @@ TALER_MERCHANTDB_update_money_pot (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "update_money_pot", - "SELECT" - " out_conflict_name AS conflict_name" - ",out_conflict_total AS conflict_total" - ",out_not_found AS not_found" - " FROM merchant_do_update_money_pot" - "($1,$2,$3,$4,$5,$6);"); + PREPARE_INSTANCE (pg, + stmt, + "update_money_pot", + "SELECT" + " out_conflict_name AS conflict_name" + ",out_conflict_total AS conflict_total" + ",out_not_found AS not_found" + " FROM merchant_do_update_money_pot" + "($1,$2,$3,$4,$5);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "update_money_pot", + stmt, params, rs); GNUNET_PQ_cleanup_query_params_closures (params); diff --git a/src/backenddb/update_otp.c b/src/backenddb/update_otp.c @@ -34,7 +34,6 @@ TALER_MERCHANTDB_update_otp (struct TALER_MERCHANTDB_PostgresContext *pg, { uint32_t pos32 = (uint32_t) td->otp_algorithm; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (otp_id), GNUNET_PQ_query_param_string (td->otp_description), GNUNET_PQ_query_param_uint32 (&pos32), @@ -44,21 +43,22 @@ TALER_MERCHANTDB_update_otp (struct TALER_MERCHANTDB_PostgresContext *pg, : GNUNET_PQ_query_param_string (td->otp_key), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "update_otp", - "UPDATE merchant_otp_devices SET" - " otp_description=$3" - ",otp_algorithm=$4" - ",otp_ctr=$5" - ",otp_key=COALESCE($6,otp_key)" - " WHERE merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND otp_id=$2"); + PREPARE_INSTANCE (pg, + stmt, + "update_otp", + "UPDATE merchant_otp_devices SET" + " otp_description=$2" + ",otp_algorithm=$3" + ",otp_ctr=$4" + ",otp_key=COALESCE($5,otp_key)" + " WHERE otp_id=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_otp", + stmt, params); } diff --git a/src/backenddb/update_pending_webhook.c b/src/backenddb/update_pending_webhook.c @@ -36,15 +36,19 @@ TALER_MERCHANTDB_update_pending_webhook (struct TALER_MERCHANTDB_PostgresContext GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "update_pending_webhook", - "UPDATE merchant_pending_webhooks SET" - " retries=retries+1" - ",next_attempt=$2" - " WHERE webhook_pending_serial=$1"); + PREPARE_INSTANCE (pg, + stmt, + "update_pending_webhook", + "UPDATE merchant_pending_webhooks SET" + " retries=retries+1" + ",next_attempt=$2" + " WHERE webhook_pending_serial=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_pending_webhook", + stmt, params); } diff --git a/src/backenddb/update_product.c b/src/backenddb/update_product.c @@ -44,43 +44,40 @@ TALER_MERCHANTDB_update_product (struct TALER_MERCHANTDB_PostgresContext *pg, bool *no_pot) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), /* $1 */ - GNUNET_PQ_query_param_string (product_id), + GNUNET_PQ_query_param_string (product_id), /* $1 */ GNUNET_PQ_query_param_string (pd->description), - TALER_PQ_query_param_json (pd->description_i18n), /* $4 */ + TALER_PQ_query_param_json (pd->description_i18n), /* $3 */ GNUNET_PQ_query_param_string (pd->unit), - GNUNET_PQ_query_param_string (pd->image), /* $6 */ - TALER_PQ_query_param_json (pd->taxes), /* $7 */ + GNUNET_PQ_query_param_string (pd->image), /* $5 */ + TALER_PQ_query_param_json (pd->taxes), /* $6 */ TALER_PQ_query_param_array_amount_with_currency ( pd->price_array_length, pd->price_array, - pg->conn), /* $8 */ - GNUNET_PQ_query_param_uint64 (&pd->total_stock), /* $9 */ - GNUNET_PQ_query_param_uint32 (&pd->total_stock_frac), /* $10 */ - GNUNET_PQ_query_param_bool (pd->allow_fractional_quantity), /* $11 */ + pg->conn), /* $7 */ + GNUNET_PQ_query_param_uint64 (&pd->total_stock), /* $8 */ + GNUNET_PQ_query_param_uint32 (&pd->total_stock_frac), /* $9 */ + GNUNET_PQ_query_param_bool (pd->allow_fractional_quantity), /* $10 */ GNUNET_PQ_query_param_uint32 (&pd->fractional_precision_level), GNUNET_PQ_query_param_uint64 (&pd->total_lost), - TALER_PQ_query_param_json (pd->address), /* $14 */ + TALER_PQ_query_param_json (pd->address), /* $13 */ GNUNET_PQ_query_param_timestamp (&pd->next_restock), GNUNET_PQ_query_param_uint32 (&pd->minimum_age), GNUNET_PQ_query_param_array_uint64 (num_cats, cats, - pg->conn), /* $17 */ - GNUNET_PQ_query_param_string (pd->product_name), /* $18 */ + pg->conn), /* $16 */ + GNUNET_PQ_query_param_string (pd->product_name), /* $17 */ (0 == pd->product_group_id) ? GNUNET_PQ_query_param_null () : GNUNET_PQ_query_param_uint64 (&pd->product_group_id), (0 == pd->money_pot_id) ? GNUNET_PQ_query_param_null () - : GNUNET_PQ_query_param_uint64 (&pd->money_pot_id), /* $20 */ + : GNUNET_PQ_query_param_uint64 (&pd->money_pot_id), /* $19 */ GNUNET_PQ_query_param_bool (pd->price_is_net), GNUNET_PQ_query_param_end }; uint64_t ncat; bool cats_found = true; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_bool ("no_instance", - no_instance), GNUNET_PQ_result_spec_bool ("no_product", no_product), GNUNET_PQ_result_spec_bool ("lost_reduced", @@ -100,7 +97,9 @@ TALER_MERCHANTDB_update_product (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + *no_instance = false; if ( (pd->total_stock < pd->total_lost + pd->total_sold) || (pd->total_lost < pd->total_lost + pd->total_sold) /* integer overflow */) @@ -108,24 +107,27 @@ TALER_MERCHANTDB_update_product (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "update_product", - "SELECT" - " out_lost_reduced AS lost_reduced" - ",out_sold_reduced AS sold_reduced" - ",out_stocked_reduced AS stocked_reduced" - ",out_no_product AS no_product" - ",out_no_cat AS no_cat" - ",out_no_instance AS no_instance" - ",out_no_group AS no_group" - ",out_no_pot AS no_pot" - " FROM merchant_do_update_product" - "($1,$2,$3,$4::TEXT::JSONB,$5,$6,$7::TEXT::JSONB,$8,$9" - ",$10,$11,$12,$13,$14::TEXT::JSONB,$15,$16,$17,$18" - ",$19,$20,$21);"); + PREPARE_INSTANCE (pg, + stmt, + "update_product", + "SELECT" + " out_lost_reduced AS lost_reduced" + ",out_sold_reduced AS sold_reduced" + ",out_stocked_reduced AS stocked_reduced" + ",out_no_product AS no_product" + ",out_no_cat AS no_cat" + ",out_no_group AS no_group" + ",out_no_pot AS no_pot" + " FROM merchant_do_update_product" + "($1,$2,$3::TEXT::JSONB,$4,$5,$6::TEXT::JSONB,$7,$8" + ",$9,$10,$11,$12,$13::TEXT::JSONB,$14,$15,$16,$17" + ",$18,$19,$20);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "update_product", + stmt, params, rs); GNUNET_PQ_cleanup_query_params_closures (params); diff --git a/src/backenddb/update_product_group.c b/src/backenddb/update_product_group.c @@ -36,7 +36,6 @@ TALER_MERCHANTDB_update_product_group ( bool *conflict) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&product_group_id), GNUNET_PQ_query_param_string (name), GNUNET_PQ_query_param_string (description), @@ -51,18 +50,23 @@ TALER_MERCHANTDB_update_product_group ( GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "update_product_group", - "SELECT" - " out_conflict AS conflict" - ",out_not_found AS not_found" - " FROM merchant_do_update_product_group" - "($1,$2,$3,$4);"); + PREPARE_INSTANCE (pg, + stmt, + "update_product_group", + "SELECT" + " out_conflict AS conflict" + ",out_not_found AS not_found" + " FROM merchant_do_update_product_group" + "($1,$2,$3);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "update_product_group", + stmt, params, rs); if (qs <= 0) diff --git a/src/backenddb/update_report.c b/src/backenddb/update_report.c @@ -40,7 +40,6 @@ TALER_MERCHANTDB_update_report (struct TALER_MERCHANTDB_PostgresContext *pg, { struct GNUNET_TIME_Timestamp start; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&report_id), GNUNET_PQ_query_param_string (report_program_section), GNUNET_PQ_query_param_string (report_description), @@ -52,7 +51,11 @@ TALER_MERCHANTDB_update_report (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_query_param_timestamp (&start), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); start = GNUNET_TIME_absolute_to_timestamp ( GNUNET_TIME_absolute_add ( @@ -62,23 +65,20 @@ TALER_MERCHANTDB_update_report (struct TALER_MERCHANTDB_PostgresContext *pg, frequency_shift))); check_connection (pg); - PREPARE (pg, - "update_report", - "UPDATE merchant_reports SET" - " report_program_section=$3" - " ,report_description=$4" - " ,mime_type=$5" - " ,data_source=$6" - " ,target_address=$7" - " ,frequency=$8" - " ,frequency_shift=$9" - " ,next_transmission=$10" - " WHERE merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND report_serial=$2;"); + PREPARE_INSTANCE (pg, + stmt, + "update_report", + "UPDATE merchant_reports SET" + " report_program_section=$2" + " ,report_description=$3" + " ,mime_type=$4" + " ,data_source=$5" + " ,target_address=$6" + " ,frequency=$7" + " ,frequency_shift=$8" + " ,next_transmission=$9" + " WHERE report_serial=$1;"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_report", + stmt, params); } diff --git a/src/backenddb/update_report_status.c b/src/backenddb/update_report_status.c @@ -36,7 +36,6 @@ TALER_MERCHANTDB_update_report_status (struct TALER_MERCHANTDB_PostgresContext * { uint32_t ec = (uint32_t) last_error_code; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&report_id), GNUNET_PQ_query_param_timestamp (&next_transmission), TALER_EC_NONE == ec @@ -47,20 +46,21 @@ TALER_MERCHANTDB_update_report_status (struct TALER_MERCHANTDB_PostgresContext * : GNUNET_PQ_query_param_string (last_error_detail), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "update_report_status", - "UPDATE merchant_reports SET" - " next_transmission=$3" - ",last_error_code=$4" - ",last_error_detail=$5" - " WHERE merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND report_serial=$2;"); + PREPARE_INSTANCE (pg, + stmt, + "update_report_status", + "UPDATE merchant_reports SET" + " next_transmission=$2" + ",last_error_code=$3" + ",last_error_detail=$4" + " WHERE report_serial=$1;"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_report_status", + stmt, params); } diff --git a/src/backenddb/update_template.c b/src/backenddb/update_template.c @@ -33,7 +33,6 @@ TALER_MERCHANTDB_update_template (struct TALER_MERCHANTDB_PostgresContext *pg, const struct TALER_MERCHANTDB_TemplateDetails *td) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (template_id), GNUNET_PQ_query_param_string (td->template_description), (NULL == td->otp_id) @@ -46,32 +45,29 @@ TALER_MERCHANTDB_update_template (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "update_template", - "WITH mid AS (" - " SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - ",otp AS (" - " SELECT otp_serial" - " FROM merchant_otp_devices" - " JOIN mid USING (merchant_serial)" - " WHERE otp_id=$4)" - "UPDATE merchant_template SET" - " template_description=$3" - ",otp_device_id=" - " COALESCE((SELECT otp_serial" - " FROM otp), NULL)" - ",template_contract=$5::TEXT::JSONB" - ",editable_defaults=$6::TEXT::JSONB" - " WHERE merchant_serial=" - " (SELECT merchant_serial" - " FROM mid)" - " AND template_id=$2"); + PREPARE_INSTANCE (pg, + stmt, + "update_template", + "WITH otp AS (" + " SELECT otp_serial" + " FROM merchant_otp_devices" + " WHERE otp_id=$3)" + "UPDATE merchant_template SET" + " template_description=$2" + ",otp_device_id=" + " COALESCE((SELECT otp_serial" + " FROM otp), NULL)" + ",template_contract=$4::TEXT::JSONB" + ",editable_defaults=$5::TEXT::JSONB" + " WHERE template_id=$1"); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_template", + stmt, params); GNUNET_PQ_cleanup_query_params_closures (params); return qs; diff --git a/src/backenddb/update_token_family.c b/src/backenddb/update_token_family.c @@ -33,7 +33,6 @@ TALER_MERCHANTDB_update_token_family (struct TALER_MERCHANTDB_PostgresContext *p const struct TALER_MERCHANTDB_TokenFamilyDetails *details) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (token_family_slug), GNUNET_PQ_query_param_string (details->name), GNUNET_PQ_query_param_string (details->description), @@ -45,23 +44,24 @@ TALER_MERCHANTDB_update_token_family (struct TALER_MERCHANTDB_PostgresContext *p GNUNET_PQ_query_param_timestamp (&details->valid_before), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "update_token_family", - "UPDATE merchant_token_families SET" - " name=$3" - ",description=$4" - ",description_i18n=$5::TEXT::JSONB" - ",extra_data=$6::TEXT::JSONB" - ",valid_after=$7" - ",valid_before=$8" - " WHERE merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND slug=$2"); + PREPARE_INSTANCE (pg, + stmt, + "update_token_family", + "UPDATE merchant_token_families SET" + " name=$2" + ",description=$3" + ",description_i18n=$4::TEXT::JSONB" + ",extra_data=$5::TEXT::JSONB" + ",valid_after=$6" + ",valid_before=$7" + " WHERE slug=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_token_family", + stmt, params); } diff --git a/src/backenddb/update_transfer_status.c b/src/backenddb/update_transfer_status.c @@ -51,19 +51,23 @@ TALER_MERCHANTDB_update_transfer_status (struct TALER_MERCHANTDB_PostgresContext GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + + GNUNET_assert (NULL != pg->current_merchant_id); check_connection (pg); - PREPARE (pg, - "update_transfer_status", - "UPDATE merchant_expected_transfers SET" - " last_http_status=$3" - ",last_ec=$4" - ",last_detail=$5" - ",retry_needed=$6" - ",retry_time=$7" - " WHERE wtid=$1" - " AND exchange_url=$2"); + PREPARE_INSTANCE (pg, + stmt, + "update_transfer_status", + "UPDATE merchant_expected_transfers SET" + " last_http_status=$3" + ",last_ec=$4" + ",last_detail=$5" + ",retry_needed=$6" + ",retry_time=$7" + " WHERE wtid=$1" + " AND exchange_url=$2"); return GNUNET_PQ_eval_prepared_non_select ( pg->conn, - "update_transfer_status", + stmt, params); } diff --git a/src/backenddb/update_unit.c b/src/backenddb/update_unit.c @@ -42,7 +42,6 @@ TALER_MERCHANTDB_update_unit (struct TALER_MERCHANTDB_PostgresContext *pg, bool *builtin_conflict) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (unit_id), (NULL == unit_name_long) ? GNUNET_PQ_query_param_null () @@ -68,8 +67,6 @@ TALER_MERCHANTDB_update_unit (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_bool ("out_no_instance", - no_instance), GNUNET_PQ_result_spec_bool ("out_no_unit", no_unit), GNUNET_PQ_result_spec_bool ("out_builtin_conflict", @@ -77,18 +74,23 @@ TALER_MERCHANTDB_update_unit (struct TALER_MERCHANTDB_PostgresContext *pg, GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + *no_instance = false; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "update_unit", - "SELECT" - " out_no_instance" - " ,out_no_unit" - " ,out_builtin_conflict" - " FROM merchant_do_update_unit" - "($1,$2,$3,$4,$5,$6,$7,$8,$9);"); + PREPARE_INSTANCE (pg, + stmt, + "update_unit", + "SELECT" + " out_no_unit" + " ,out_builtin_conflict" + " FROM merchant_do_update_unit" + "($1,$2,$3,$4,$5,$6,$7,$8);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "update_unit", + stmt, params, rs); GNUNET_PQ_cleanup_query_params_closures (params); diff --git a/src/backenddb/update_webhook.c b/src/backenddb/update_webhook.c @@ -32,7 +32,6 @@ TALER_MERCHANTDB_update_webhook (struct TALER_MERCHANTDB_PostgresContext *pg, const struct TALER_MERCHANTDB_WebhookDetails *wb) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (webhook_id), GNUNET_PQ_query_param_string (wb->event_type), GNUNET_PQ_query_param_string (wb->url), @@ -45,23 +44,24 @@ TALER_MERCHANTDB_update_webhook (struct TALER_MERCHANTDB_PostgresContext *pg, : GNUNET_PQ_query_param_string (wb->body_template), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance_id, + pg->current_merchant_id)); check_connection (pg); - PREPARE (pg, - "update_webhook", - "UPDATE merchant_webhook SET" - " event_type=$3" - ",url=$4" - ",http_method=$5" - ",header_template=$6" - ",body_template=$7" - " WHERE merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)" - " AND webhook_id=$2"); + PREPARE_INSTANCE (pg, + stmt, + "update_webhook", + "UPDATE merchant_webhook SET" + " event_type=$2" + ",url=$3" + ",http_method=$4" + ",header_template=$5" + ",body_template=$6" + " WHERE webhook_id=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_webhook", + stmt, params); } diff --git a/src/backenddb/update_wirewatch_progress.c b/src/backenddb/update_wirewatch_progress.c @@ -33,24 +33,24 @@ TALER_MERCHANTDB_update_wirewatch_progress (struct TALER_MERCHANTDB_PostgresCont uint64_t last_serial) { struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance), GNUNET_PQ_query_param_string (payto_uri.full_payto), GNUNET_PQ_query_param_uint64 (&last_serial), GNUNET_PQ_query_param_end }; + char stmt[PG_PREP_INSTANCE_NAME_MAX]; - PREPARE (pg, - "update_wirewatch_progress", - "UPDATE merchant_accounts" - " SET last_bank_serial=$3" - " WHERE REGEXP_REPLACE(payto_uri,'\\?.*','')" - " =REGEXP_REPLACE(CAST ($2 AS TEXT),'\\?.*','')" - " AND merchant_serial =" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$1)"); + GNUNET_assert (NULL != pg->current_merchant_id); + GNUNET_assert (0 == strcmp (instance, + pg->current_merchant_id)); check_connection (pg); + PREPARE_INSTANCE (pg, + stmt, + "update_wirewatch_progress", + "UPDATE merchant_accounts" + " SET last_bank_serial=$2" + " WHERE REGEXP_REPLACE(payto_uri,'\\?.*','')" + " =REGEXP_REPLACE(CAST ($1 AS TEXT),'\\?.*','')"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "update_wirewatch_progress", + stmt, params); } diff --git a/src/include/merchant-database/set_instance.h b/src/include/merchant-database/set_instance.h @@ -0,0 +1,51 @@ +/* + This file is part of TALER + Copyright (C) 2026 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file src/include/merchant-database/set_instance.h + * @brief Select the per-instance schema active on the connection + * @author Christian Grothoff + */ +#ifndef MERCHANT_DATABASE_SET_INSTANCE_H +#define MERCHANT_DATABASE_SET_INSTANCE_H + +#include <taler/taler_util.h> +#include "merchantdb_lib.h" + + +/** + * Resolve @a instance_id to its merchant_serial and route subsequent + * per-instance queries on @a pg to the corresponding + * `merchant_instance_<serial>` schema. Updates the connection's + * search_path to "merchant_instance_<serial>, merchant" and stores + * @a instance_id / serial in the context for use by per-instance + * call sites and the #PREPARE_INSTANCE macro. + * + * Idempotent: a call with an already-active @a instance_id is a no-op. + * + * @param pg database connection + * @param instance_id instance to activate (must be NUL-terminated) + * @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the instance was found + * and the search_path was updated; + * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if no such instance exists; + * a hard or soft error code otherwise + */ +enum GNUNET_DB_QueryStatus +TALER_MERCHANTDB_set_instance ( + struct TALER_MERCHANTDB_PostgresContext *pg, + const char *instance_id); + + +#endif