/* This file is part of TALER (C) 2014--2019 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 ANASTASISABILITY 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 */ /** * @file sync/plugin_syncdb_postgres.c * @brief database helper functions for postgres used by sync * @author Christian Grothoff */ #include "platform.h" #include #include #include #include #include "sync_database_plugin.h" #include "sync_database_lib.h" /** * Type of the "cls" argument given to each of the functions in * our API. */ struct PostgresClosure { /** * Postgres connection handle. */ struct GNUNET_PQ_Context *conn; /** * Underlying configuration. */ const struct GNUNET_CONFIGURATION_Handle *cfg; /** * Name of the currently active transaction, NULL if none is active. */ const char *transaction_name; /** * Currency we accept payments in. */ char *currency; }; /** * Drop sync tables * * @param cls closure our `struct Plugin` * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ static int postgres_drop_tables (void *cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_ExecuteStatement es[] = { GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS accounts CASCADE;"), GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS payments;"), GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS backups;"), GNUNET_PQ_EXECUTE_STATEMENT_END }; return GNUNET_PQ_exec_statements (pg->conn, es); } /** * Check that the database connection is still up. * * @param pg connection to check */ static void check_connection (void *cls) { struct PostgresClosure *pg = cls; GNUNET_PQ_reconnect_if_down (pg->conn); } /** * Do a pre-flight check that we are not in an uncommitted transaction. * If we are, try to commit the previous transaction and output a warning. * Does not return anything, as we will continue regardless of the outcome. * * @param cls the `struct PostgresClosure` with the plugin-specific state */ static void postgres_preflight (void *cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_ExecuteStatement es[] = { GNUNET_PQ_make_execute ("COMMIT"), GNUNET_PQ_EXECUTE_STATEMENT_END }; if (NULL == pg->transaction_name) return; /* all good */ if (GNUNET_OK == GNUNET_PQ_exec_statements (pg->conn, es)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "BUG: Preflight check committed transaction `%s'!\n", pg->transaction_name); } else { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "BUG: Preflight check failed to commit transaction `%s'!\n", pg->transaction_name); } pg->transaction_name = NULL; } /** * Start a transaction. * * @param cls the `struct PostgresClosure` with the plugin-specific state * @param name unique name identifying the transaction (for debugging), * must point to a constant * @return #GNUNET_OK on success */ static int begin_transaction (void *cls, const char *name) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_ExecuteStatement es[] = { GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"), GNUNET_PQ_EXECUTE_STATEMENT_END }; check_connection (pg); postgres_preflight (pg); pg->transaction_name = name; if (GNUNET_OK != GNUNET_PQ_exec_statements (pg->conn, es)) { TALER_LOG_ERROR ("Failed to start transaction\n"); GNUNET_break (0); return GNUNET_SYSERR; } return GNUNET_OK; } /** * Roll back the current transaction of a database connection. * * @param cls the `struct PostgresClosure` with the plugin-specific state * @return #GNUNET_OK on success */ static void rollback (void *cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_ExecuteStatement es[] = { GNUNET_PQ_make_execute ("ROLLBACK"), GNUNET_PQ_EXECUTE_STATEMENT_END }; if (GNUNET_OK != GNUNET_PQ_exec_statements (pg->conn, es)) { TALER_LOG_ERROR ("Failed to rollback transaction\n"); GNUNET_break (0); } pg->transaction_name = NULL; } /** * Commit the current transaction of a database connection. * * @param cls the `struct PostgresClosure` with the plugin-specific state * @return transaction status code */ static enum GNUNET_DB_QueryStatus commit_transaction (void *cls) { struct PostgresClosure *pg = cls; enum SYNC_DB_QueryStatus qs; struct GNUNET_PQ_QueryParam no_params[] = { GNUNET_PQ_query_param_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "do_commit", no_params); pg->transaction_name = NULL; return qs; } /** * Function called to perform "garbage collection" on the * database, expiring records we no longer require. Deletes * all user records that are not paid up (and by cascade deletes * the associated recovery documents). Also deletes expired * truth and financial records older than @a fin_expire. * * @param cls closure * @param expire_backups backups older than the given time stamp should be garbage collected * @param expire_pending_payments payments still pending from since before * this value should be garbage collected * @return transaction status */ static enum SYNC_DB_QueryStatus postgres_gc (void *cls, struct GNUNET_TIME_Absolute expire_backups, struct GNUNET_TIME_Absolute expire_pending_payments) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { TALER_PQ_query_param_absolute_time (&expire_backups), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_QueryParam params2[] = { TALER_PQ_query_param_absolute_time (&expire_pending_payments), GNUNET_PQ_query_param_end }; enum SYNC_DB_QueryStatus qs; check_connection (pg); postgres_preflight (pg); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "gc_accounts", params); if (qs < 0) return qs; return GNUNET_PQ_eval_prepared_non_select (pg->conn, "gc_pending_payments", params2); } /** * Store payment. Used to begin a payment, not indicative * that the payment actually was made. (That is done * when we increment the account's lifetime.) * * @param cls closure * @param account_pub account to store @a backup under * @param order_id order we created * @param amount how much we asked for * @return transaction status */ static enum SYNC_DB_QueryStatus postgres_store_payment (void *cls, const struct SYNC_AccountPublicKeyP *account_pub, const char *order_id, const struct TALER_Amount *amount) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_absolute_time (&now), TALER_PQ_query_param_amount (amount), GNUNET_PQ_query_param_end }; check_connection (pg); postgres_preflight (pg); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "payment_insert", params); switch (qs) { case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: GNUNET_break (0); return SYNC_DB_NO_RESULTS; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return SYNC_DB_ONE_RESULT; case GNUNET_DB_STATUS_HARD_ERROR: GNUNET_break (0); return SYNC_DB_HARD_ERROR; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } } /** * Closure for #payment_by_account_cb. */ struct PaymentIteratorContext { /** * Function to call on each result */ SYNC_DB_PaymentPendingIterator it; /** * Closure for @e it. */ void *it_cls; /** * Plugin context. */ struct PostgresClosure *pg; /** * Query status to return. */ enum GNUNET_DB_QueryStatus qs; }; /** * Helper function for #postgres_lookup_pending_payments_by_account(). * To be called with the results of a SELECT statement * that has returned @a num_results results. * * @param cls closure of type `struct PaymentIteratorContext *` * @param result the postgres result * @param num_result the number of results in @a result */ static void payment_by_account_cb (void *cls, PGresult *result, unsigned int num_results) { struct PaymentIteratorContext *pic = cls; for (unsigned int i = 0; i < num_results; i++) { struct GNUNET_TIME_Absolute timestamp; char *order_id; struct TALER_Amount amount; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_absolute_time ("timestamp", ×tamp), GNUNET_PQ_result_spec_string ("order_id", &order_id), TALER_PQ_result_spec_amount ("amount", pic->pg->currency, &amount), GNUNET_PQ_result_spec_end }; if (GNUNET_OK != GNUNET_PQ_extract_result (result, rs, i)) { GNUNET_break (0); pic->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } pic->qs = i + 1; pic->it (pic->it_cls, timestamp, order_id, &amount); GNUNET_PQ_cleanup_result (rs); } } /** * Lookup pending payments by account. * * @param cls closure * @param account_pub account to look for pending payments under * @param it iterator to call on all pending payments * @param it_cls closure for @a it * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_lookup_pending_payments_by_account (void *cls, const struct SYNC_AccountPublicKeyP *account_pub, SYNC_DB_PaymentPendingIterator it, void *it_cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; struct PaymentIteratorContext pic = { .it = it, .it_cls = it_cls, .pg = pg }; enum GNUNET_DB_QueryStatus qs; check_connection (pg); postgres_preflight (pg); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "payments_select_by_account", params, &payment_by_account_cb, &pic); if (qs > 0) return pic.qs; GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs); return qs; } /** * Store backup. Only applicable for the FIRST backup under * an @a account_pub. Use @e update_backup_TR to update an * existing backup. * * @param cls closure * @param account_pub account to store @a backup under * @param account_sig signature affirming storage request * @param backup_hash hash of @a backup * @param backup_size number of bytes in @a backup * @param backup raw data to backup * @return transaction status */ static enum SYNC_DB_QueryStatus postgres_store_backup (void *cls, const struct SYNC_AccountPublicKeyP *account_pub, const struct SYNC_AccountSignatureP *account_sig, const struct GNUNET_HashCode *backup_hash, size_t backup_size, const void *backup) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; struct GNUNET_HashCode bh; static struct GNUNET_HashCode no_previous_hash; check_connection (pg); postgres_preflight (pg); { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_auto_from_type (account_sig), GNUNET_PQ_query_param_auto_from_type (&no_previous_hash), GNUNET_PQ_query_param_auto_from_type (backup_hash), GNUNET_PQ_query_param_fixed_size (backup, backup_size), GNUNET_PQ_query_param_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "backup_insert", params); } switch (qs) { case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: GNUNET_break (0); return SYNC_DB_NO_RESULTS; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return SYNC_DB_ONE_RESULT; case GNUNET_DB_STATUS_HARD_ERROR: /* handle interesting case below */ break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } /* First, check if account exists */ { struct GNUNET_TIME_Absolute ed; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("expiration_date", &ed), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "account_select", params, rs); } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: return SYNC_DB_PAYMENT_REQUIRED; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* handle interesting case below */ break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } /* account exists, check if existing backup conflicts */ { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("backup_hash", &bh), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "backup_select_hash", params, rs); } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: /* original error must have been a hard error, oddly enough */ return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* handle interesting case below */ break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } /* had an existing backup, is it identical? */ if (0 != GNUNET_memcmp (&bh, backup_hash)) /* previous conflicting backup exists */ return SYNC_DB_OLD_BACKUP_MISSMATCH; /* backup identical to what was provided, no change */ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } /** * Update backup. * * @param cls closure * @param account_pub account to store @a backup under * @param account_sig signature affirming storage request * @param old_backup_hash hash of the previous backup (must match) * @param backup_hash hash of @a backup * @param backup_size number of bytes in @a backup * @param backup raw data to backup * @return transaction status */ static enum SYNC_DB_QueryStatus postgres_update_backup (void *cls, const struct SYNC_AccountPublicKeyP *account_pub, const struct GNUNET_HashCode *old_backup_hash, const struct SYNC_AccountSignatureP *account_sig, const struct GNUNET_HashCode *backup_hash, size_t backup_size, const void *backup) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; struct GNUNET_HashCode bh; check_connection (pg); postgres_preflight (pg); { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (backup_hash), GNUNET_PQ_query_param_auto_from_type (account_sig), GNUNET_PQ_query_param_auto_from_type (old_backup_hash), GNUNET_PQ_query_param_fixed_size (backup, backup_size), GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_auto_from_type (old_backup_hash), GNUNET_PQ_query_param_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "backup_update", params); } switch (qs) { case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: GNUNET_break (0); return SYNC_DB_OLD_BACKUP_MISSING; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return SYNC_DB_ONE_RESULT; case GNUNET_DB_STATUS_HARD_ERROR: /* handle interesting case below */ break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } /* First, check if account exists */ { struct GNUNET_TIME_Absolute ed; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("expiration_date", &ed), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "account_select", params, rs); } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: return SYNC_DB_PAYMENT_REQUIRED; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* handle interesting case below */ break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } /* account exists, check if existing backup conflicts */ { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("backup_hash", &bh), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "backup_select_hash", params, rs); } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: /* Well, trying to update where there is no original is a hard erorr, even though an odd one */ return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* handle interesting case below */ break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } /* had an existing backup, is it identical? */ if (0 == GNUNET_memcmp (&bh, backup_hash)) /* backup identical to what was provided, no change */ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; if (0 == GNUNET_memcmp (&bh, old_backup_hash)) /* all constraints seem satisified, original error must have been a hard error */ return GNUNET_DB_STATUS_HARD_ERROR; /* previous backup does not match old_backup_hash */ return SYNC_DB_OLD_BACKUP_MISSMATCH; } /** * Lookup an account and associated backup meta data. * * @param cls closure * @param account_pub account to store @a backup under * @param backup_hash[OUT] set to hash of @a backup * @return transaction status */ static enum SYNC_DB_QueryStatus postgres_lookup_account (void *cls, const struct SYNC_AccountPublicKeyP *account_pub, struct GNUNET_HashCode *backup_hash) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; check_connection (pg); postgres_preflight (pg); { struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("backup_hash", backup_hash), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "backup_select_hash", params, rs); } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: break; /* handle interesting case below */ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return SYNC_DB_ONE_RESULT; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } /* check if account exists */ { struct GNUNET_TIME_Absolute expiration; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("expiration_date", &expiration), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "account_select", params, rs); } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: /* indicates: no account */ return SYNC_DB_PAYMENT_REQUIRED; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* indicates: no backup */ return SYNC_DB_NO_RESULTS; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } } /** * Obtain backup. * * @param cls closure * @param account_pub account to store @a backup under * @param account_sig[OUT] set to signature affirming storage request * @param prev_hash[OUT] set to hash of previous @a backup, all zeros if none * @param backup_hash[OUT] set to hash of @a backup * @param backup_size[OUT] set to number of bytes in @a backup * @param backup[OUT] set to raw data to backup, caller MUST FREE */ static enum SYNC_DB_QueryStatus postgres_lookup_backup (void *cls, const struct SYNC_AccountPublicKeyP *account_pub, struct SYNC_AccountSignatureP *account_sig, struct GNUNET_HashCode *prev_hash, struct GNUNET_HashCode *backup_hash, size_t *backup_size, void **backup) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("account_sig", account_sig), GNUNET_PQ_result_spec_auto_from_type ("prev_hash", prev_hash), GNUNET_PQ_result_spec_auto_from_type ("backup_hash", backup_hash), GNUNET_PQ_result_spec_variable_size ("data", backup, backup_size), GNUNET_PQ_result_spec_end }; check_connection (pg); postgres_preflight (pg); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "backup_select", params, rs); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: return SYNC_DB_NO_RESULTS; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return SYNC_DB_ONE_RESULT; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } } /** * Increment account lifetime. * * @param cls closure * @param account_pub which account received a payment * @param order_id order which was paid, must be unique and match pending payment * @param lifetime for how long is the account now paid (increment) * @return transaction status */ static enum SYNC_DB_QueryStatus postgres_increment_lifetime (void *cls, const struct SYNC_AccountPublicKeyP *account_pub, const char *order_id, struct GNUNET_TIME_Relative lifetime) { struct PostgresClosure *pg = cls; struct GNUNET_TIME_Absolute expiration; enum GNUNET_DB_QueryStatus qs; check_connection (pg); if (GNUNET_OK != begin_transaction (pg, "increment lifetime")) { GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "payment_done", params); if (0 >= qs) { /* payment made before, or unknown, or error => no further action! */ rollback (pg); return qs; } } { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_result_spec_absolute_time ("expiration_date", &expiration), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "account_select", params, rs); } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: rollback (pg); return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: rollback (pg); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_absolute_time (&expiration), GNUNET_PQ_query_param_end }; expiration = GNUNET_TIME_relative_to_absolute (lifetime); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "account_insert", params); } break; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_absolute_time (&expiration), GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; expiration = GNUNET_TIME_absolute_add (expiration, lifetime); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "account_update", params); } break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: rollback (pg); return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: rollback (pg); GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: GNUNET_break (0); rollback (pg); return SYNC_DB_NO_RESULTS; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } qs = commit_transaction (pg); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: return SYNC_DB_ONE_RESULT; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return SYNC_DB_ONE_RESULT; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } } /** * Initialize Postgres database subsystem. * * @param cls a configuration instance * @return NULL on error, otherwise a `struct TALER_SYNCDB_Plugin` */ void * libsync_plugin_db_postgres_init (void *cls) { struct GNUNET_CONFIGURATION_Handle *cfg = cls; struct PostgresClosure *pg; struct SYNC_DatabasePlugin *plugin; struct GNUNET_PQ_ExecuteStatement es[] = { /* Orders created by the frontend, not signed or given a nonce yet. The contract terms will change (nonce will be added) when moved to the contract terms table */ GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS accounts" "(account_pub BYTEA PRIMARY KEY CHECK (length(account_pub)=32)" ",expiration_date INT8 NOT NULL" ");"), GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS payments" "(account_pub BYTEA CHECK (length(account_pub)=32)" ",order_id VARCHAR PRIMARY KEY" ",timestamp INT8 NOT NULL" ",amount_val INT8 NOT NULL" /* amount we were paid */ ",amount_frac INT4 NOT NULL" ",paid BOOLEAN NOT NULL DEFAULT FALSE" ");"), GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS backups" "(account_pub BYTEA PRIMARY KEY REFERENCES accounts (account_pub)" ",account_sig BYTEA NOT NULL CHECK (length(account_sig)=64)" ",prev_hash BYTEA NOT NULL CHECK (length(prev_hash)=64)" ",backup_hash BYTEA NOT NULL CHECK (length(backup_hash)=64)" ",data BYTEA NOT NULL" ");"), /* index for gc */ GNUNET_PQ_make_try_execute ( "CREATE INDEX accounts_expire ON " "accounts (expiration_date);"), GNUNET_PQ_make_try_execute ( "CREATE INDEX payments_timestamp ON " "payments (paid,timestamp);"), GNUNET_PQ_EXECUTE_STATEMENT_END }; struct GNUNET_PQ_PreparedStatement ps[] = { GNUNET_PQ_make_prepare ("account_insert", "INSERT INTO accounts " "(account_pub" ",expiration_date" ") VALUES " "($1,$2);", 2), GNUNET_PQ_make_prepare ("payment_insert", "INSERT INTO payments " "(account_pub" ",order_id" ",timestamp" ",amount_val" ",amount_frac" ") VALUES " "($1,$2,$3,$4,$5);", 5), GNUNET_PQ_make_prepare ("payment_done", "UPDATE payments " "SET" " paid=TRUE " "WHERE" " order_id=$1" " AND" " account_pub=$2" " AND" " paid=FALSE;", 2), GNUNET_PQ_make_prepare ("account_update", "UPDATE accounts " "SET" " expiration_date=$1 " "WHERE" " account_pub=$2;", 2), GNUNET_PQ_make_prepare ("account_select", "SELECT" " expiration_date " "FROM" " accounts " "WHERE" " account_pub=$1;", 1), GNUNET_PQ_make_prepare ("payments_select", "SELECT" " account_pub" ",order_id" ",amount_val" ",amount_frac" " FROM payments" " WHERE paid=FALSE;", 0), GNUNET_PQ_make_prepare ("payments_select_by_account", "SELECT" " timestamp" ",order_id" ",amount_val" ",amount_frac" " FROM payments" " WHERE" " paid=FALSE" " AND" " account_pub=$1;", 1), GNUNET_PQ_make_prepare ("gc_accounts", "DELETE FROM accounts " "WHERE" " expiration_date < $1;", 1), GNUNET_PQ_make_prepare ("gc_pending_payments", "DELETE FROM payments " "WHERE" " paid=FALSE" " AND" " timestamp < $1;", 1), GNUNET_PQ_make_prepare ("backup_insert", "INSERT INTO backups " "(account_pub" ",account_sig" ",prev_hash" ",backup_hash" ",data" ") VALUES " "($1,$2,$3,$4,$5);", 5), GNUNET_PQ_make_prepare ("backup_update", "UPDATE backups " " SET" " backup_hash=$1" ",account_sig=$2" ",prev_hash=$3" ",data=$4" " WHERE" " account_pub=$5" " AND" " backup_hash=$6;", 6), GNUNET_PQ_make_prepare ("backup_select_hash", "SELECT " " backup_hash " "FROM" " backups " "WHERE" " account_pub=$1;", 1), GNUNET_PQ_make_prepare ("backup_select", "SELECT " " account_sig" ",prev_hash" ",backup_hash" ",data " "FROM" " backups " "WHERE" " account_pub=$1;", 1), GNUNET_PQ_make_prepare ("do_commit", "COMMIT", 0), GNUNET_PQ_PREPARED_STATEMENT_END }; pg = GNUNET_new (struct PostgresClosure); pg->cfg = cfg; pg->conn = GNUNET_PQ_connect_with_cfg (cfg, "syncdb-postgres", es, ps); if (NULL == pg->conn) { GNUNET_free (pg); return NULL; } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "taler", "CURRENCY", &pg->currency)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "taler", "CURRENCY"); GNUNET_PQ_disconnect (pg->conn); GNUNET_free (pg); return NULL; } plugin = GNUNET_new (struct SYNC_DatabasePlugin); plugin->cls = pg; plugin->drop_tables = &postgres_drop_tables; plugin->gc = &postgres_gc; plugin->store_payment_TR = &postgres_store_payment; plugin->lookup_pending_payments_by_account_TR = &postgres_lookup_pending_payments_by_account; plugin->store_backup_TR = &postgres_store_backup; plugin->lookup_account_TR = &postgres_lookup_account; plugin->lookup_backup_TR = &postgres_lookup_backup; plugin->update_backup_TR = &postgres_update_backup; plugin->increment_lifetime_TR = &postgres_increment_lifetime; return plugin; } /** * Shutdown Postgres database subsystem. * * @param cls a `struct SYNC_DB_Plugin` * @return NULL (always) */ void * libsync_plugin_db_postgres_done (void *cls) { struct SYNC_DatabasePlugin *plugin = cls; struct PostgresClosure *pg = plugin->cls; GNUNET_PQ_disconnect (pg->conn); GNUNET_free (pg); GNUNET_free (plugin); return NULL; } /* end of plugin_syncdb_postgres.c */