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:
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 (×tamp),
@@ -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 (×tamp),
@@ -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,
<c);
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,
<dc);
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,
<do);
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,
<dc);
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,
<c);
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",
¤t_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