diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 360 |
1 files changed, 189 insertions, 171 deletions
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index ee2180dd..a0b28205 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014, 2015, 2016 INRIA + (C) 2014, 2015, 2016, 2017 INRIA 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 @@ -182,55 +182,6 @@ postgres_rollback (void *cls) PQclear (result); } -/** - * Check the @a result's error code to see what happened. - * Also logs errors. - * - * @param pg `struct PostgresClosure` - * @param result result to check - * @return #GNUNET_OK if the request/transaction succeeded - * #GNUNET_NO if it failed but could succeed if retried - * #GNUNET_SYSERR on hard errors - */ -static int -evaluate_pq_result (struct PostgresClosure *pg, - PGresult *result) -{ - if (PGRES_COMMAND_OK != - PQresultStatus (result)) - { - const char *sqlstate; - - sqlstate = PQresultErrorField (result, - PG_DIAG_SQLSTATE); - if (NULL == sqlstate) - { - /* very unexpected... */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - if ( (0 == strcmp (sqlstate, - PQ_DIAG_SQLSTATE_DEADLOCK)) || /*FIXME: no threads, really needed?*/ - (0 == strcmp (sqlstate, - PQ_DIAG_SQLSTATE_SERIALIZATION_FAILURE)) ) - { - /* These two can be retried and have a fair chance of working - the next time */ - QUERY_ERR (result, pg->conn); - return GNUNET_NO; - } - BREAK_DB_ERR(result); - return GNUNET_SYSERR; - } - return GNUNET_OK; - - /** - * NOTE: this function equals 99% the one from the exchange codebase. - * The difference is that here we don't use the session->state field ( - * see original code: exchange/src/exchangedb/plugin_exchangedb_postgres.c). - * Good/bad? - */ -} /** * Commit the current transaction of a database connection. @@ -244,23 +195,23 @@ static int postgres_commit (void *cls) { struct PostgresClosure *pg = cls; - PGresult *result; - int ret; - - result = PQexec (pg->conn, - "COMMIT"); - ret = evaluate_pq_result (pg, - result); - GNUNET_break (GNUNET_SYSERR != ret); - PQclear (result); - return ret; + enum GNUNET_PQ_QueryStatus ret; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_end + }; - /** - * NOTE: this function equals 99% the one from the exchange codebase. - * The difference is that here we don't use the session->state field ( - * see original code: exchange/src/exchangedb/plugin_exchangedb_postgres.c). - * Good/bad? - */ + ret = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "end_transaction", + params); + switch (ret) + { + case GNUNET_PQ_STATUS_HARD_ERROR: + return GNUNET_SYSERR; + case GNUNET_PQ_STATUS_SOFT_ERROR: + return GNUNET_NO; + default: + return GNUNET_OK; + } } @@ -323,7 +274,7 @@ postgres_initialize (void *cls) ",exchange_proof BYTEA NOT NULL" ",PRIMARY KEY (h_contract_terms, coin_pub)" ");"), - GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_proofs (" + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_proofs (" " exchange_uri VARCHAR NOT NULL" ",wtid BYTEA CHECK (LENGTH(wtid)=32)" ",execution_time INT8 NOT NULL" @@ -413,11 +364,14 @@ postgres_initialize (void *cls) " h_contract_terms=$1" " AND merchant_pub=$2", 2), + GNUNET_PQ_make_prepare ("end_transaction", + "COMMIT", + 0), /*NOTE: minimal version, to be expanded on a needed basis*/ GNUNET_PQ_make_prepare ("find_refunds", "SELECT" - " refund_amount_val" + " refund_amount_val" ",refund_amount_frac" ",refund_amount_curr" " FROM merchant_refunds" @@ -1763,72 +1717,98 @@ postgres_get_refunds_from_contract_terms_hash (void *cls, return GNUNET_OK; } + /** - * Function called when some backoffice staff decides to award or - * increase the refund on an existing contract. - * - * @param cls closure - * @param h_contract_terms - * @param merchant_pub merchant's instance public key - * @param refund maximum refund to return to the customer for this contract - * @param reason 0-terminated UTF-8 string giving the reason why the customer - * got a refund (free form, business-specific) - * @return #GNUNET_OK if the refund is accepted - * #GNUNET_NO if the refund is at or below the previous refund amount - * #GNUNET_SYSERR on database error, i.e. contract unknown, DB on fire, - * (FIXME: distinguish hard/soft? who does retries?) + * Closure for #process_refund_cb. */ -int -postgres_increase_refund_for_contract (void *cls, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct TALER_Amount *refund, - const char *reason) +struct FindRefundContext { + struct TALER_Amount refunded_amount; - struct PostgresClosure *pg = cls; - PGresult *result; - unsigned int i; + int err; +}; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (h_contract_terms), - GNUNET_PQ_query_param_auto_from_type (merchant_pub), - GNUNET_PQ_query_param_end - }; - if (GNUNET_OK != - postgres_start (cls)) +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure, our `struct FindRefundContext` + * @param result the postgres result + * @param num_result the number of results in @a result + */ +static void +process_refund_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct FindRefundContext *ictx = cls; + + for (unsigned int i=0; i<num_results; i++) { - GNUNET_break (0); - return GNUNET_SYSERR; - } + /*Sum up refund*/ + struct TALER_Amount acc; + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_result_spec_amount ("refund_amount", + &acc), + GNUNET_PQ_result_spec_end + }; - result = GNUNET_PQ_exec_prepared (pg->conn, - "find_deposits", - params); + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + ictx->err = GNUNET_SYSERR; - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; + if (GNUNET_SYSERR == + TALER_amount_add (&ictx->refunded_amount, + &ictx->refunded_amount, + &acc)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not add amounts\n"); + ictx->err = GNUNET_SYSERR; + } } +} - if (0 == PQntuples (result)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unknown contract: %s (merchant_pub: %s), no refund possible\n", - GNUNET_h2s (h_contract_terms), - TALER_B2S (merchant_pub)); - return GNUNET_SYSERR; - } - for (i=0;i<PQntuples (result);i++) +/** + * Closure for #process_deposits_cb. + */ +struct InsertRefundContext +{ + struct PostgresClosure *pg; + + int err; +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure, our `struct InsertRefundContext` + * @param result the postgres result + * @param num_result the number of results in @a result + */ +static void +process_deposits_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct InsertRefundContext *ctx = cls; + + for (unsigned int i=0;i<num_results;i++) { struct TALER_CoinSpendPublicKeyP coin_pub; struct TALER_Amount amount_with_fee; - struct TALER_Amount refunded_amount; - + struct FindRefundContext ictx; + enum GNUNET_PQ_QueryStatus ires; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (&coin_pub), + GNUNET_PQ_query_param_end + }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("coin_pub", &coin_pub), @@ -1843,53 +1823,23 @@ postgres_increase_refund_for_contract (void *cls, i)) { GNUNET_break (0); - PQclear (result); - return GNUNET_SYSERR; + ctx->err = GNUNET_SYSERR; + return; } - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (&coin_pub), - GNUNET_PQ_query_param_end - }; - result = GNUNET_PQ_exec_prepared (pg->conn, - "find_refunds", - params); - - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - goto rollback; - } TALER_amount_get_zero (amount_with_fee.currency, - &refunded_amount); - if (0 < PQntuples (result)) - { - for (i=0; PQntuples (result); i++) - { - /*Sum up refund*/ - struct TALER_Amount acc; - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_result_spec_amount ("refund_amount", - &acc), - GNUNET_PQ_result_spec_end - }; - - if (GNUNET_OK != - GNUNET_PQ_extract_result (result, - rs, - i)) - goto rollback; - - if (GNUNET_SYSERR == TALER_amount_add (&refunded_amount, - &refunded_amount, - &acc)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not add amounts\n"); - goto rollback; - } - } - } + &ictx.refunded_amount); + ictx.err = GNUNET_OK; /* no error so far */ + ires = GNUNET_PQ_eval_prepared_multi_select (ctx->pg->conn, + "find_refunds", + params, + &process_refund_cb, + &ictx); + if ( (GNUNET_OK != ictx.err) || + (GNUNET_PQ_STATUS_HARD_ERROR == ires) ) + goto rollback; + if (GNUNET_PQ_STATUS_SOFT_ERROR == ires) + goto rollback; // FIXME: #5010: actually rollback + retry! /** * FIXME: @@ -1901,11 +1851,85 @@ postgres_increase_refund_for_contract (void *cls, "Refund situation for coin '%s', deposited: %s, refunded: %s\n", TALER_B2S (&coin_pub), TALER_amount_to_string (&amount_with_fee), - TALER_amount_to_string (&refunded_amount)); + TALER_amount_to_string (&ictx.refunded_amount)); /*NOTE: this makes testcase fail. To keep around until logic has been written*/ + ctx->err = GNUNET_SYSERR; + return; + } + + rollback: + ctx->err = GNUNET_SYSERR; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed transaction, doing rollback\n"); + PQclear (result); + postgres_rollback (ctx->pg); + return; +} + + + +/** + * Function called when some backoffice staff decides to award or + * increase the refund on an existing contract. + * + * @param cls closure + * @param h_contract_terms + * @param merchant_pub merchant's instance public key + * @param refund maximum refund to return to the customer for this contract + * @param reason 0-terminated UTF-8 string giving the reason why the customer + * got a refund (free form, business-specific) + * @return #GNUNET_OK if the refund is accepted + * #GNUNET_NO if the refund is at or below the previous refund amount + * #GNUNET_SYSERR on database error, i.e. contract unknown, DB on fire, + * (FIXME: distinguish hard/soft? who does retries?) + */ +static int +postgres_increase_refund_for_contract (void *cls, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_Amount *refund, + const char *reason) +{ + struct PostgresClosure *pg = cls; + struct InsertRefundContext ctx; + enum GNUNET_PQ_QueryStatus ret; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_auto_from_type (merchant_pub), + GNUNET_PQ_query_param_end + }; + + if (GNUNET_OK != + postgres_start (cls)) + { + GNUNET_break (0); return GNUNET_SYSERR; } + ctx.pg = pg; + ctx.err = GNUNET_OK; + ret = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "find_deposits", + params, + &process_deposits_cb, + &ctx); + switch (ret) + { + case GNUNET_PQ_STATUS_SUCCESS_NO_RESULTS: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unknown contract: %s (merchant_pub: %s), no refund possible\n", + GNUNET_h2s (h_contract_terms), + TALER_B2S (merchant_pub)); + return GNUNET_SYSERR; + case GNUNET_PQ_STATUS_SOFT_ERROR: + return GNUNET_SYSERR; /* UGH, BUG #5010! */ + case GNUNET_PQ_STATUS_HARD_ERROR: + return GNUNET_SYSERR; + default: + /* got one or more deposits */ + break; + } + if (GNUNET_OK != postgres_commit (cls)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, @@ -1914,15 +1938,9 @@ postgres_increase_refund_for_contract (void *cls, } return GNUNET_OK; - - rollback: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed transaction, doing rollback\n"); - PQclear (result); - postgres_rollback (pg); - return GNUNET_SYSERR; } + /** * Lookup proof information about a wire transfer. * |