plugin_syncdb_postgres.c (42389B)
1 /* 2 This file is part of TALER 3 (C) 2014--2022 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Lesser General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of ANASTASISABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file syncdb/plugin_syncdb_postgres.c 18 * @brief database helper functions for postgres used by sync 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include <gnunet/gnunet_db_lib.h> 24 #include <gnunet/gnunet_pq_lib.h> 25 #include <taler/taler_pq_lib.h> 26 #include "sync_database_plugin.h" 27 #include "sync_database_lib.h" 28 29 /** 30 * Type of the "cls" argument given to each of the functions in 31 * our API. 32 */ 33 struct PostgresClosure 34 { 35 36 /** 37 * Postgres connection handle. 38 */ 39 struct GNUNET_PQ_Context *conn; 40 41 /** 42 * Directory with SQL statements to run to create tables. 43 */ 44 char *sql_dir; 45 46 /** 47 * Underlying configuration. 48 */ 49 const struct GNUNET_CONFIGURATION_Handle *cfg; 50 51 /** 52 * Name of the currently active transaction, NULL if none is active. 53 */ 54 const char *transaction_name; 55 56 /** 57 * Currency we accept payments in. 58 */ 59 char *currency; 60 61 /** 62 * Did we initialize the prepared statements 63 * for this session? 64 */ 65 bool init; 66 67 }; 68 69 70 /** 71 * Drop sync tables 72 * 73 * @param cls closure our `struct Plugin` 74 * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure 75 */ 76 static enum GNUNET_GenericReturnValue 77 postgres_drop_tables (void *cls) 78 { 79 struct PostgresClosure *pg = cls; 80 struct GNUNET_PQ_Context *conn; 81 enum GNUNET_GenericReturnValue ret; 82 83 if (NULL != pg->conn) 84 { 85 GNUNET_PQ_disconnect (pg->conn); 86 pg->conn = NULL; 87 pg->init = false; 88 } 89 conn = GNUNET_PQ_connect_with_cfg (pg->cfg, 90 "syncdb-postgres", 91 NULL, 92 NULL, 93 NULL); 94 if (NULL == conn) 95 return GNUNET_SYSERR; 96 ret = GNUNET_PQ_exec_sql (conn, 97 "drop"); 98 GNUNET_PQ_disconnect (conn); 99 return ret; 100 } 101 102 103 /** 104 * Establish connection to the database. 105 * 106 * @param cls plugin context 107 * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure 108 */ 109 static enum GNUNET_GenericReturnValue 110 prepare_statements (void *cls) 111 { 112 struct PostgresClosure *pg = cls; 113 struct GNUNET_PQ_PreparedStatement ps[] = { 114 GNUNET_PQ_make_prepare ("account_insert", 115 "INSERT INTO accounts " 116 "(account_pub" 117 ",expiration_date" 118 ") VALUES " 119 "($1,$2);"), 120 GNUNET_PQ_make_prepare ("payment_insert", 121 "INSERT INTO payments " 122 "(account_pub" 123 ",order_id" 124 ",token" 125 ",timestamp" 126 ",amount" 127 ") VALUES " 128 "($1,$2,$3,$4,$5);"), 129 GNUNET_PQ_make_prepare ("payment_done", 130 "UPDATE payments " 131 "SET" 132 " paid=TRUE " 133 "WHERE" 134 " order_id=$1" 135 " AND" 136 " account_pub=$2" 137 " AND" 138 " paid=FALSE;"), 139 GNUNET_PQ_make_prepare ("account_update", 140 "UPDATE accounts " 141 "SET" 142 " expiration_date=$1 " 143 "WHERE" 144 " account_pub=$2;"), 145 GNUNET_PQ_make_prepare ("account_select", 146 "SELECT" 147 " expiration_date " 148 "FROM" 149 " accounts " 150 "WHERE" 151 " account_pub=$1;"), 152 GNUNET_PQ_make_prepare ("payments_select", 153 "SELECT" 154 " account_pub" 155 ",order_id" 156 ",amount" 157 " FROM payments" 158 " WHERE paid=FALSE;"), 159 GNUNET_PQ_make_prepare ("payments_select_by_account", 160 "SELECT" 161 " timestamp" 162 ",order_id" 163 ",token" 164 ",amount" 165 " FROM payments" 166 " WHERE" 167 " paid=FALSE" 168 " AND" 169 " account_pub=$1;"), 170 GNUNET_PQ_make_prepare ("gc_accounts", 171 "DELETE FROM accounts " 172 "WHERE" 173 " expiration_date < $1;"), 174 GNUNET_PQ_make_prepare ("gc_pending_payments", 175 "DELETE FROM payments " 176 "WHERE" 177 " paid=FALSE" 178 " AND" 179 " timestamp < $1;"), 180 GNUNET_PQ_make_prepare ("backup_insert", 181 "INSERT INTO backups " 182 "(account_pub" 183 ",account_sig" 184 ",prev_hash" 185 ",backup_hash" 186 ",data" 187 ") VALUES " 188 "($1,$2,$3,$4,$5);"), 189 GNUNET_PQ_make_prepare ("backup_update", 190 "UPDATE backups " 191 " SET" 192 " backup_hash=$1" 193 ",account_sig=$2" 194 ",prev_hash=$3" 195 ",data=$4" 196 " WHERE" 197 " account_pub=$5" 198 " AND" 199 " backup_hash=$6;"), 200 GNUNET_PQ_make_prepare ("backup_select_hash", 201 "SELECT " 202 " backup_hash " 203 "FROM" 204 " backups " 205 "WHERE" 206 " account_pub=$1;"), 207 GNUNET_PQ_make_prepare ("backup_select", 208 "SELECT " 209 " account_sig" 210 ",prev_hash" 211 ",backup_hash" 212 ",data " 213 "FROM" 214 " backups " 215 "WHERE" 216 " account_pub=$1;"), 217 GNUNET_PQ_make_prepare ("do_commit", 218 "COMMIT"), 219 GNUNET_PQ_PREPARED_STATEMENT_END 220 }; 221 enum GNUNET_GenericReturnValue ret; 222 223 ret = GNUNET_PQ_prepare_statements (pg->conn, 224 ps); 225 if (GNUNET_OK != ret) 226 return ret; 227 pg->init = true; 228 return GNUNET_OK; 229 } 230 231 232 /** 233 * Connect to the database if the connection does not exist yet. 234 * 235 * @param pg the plugin-specific state 236 * @param skip_prepare true if we should skip prepared statement setup 237 * @return #GNUNET_OK on success 238 */ 239 static enum GNUNET_GenericReturnValue 240 internal_setup (struct PostgresClosure *pg) 241 { 242 if (NULL == pg->conn) 243 { 244 #if AUTO_EXPLAIN 245 /* Enable verbose logging to see where queries do not 246 properly use indices */ 247 struct GNUNET_PQ_ExecuteStatement es[] = { 248 GNUNET_PQ_make_try_execute ("LOAD 'auto_explain';"), 249 GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"), 250 GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"), 251 GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"), 252 /* https://wiki.postgresql.org/wiki/Serializable suggests to really 253 force the default to 'serializable' if SSI is to be used. */ 254 GNUNET_PQ_make_try_execute ( 255 "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"), 256 GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"), 257 GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"), 258 GNUNET_PQ_make_execute ("SET search_path TO sync;"), 259 GNUNET_PQ_EXECUTE_STATEMENT_END 260 }; 261 #else 262 struct GNUNET_PQ_ExecuteStatement es[] = { 263 GNUNET_PQ_make_execute ("SET search_path TO sync;"), 264 GNUNET_PQ_EXECUTE_STATEMENT_END 265 }; 266 #endif 267 struct GNUNET_PQ_Context *db_conn; 268 269 db_conn = GNUNET_PQ_connect_with_cfg2 (pg->cfg, 270 "syncdb-postgres", 271 "sync-", 272 es, 273 NULL, 274 GNUNET_PQ_FLAG_CHECK_CURRENT); 275 if (NULL == db_conn) 276 return GNUNET_SYSERR; 277 pg->conn = db_conn; 278 } 279 if (NULL == pg->transaction_name) 280 GNUNET_PQ_reconnect_if_down (pg->conn); 281 if (pg->init) 282 return GNUNET_OK; 283 return prepare_statements (pg); 284 } 285 286 287 /** 288 * Do a pre-flight check that we are not in an uncommitted transaction. 289 * If we are, try to commit the previous transaction and output a warning. 290 * Does not return anything, as we will continue regardless of the outcome. 291 * 292 * @param cls the `struct PostgresClosure` with the plugin-specific state 293 * @return #GNUNET_OK if everything is fine 294 * #GNUNET_NO if a transaction was rolled back 295 * #GNUNET_SYSERR on hard errors 296 */ 297 static enum GNUNET_GenericReturnValue 298 postgres_preflight (void *cls) 299 { 300 struct PostgresClosure *pg = cls; 301 struct GNUNET_PQ_ExecuteStatement es[] = { 302 GNUNET_PQ_make_execute ("ROLLBACK"), 303 GNUNET_PQ_EXECUTE_STATEMENT_END 304 }; 305 306 if (! pg->init) 307 { 308 if (GNUNET_OK != 309 internal_setup (pg)) 310 { 311 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 312 "Failed to ensure DB is initialized\n"); 313 return GNUNET_SYSERR; 314 } 315 } 316 if (NULL == pg->transaction_name) 317 return GNUNET_OK; /* all good */ 318 if (GNUNET_OK == 319 GNUNET_PQ_exec_statements (pg->conn, 320 es)) 321 { 322 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 323 "BUG: Preflight check rolled back transaction `%s'!\n", 324 pg->transaction_name); 325 } 326 else 327 { 328 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 329 "BUG: Preflight check failed to rollback transaction `%s'!\n", 330 pg->transaction_name); 331 } 332 pg->transaction_name = NULL; 333 return GNUNET_NO; 334 } 335 336 337 /** 338 * Check that the database connection is still up. 339 * 340 * @param cls a `struct PostgresClosure` with connection to check 341 */ 342 static void 343 check_connection (void *cls) 344 { 345 struct PostgresClosure *pg = cls; 346 347 GNUNET_PQ_reconnect_if_down (pg->conn); 348 } 349 350 351 /** 352 * Start a transaction. 353 * 354 * @param cls the `struct PostgresClosure` with the plugin-specific state 355 * @param name unique name identifying the transaction (for debugging), 356 * must point to a constant 357 * @return #GNUNET_OK on success 358 */ 359 static enum GNUNET_GenericReturnValue 360 begin_transaction (void *cls, 361 const char *name) 362 { 363 struct PostgresClosure *pg = cls; 364 struct GNUNET_PQ_ExecuteStatement es[] = { 365 GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"), 366 GNUNET_PQ_EXECUTE_STATEMENT_END 367 }; 368 369 check_connection (pg); 370 postgres_preflight (pg); 371 pg->transaction_name = name; 372 if (GNUNET_OK != 373 GNUNET_PQ_exec_statements (pg->conn, 374 es)) 375 { 376 TALER_LOG_ERROR ("Failed to start transaction\n"); 377 GNUNET_break (0); 378 return GNUNET_SYSERR; 379 } 380 return GNUNET_OK; 381 } 382 383 384 /** 385 * Roll back the current transaction of a database connection. 386 * 387 * @param cls the `struct PostgresClosure` with the plugin-specific state 388 */ 389 static void 390 rollback (void *cls) 391 { 392 struct PostgresClosure *pg = cls; 393 struct GNUNET_PQ_ExecuteStatement es[] = { 394 GNUNET_PQ_make_execute ("ROLLBACK"), 395 GNUNET_PQ_EXECUTE_STATEMENT_END 396 }; 397 398 if (GNUNET_OK != 399 GNUNET_PQ_exec_statements (pg->conn, 400 es)) 401 { 402 TALER_LOG_ERROR ("Failed to rollback transaction\n"); 403 GNUNET_break (0); 404 } 405 pg->transaction_name = NULL; 406 } 407 408 409 /** 410 * Commit the current transaction of a database connection. 411 * 412 * @param cls the `struct PostgresClosure` with the plugin-specific state 413 * @return transaction status code 414 */ 415 static enum GNUNET_DB_QueryStatus 416 commit_transaction (void *cls) 417 { 418 struct PostgresClosure *pg = cls; 419 enum GNUNET_DB_QueryStatus qs; 420 struct GNUNET_PQ_QueryParam no_params[] = { 421 GNUNET_PQ_query_param_end 422 }; 423 424 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 425 "do_commit", 426 no_params); 427 pg->transaction_name = NULL; 428 return qs; 429 } 430 431 432 /** 433 * Function called to perform "garbage collection" on the 434 * database, expiring records we no longer require. Deletes 435 * all user records that are not paid up (and by cascade deletes 436 * the associated recovery documents). Also deletes expired 437 * truth and financial records older than @a fin_expire. 438 * 439 * @param cls closure 440 * @param expire_backups backups older than the given time stamp should be garbage collected 441 * @param expire_pending_payments payments still pending from since before 442 * this value should be garbage collected 443 * @return transaction status 444 */ 445 static enum GNUNET_DB_QueryStatus 446 postgres_gc (void *cls, 447 struct GNUNET_TIME_Absolute expire_backups, 448 struct GNUNET_TIME_Absolute expire_pending_payments) 449 { 450 struct PostgresClosure *pg = cls; 451 struct GNUNET_PQ_QueryParam params[] = { 452 GNUNET_PQ_query_param_absolute_time (&expire_backups), 453 GNUNET_PQ_query_param_end 454 }; 455 struct GNUNET_PQ_QueryParam params2[] = { 456 GNUNET_PQ_query_param_absolute_time (&expire_pending_payments), 457 GNUNET_PQ_query_param_end 458 }; 459 enum GNUNET_DB_QueryStatus qs; 460 461 check_connection (pg); 462 postgres_preflight (pg); 463 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 464 "gc_accounts", 465 params); 466 if (qs < 0) 467 return qs; 468 return GNUNET_PQ_eval_prepared_non_select (pg->conn, 469 "gc_pending_payments", 470 params2); 471 } 472 473 474 /** 475 * Store payment. Used to begin a payment, not indicative 476 * that the payment actually was made. (That is done 477 * when we increment the account's lifetime.) 478 * 479 * @param cls closure 480 * @param account_pub account to store @a backup under 481 * @param order_id order we create 482 * @param token claim token to use, NULL for none 483 * @param amount how much we asked for 484 * @return transaction status 485 */ 486 static enum SYNC_DB_QueryStatus 487 postgres_store_payment (void *cls, 488 const struct SYNC_AccountPublicKeyP *account_pub, 489 const char *order_id, 490 const struct TALER_ClaimTokenP *token, 491 const struct TALER_Amount *amount) 492 { 493 struct PostgresClosure *pg = cls; 494 enum GNUNET_DB_QueryStatus qs; 495 struct TALER_ClaimTokenP tok; 496 struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); 497 struct GNUNET_PQ_QueryParam params[] = { 498 GNUNET_PQ_query_param_auto_from_type (account_pub), 499 GNUNET_PQ_query_param_string (order_id), 500 GNUNET_PQ_query_param_auto_from_type (&tok), 501 GNUNET_PQ_query_param_timestamp (&now), 502 TALER_PQ_query_param_amount (pg->conn, 503 amount), 504 GNUNET_PQ_query_param_end 505 }; 506 507 if (NULL == token) 508 memset (&tok, 0, sizeof (tok)); 509 else 510 tok = *token; 511 check_connection (pg); 512 postgres_preflight (pg); 513 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 514 "payment_insert", 515 params); 516 switch (qs) 517 { 518 case GNUNET_DB_STATUS_SOFT_ERROR: 519 GNUNET_break (0); 520 return SYNC_DB_SOFT_ERROR; 521 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 522 GNUNET_break (0); 523 return SYNC_DB_NO_RESULTS; 524 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 525 return SYNC_DB_ONE_RESULT; 526 case GNUNET_DB_STATUS_HARD_ERROR: 527 GNUNET_break (0); 528 return SYNC_DB_HARD_ERROR; 529 default: 530 GNUNET_break (0); 531 return SYNC_DB_HARD_ERROR; 532 } 533 } 534 535 536 /** 537 * Closure for #payment_by_account_cb. 538 */ 539 struct PaymentIteratorContext 540 { 541 /** 542 * Function to call on each result 543 */ 544 SYNC_DB_PaymentPendingIterator it; 545 546 /** 547 * Closure for @e it. 548 */ 549 void *it_cls; 550 551 /** 552 * Plugin context. 553 */ 554 struct PostgresClosure *pg; 555 556 /** 557 * Query status to return. 558 */ 559 enum GNUNET_DB_QueryStatus qs; 560 561 }; 562 563 564 /** 565 * Helper function for #postgres_lookup_pending_payments_by_account(). 566 * To be called with the results of a SELECT statement 567 * that has returned @a num_results results. 568 * 569 * @param cls closure of type `struct PaymentIteratorContext *` 570 * @param result the postgres result 571 * @param num_results the number of results in @a result 572 */ 573 static void 574 payment_by_account_cb (void *cls, 575 PGresult *result, 576 unsigned int num_results) 577 { 578 struct PaymentIteratorContext *pic = cls; 579 580 for (unsigned int i = 0; i < num_results; i++) 581 { 582 struct GNUNET_TIME_Timestamp timestamp; 583 char *order_id; 584 struct TALER_Amount amount; 585 struct TALER_ClaimTokenP token; 586 struct GNUNET_PQ_ResultSpec rs[] = { 587 GNUNET_PQ_result_spec_timestamp ("timestamp", 588 ×tamp), 589 GNUNET_PQ_result_spec_string ("order_id", 590 &order_id), 591 GNUNET_PQ_result_spec_auto_from_type ("token", 592 &token), 593 TALER_PQ_result_spec_amount ("amount", 594 pic->pg->currency, 595 &amount), 596 GNUNET_PQ_result_spec_end 597 }; 598 599 if (GNUNET_OK != 600 GNUNET_PQ_extract_result (result, 601 rs, 602 i)) 603 { 604 GNUNET_break (0); 605 pic->qs = GNUNET_DB_STATUS_HARD_ERROR; 606 return; 607 } 608 pic->qs = i + 1; 609 pic->it (pic->it_cls, 610 timestamp, 611 order_id, 612 &token, 613 &amount); 614 GNUNET_PQ_cleanup_result (rs); 615 } 616 } 617 618 619 /** 620 * Lookup pending payments by account. 621 * 622 * @param cls closure 623 * @param account_pub account to look for pending payments under 624 * @param it iterator to call on all pending payments 625 * @param it_cls closure for @a it 626 * @return transaction status 627 */ 628 static enum GNUNET_DB_QueryStatus 629 postgres_lookup_pending_payments_by_account (void *cls, 630 const struct 631 SYNC_AccountPublicKeyP *account_pub 632 , 633 SYNC_DB_PaymentPendingIterator it, 634 void *it_cls) 635 { 636 struct PostgresClosure *pg = cls; 637 struct GNUNET_PQ_QueryParam params[] = { 638 GNUNET_PQ_query_param_auto_from_type (account_pub), 639 GNUNET_PQ_query_param_end 640 }; 641 struct PaymentIteratorContext pic = { 642 .it = it, 643 .it_cls = it_cls, 644 .pg = pg 645 }; 646 enum GNUNET_DB_QueryStatus qs; 647 648 check_connection (pg); 649 postgres_preflight (pg); 650 qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, 651 "payments_select_by_account", 652 params, 653 &payment_by_account_cb, 654 &pic); 655 if (qs > 0) 656 return pic.qs; 657 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs); 658 return qs; 659 } 660 661 662 /** 663 * Store backup. Only applicable for the FIRST backup under 664 * an @a account_pub. Use @e update_backup_TR to update an 665 * existing backup. 666 * 667 * @param cls closure 668 * @param account_pub account to store @a backup under 669 * @param account_sig signature affirming storage request 670 * @param backup_hash hash of @a backup 671 * @param backup_size number of bytes in @a backup 672 * @param backup raw data to backup 673 * @return transaction status 674 */ 675 static enum SYNC_DB_QueryStatus 676 postgres_store_backup (void *cls, 677 const struct SYNC_AccountPublicKeyP *account_pub, 678 const struct SYNC_AccountSignatureP *account_sig, 679 const struct GNUNET_HashCode *backup_hash, 680 size_t backup_size, 681 const void *backup) 682 { 683 struct PostgresClosure *pg = cls; 684 enum GNUNET_DB_QueryStatus qs; 685 struct GNUNET_HashCode bh; 686 static struct GNUNET_HashCode no_previous_hash; 687 688 check_connection (pg); 689 postgres_preflight (pg); 690 { 691 struct GNUNET_PQ_QueryParam params[] = { 692 GNUNET_PQ_query_param_auto_from_type (account_pub), 693 GNUNET_PQ_query_param_auto_from_type (account_sig), 694 GNUNET_PQ_query_param_auto_from_type (&no_previous_hash), 695 GNUNET_PQ_query_param_auto_from_type (backup_hash), 696 GNUNET_PQ_query_param_fixed_size (backup, 697 backup_size), 698 GNUNET_PQ_query_param_end 699 }; 700 701 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 702 "backup_insert", 703 params); 704 } 705 switch (qs) 706 { 707 case GNUNET_DB_STATUS_SOFT_ERROR: 708 GNUNET_break (0); 709 return SYNC_DB_SOFT_ERROR; 710 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 711 return SYNC_DB_NO_RESULTS; 712 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 713 return SYNC_DB_ONE_RESULT; 714 case GNUNET_DB_STATUS_HARD_ERROR: 715 /* handle interesting case below */ 716 break; 717 default: 718 GNUNET_break (0); 719 return SYNC_DB_HARD_ERROR; 720 } 721 722 /* First, check if account exists */ 723 { 724 struct GNUNET_TIME_Timestamp ed; 725 struct GNUNET_PQ_QueryParam params[] = { 726 GNUNET_PQ_query_param_auto_from_type (account_pub), 727 GNUNET_PQ_query_param_end 728 }; 729 struct GNUNET_PQ_ResultSpec rs[] = { 730 GNUNET_PQ_result_spec_auto_from_type ("expiration_date", 731 &ed), 732 GNUNET_PQ_result_spec_end 733 }; 734 735 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 736 "account_select", 737 params, 738 rs); 739 } 740 switch (qs) 741 { 742 case GNUNET_DB_STATUS_HARD_ERROR: 743 return SYNC_DB_HARD_ERROR; 744 case GNUNET_DB_STATUS_SOFT_ERROR: 745 GNUNET_break (0); 746 return SYNC_DB_SOFT_ERROR; 747 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 748 return SYNC_DB_PAYMENT_REQUIRED; 749 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 750 /* handle interesting case below */ 751 break; 752 default: 753 GNUNET_break (0); 754 return SYNC_DB_HARD_ERROR; 755 } 756 757 /* account exists, check if existing backup conflicts */ 758 { 759 struct GNUNET_PQ_QueryParam params[] = { 760 GNUNET_PQ_query_param_auto_from_type (account_pub), 761 GNUNET_PQ_query_param_end 762 }; 763 struct GNUNET_PQ_ResultSpec rs[] = { 764 GNUNET_PQ_result_spec_auto_from_type ("backup_hash", 765 &bh), 766 GNUNET_PQ_result_spec_end 767 }; 768 769 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 770 "backup_select_hash", 771 params, 772 rs); 773 } 774 switch (qs) 775 { 776 case GNUNET_DB_STATUS_HARD_ERROR: 777 return SYNC_DB_HARD_ERROR; 778 case GNUNET_DB_STATUS_SOFT_ERROR: 779 GNUNET_break (0); 780 return SYNC_DB_SOFT_ERROR; 781 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 782 /* original error must have been a hard error, oddly enough */ 783 return SYNC_DB_HARD_ERROR; 784 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 785 /* handle interesting case below */ 786 break; 787 default: 788 GNUNET_break (0); 789 return SYNC_DB_HARD_ERROR; 790 } 791 792 /* had an existing backup, is it identical? */ 793 if (0 != GNUNET_memcmp (&bh, 794 backup_hash)) 795 /* previous conflicting backup exists */ 796 return SYNC_DB_OLD_BACKUP_MISMATCH; 797 /* backup identical to what was provided, no change */ 798 return SYNC_DB_NO_RESULTS; 799 } 800 801 802 /** 803 * Update backup. 804 * 805 * @param cls closure 806 * @param account_pub account to store @a backup under 807 * @param account_sig signature affirming storage request 808 * @param old_backup_hash hash of the previous backup (must match) 809 * @param backup_hash hash of @a backup 810 * @param backup_size number of bytes in @a backup 811 * @param backup raw data to backup 812 * @return transaction status 813 */ 814 static enum SYNC_DB_QueryStatus 815 postgres_update_backup (void *cls, 816 const struct SYNC_AccountPublicKeyP *account_pub, 817 const struct GNUNET_HashCode *old_backup_hash, 818 const struct SYNC_AccountSignatureP *account_sig, 819 const struct GNUNET_HashCode *backup_hash, 820 size_t backup_size, 821 const void *backup) 822 { 823 struct PostgresClosure *pg = cls; 824 enum GNUNET_DB_QueryStatus qs; 825 struct GNUNET_HashCode bh; 826 827 check_connection (pg); 828 postgres_preflight (pg); 829 { 830 struct GNUNET_PQ_QueryParam params[] = { 831 GNUNET_PQ_query_param_auto_from_type (backup_hash), 832 GNUNET_PQ_query_param_auto_from_type (account_sig), 833 GNUNET_PQ_query_param_auto_from_type (old_backup_hash), 834 GNUNET_PQ_query_param_fixed_size (backup, 835 backup_size), 836 GNUNET_PQ_query_param_auto_from_type (account_pub), 837 GNUNET_PQ_query_param_auto_from_type (old_backup_hash), 838 GNUNET_PQ_query_param_end 839 }; 840 841 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 842 "backup_update", 843 params); 844 } 845 switch (qs) 846 { 847 case GNUNET_DB_STATUS_SOFT_ERROR: 848 GNUNET_break (0); 849 return SYNC_DB_SOFT_ERROR; 850 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 851 /* handle interesting case below */ 852 break; 853 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 854 return SYNC_DB_ONE_RESULT; 855 case GNUNET_DB_STATUS_HARD_ERROR: 856 GNUNET_break (0); 857 return SYNC_DB_HARD_ERROR; 858 default: 859 GNUNET_break (0); 860 return SYNC_DB_HARD_ERROR; 861 } 862 863 /* First, check if account exists */ 864 { 865 struct GNUNET_TIME_Timestamp ed; 866 struct GNUNET_PQ_QueryParam params[] = { 867 GNUNET_PQ_query_param_auto_from_type (account_pub), 868 GNUNET_PQ_query_param_end 869 }; 870 struct GNUNET_PQ_ResultSpec rs[] = { 871 GNUNET_PQ_result_spec_auto_from_type ("expiration_date", 872 &ed), 873 GNUNET_PQ_result_spec_end 874 }; 875 876 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 877 "account_select", 878 params, 879 rs); 880 } 881 switch (qs) 882 { 883 case GNUNET_DB_STATUS_HARD_ERROR: 884 return SYNC_DB_HARD_ERROR; 885 case GNUNET_DB_STATUS_SOFT_ERROR: 886 GNUNET_break (0); 887 return SYNC_DB_SOFT_ERROR; 888 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 889 return SYNC_DB_PAYMENT_REQUIRED; 890 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 891 /* handle interesting case below */ 892 break; 893 default: 894 GNUNET_break (0); 895 return SYNC_DB_HARD_ERROR; 896 } 897 898 /* account exists, check if existing backup conflicts */ 899 { 900 struct GNUNET_PQ_QueryParam params[] = { 901 GNUNET_PQ_query_param_auto_from_type (account_pub), 902 GNUNET_PQ_query_param_end 903 }; 904 struct GNUNET_PQ_ResultSpec rs[] = { 905 GNUNET_PQ_result_spec_auto_from_type ("backup_hash", 906 &bh), 907 GNUNET_PQ_result_spec_end 908 }; 909 910 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 911 "backup_select_hash", 912 params, 913 rs); 914 } 915 switch (qs) 916 { 917 case GNUNET_DB_STATUS_HARD_ERROR: 918 return SYNC_DB_HARD_ERROR; 919 case GNUNET_DB_STATUS_SOFT_ERROR: 920 GNUNET_break (0); 921 return SYNC_DB_SOFT_ERROR; 922 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 923 return SYNC_DB_OLD_BACKUP_MISSING; 924 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 925 /* handle interesting case below */ 926 break; 927 default: 928 GNUNET_break (0); 929 return SYNC_DB_HARD_ERROR; 930 } 931 932 /* had an existing backup, is it identical? */ 933 if (0 == GNUNET_memcmp (&bh, 934 backup_hash)) 935 { 936 /* backup identical to what was provided, no change */ 937 return SYNC_DB_NO_RESULTS; 938 } 939 if (0 == GNUNET_memcmp (&bh, 940 old_backup_hash)) 941 /* all constraints seem satisfied, original error must 942 have been a hard error */ 943 return SYNC_DB_HARD_ERROR; 944 /* previous backup does not match old_backup_hash */ 945 return SYNC_DB_OLD_BACKUP_MISMATCH; 946 } 947 948 949 /** 950 * Lookup an account and associated backup meta data. 951 * 952 * @param cls closure 953 * @param account_pub account to store @a backup under 954 * @param[out] backup_hash set to hash of @a backup 955 * @return transaction status 956 */ 957 static enum SYNC_DB_QueryStatus 958 postgres_lookup_account (void *cls, 959 const struct SYNC_AccountPublicKeyP *account_pub, 960 struct GNUNET_HashCode *backup_hash) 961 { 962 struct PostgresClosure *pg = cls; 963 enum GNUNET_DB_QueryStatus qs; 964 struct GNUNET_PQ_QueryParam params[] = { 965 GNUNET_PQ_query_param_auto_from_type (account_pub), 966 GNUNET_PQ_query_param_end 967 }; 968 969 check_connection (pg); 970 postgres_preflight (pg); 971 { 972 struct GNUNET_PQ_ResultSpec rs[] = { 973 GNUNET_PQ_result_spec_auto_from_type ("backup_hash", 974 backup_hash), 975 GNUNET_PQ_result_spec_end 976 }; 977 978 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 979 "backup_select_hash", 980 params, 981 rs); 982 } 983 switch (qs) 984 { 985 case GNUNET_DB_STATUS_HARD_ERROR: 986 return SYNC_DB_HARD_ERROR; 987 case GNUNET_DB_STATUS_SOFT_ERROR: 988 GNUNET_break (0); 989 return SYNC_DB_SOFT_ERROR; 990 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 991 break; /* handle interesting case below */ 992 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 993 return SYNC_DB_ONE_RESULT; 994 default: 995 GNUNET_break (0); 996 return SYNC_DB_HARD_ERROR; 997 } 998 999 /* check if account exists */ 1000 { 1001 struct GNUNET_TIME_Timestamp expiration; 1002 struct GNUNET_PQ_ResultSpec rs[] = { 1003 GNUNET_PQ_result_spec_auto_from_type ("expiration_date", 1004 &expiration), 1005 GNUNET_PQ_result_spec_end 1006 }; 1007 1008 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 1009 "account_select", 1010 params, 1011 rs); 1012 } 1013 switch (qs) 1014 { 1015 case GNUNET_DB_STATUS_HARD_ERROR: 1016 return SYNC_DB_HARD_ERROR; 1017 case GNUNET_DB_STATUS_SOFT_ERROR: 1018 GNUNET_break (0); 1019 return SYNC_DB_SOFT_ERROR; 1020 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1021 /* indicates: no account */ 1022 return SYNC_DB_PAYMENT_REQUIRED; 1023 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1024 /* indicates: no backup */ 1025 return SYNC_DB_NO_RESULTS; 1026 default: 1027 GNUNET_break (0); 1028 return SYNC_DB_HARD_ERROR; 1029 } 1030 } 1031 1032 1033 /** 1034 * Obtain backup. 1035 * 1036 * @param cls closure 1037 * @param account_pub account to store @a backup under 1038 * @param[out] account_sig set to signature affirming storage request 1039 * @param[out] prev_hash set to hash of previous @a backup, all zeros if none 1040 * @param[out] backup_hash set to hash of @a backup 1041 * @param[out] backup_size set to number of bytes in @a backup 1042 * @param[out] backup set to raw data to backup, caller MUST FREE 1043 */ 1044 static enum SYNC_DB_QueryStatus 1045 postgres_lookup_backup (void *cls, 1046 const struct SYNC_AccountPublicKeyP *account_pub, 1047 struct SYNC_AccountSignatureP *account_sig, 1048 struct GNUNET_HashCode *prev_hash, 1049 struct GNUNET_HashCode *backup_hash, 1050 size_t *backup_size, 1051 void **backup) 1052 { 1053 struct PostgresClosure *pg = cls; 1054 enum GNUNET_DB_QueryStatus qs; 1055 struct GNUNET_PQ_QueryParam params[] = { 1056 GNUNET_PQ_query_param_auto_from_type (account_pub), 1057 GNUNET_PQ_query_param_end 1058 }; 1059 struct GNUNET_PQ_ResultSpec rs[] = { 1060 GNUNET_PQ_result_spec_auto_from_type ("account_sig", 1061 account_sig), 1062 GNUNET_PQ_result_spec_auto_from_type ("prev_hash", 1063 prev_hash), 1064 GNUNET_PQ_result_spec_auto_from_type ("backup_hash", 1065 backup_hash), 1066 GNUNET_PQ_result_spec_variable_size ("data", 1067 backup, 1068 backup_size), 1069 GNUNET_PQ_result_spec_end 1070 }; 1071 1072 check_connection (pg); 1073 postgres_preflight (pg); 1074 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 1075 "backup_select", 1076 params, 1077 rs); 1078 switch (qs) 1079 { 1080 case GNUNET_DB_STATUS_HARD_ERROR: 1081 return SYNC_DB_HARD_ERROR; 1082 case GNUNET_DB_STATUS_SOFT_ERROR: 1083 GNUNET_break (0); 1084 return SYNC_DB_SOFT_ERROR; 1085 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1086 return SYNC_DB_NO_RESULTS; 1087 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1088 return SYNC_DB_ONE_RESULT; 1089 default: 1090 GNUNET_break (0); 1091 return SYNC_DB_HARD_ERROR; 1092 } 1093 } 1094 1095 1096 /** 1097 * Increment account lifetime. 1098 * 1099 * @param cls closure 1100 * @param account_pub which account received a payment 1101 * @param order_id order which was paid, must be unique and match pending payment 1102 * @param lifetime for how long is the account now paid (increment) 1103 * @return transaction status 1104 */ 1105 static enum SYNC_DB_QueryStatus 1106 postgres_increment_lifetime (void *cls, 1107 const struct SYNC_AccountPublicKeyP *account_pub, 1108 const char *order_id, 1109 struct GNUNET_TIME_Relative lifetime) 1110 { 1111 struct PostgresClosure *pg = cls; 1112 struct GNUNET_TIME_Timestamp expiration; 1113 enum GNUNET_DB_QueryStatus qs; 1114 1115 check_connection (pg); 1116 if (GNUNET_OK != 1117 begin_transaction (pg, 1118 "increment lifetime")) 1119 { 1120 GNUNET_break (0); 1121 return SYNC_DB_HARD_ERROR; 1122 } 1123 1124 { 1125 struct GNUNET_PQ_QueryParam params[] = { 1126 GNUNET_PQ_query_param_string (order_id), 1127 GNUNET_PQ_query_param_auto_from_type (account_pub), 1128 GNUNET_PQ_query_param_end 1129 }; 1130 1131 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 1132 "payment_done", 1133 params); 1134 switch (qs) 1135 { 1136 case GNUNET_DB_STATUS_HARD_ERROR: 1137 GNUNET_break (0); 1138 rollback (pg); 1139 return SYNC_DB_HARD_ERROR; 1140 case GNUNET_DB_STATUS_SOFT_ERROR: 1141 GNUNET_break (0); 1142 rollback (pg); 1143 return SYNC_DB_SOFT_ERROR; 1144 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1145 rollback (pg); 1146 return SYNC_DB_NO_RESULTS; 1147 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1148 break; 1149 } 1150 } 1151 1152 { 1153 struct GNUNET_PQ_QueryParam params[] = { 1154 GNUNET_PQ_query_param_auto_from_type (account_pub), 1155 GNUNET_PQ_query_param_end 1156 }; 1157 struct GNUNET_PQ_ResultSpec rs[] = { 1158 GNUNET_PQ_result_spec_timestamp ("expiration_date", 1159 &expiration), 1160 GNUNET_PQ_result_spec_end 1161 }; 1162 1163 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 1164 "account_select", 1165 params, 1166 rs); 1167 } 1168 1169 switch (qs) 1170 { 1171 case GNUNET_DB_STATUS_HARD_ERROR: 1172 rollback (pg); 1173 return SYNC_DB_HARD_ERROR; 1174 case GNUNET_DB_STATUS_SOFT_ERROR: 1175 rollback (pg); 1176 return SYNC_DB_SOFT_ERROR; 1177 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1178 { 1179 struct GNUNET_PQ_QueryParam params[] = { 1180 GNUNET_PQ_query_param_auto_from_type (account_pub), 1181 GNUNET_PQ_query_param_timestamp (&expiration), 1182 GNUNET_PQ_query_param_end 1183 }; 1184 1185 expiration = GNUNET_TIME_relative_to_timestamp (lifetime); 1186 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 1187 "account_insert", 1188 params); 1189 } 1190 break; 1191 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1192 { 1193 struct GNUNET_PQ_QueryParam params[] = { 1194 GNUNET_PQ_query_param_timestamp (&expiration), 1195 GNUNET_PQ_query_param_auto_from_type (account_pub), 1196 GNUNET_PQ_query_param_end 1197 }; 1198 1199 expiration = GNUNET_TIME_absolute_to_timestamp ( 1200 GNUNET_TIME_absolute_add (expiration.abs_time, 1201 lifetime)); 1202 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 1203 "account_update", 1204 params); 1205 } 1206 break; 1207 default: 1208 GNUNET_break (0); 1209 return SYNC_DB_HARD_ERROR; 1210 } 1211 switch (qs) 1212 { 1213 case GNUNET_DB_STATUS_HARD_ERROR: 1214 rollback (pg); 1215 return SYNC_DB_HARD_ERROR; 1216 case GNUNET_DB_STATUS_SOFT_ERROR: 1217 rollback (pg); 1218 GNUNET_break (0); 1219 return SYNC_DB_SOFT_ERROR; 1220 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1221 GNUNET_break (0); 1222 rollback (pg); 1223 return SYNC_DB_NO_RESULTS; 1224 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1225 break; 1226 default: 1227 GNUNET_break (0); 1228 return SYNC_DB_HARD_ERROR; 1229 } 1230 qs = commit_transaction (pg); 1231 switch (qs) 1232 { 1233 case GNUNET_DB_STATUS_HARD_ERROR: 1234 return SYNC_DB_HARD_ERROR; 1235 case GNUNET_DB_STATUS_SOFT_ERROR: 1236 GNUNET_break (0); 1237 return SYNC_DB_SOFT_ERROR; 1238 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1239 return SYNC_DB_ONE_RESULT; 1240 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1241 return SYNC_DB_ONE_RESULT; 1242 default: 1243 GNUNET_break (0); 1244 return SYNC_DB_HARD_ERROR; 1245 } 1246 } 1247 1248 1249 /** 1250 * Initialize tables. 1251 * 1252 * @param cls the `struct PostgresClosure` with the plugin-specific state 1253 * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure 1254 */ 1255 static enum GNUNET_GenericReturnValue 1256 postgres_create_tables (void *cls) 1257 { 1258 struct PostgresClosure *pc = cls; 1259 struct GNUNET_PQ_Context *conn; 1260 struct GNUNET_PQ_ExecuteStatement es[] = { 1261 GNUNET_PQ_make_execute ("SET search_path TO sync;"), 1262 GNUNET_PQ_EXECUTE_STATEMENT_END 1263 }; 1264 1265 conn = GNUNET_PQ_connect_with_cfg (pc->cfg, 1266 "syncdb-postgres", 1267 "sync-", 1268 es, 1269 NULL); 1270 if (NULL == conn) 1271 return GNUNET_SYSERR; 1272 GNUNET_PQ_disconnect (conn); 1273 return GNUNET_OK; 1274 } 1275 1276 1277 /** 1278 * Initialize Postgres database subsystem. 1279 * 1280 * @param cls a configuration instance 1281 * @return NULL on error, otherwise a `struct TALER_SYNCDB_Plugin` 1282 */ 1283 void * 1284 libsync_plugin_db_postgres_init (void *cls); 1285 1286 /* make compiler happy */ 1287 void * 1288 libsync_plugin_db_postgres_init (void *cls) 1289 { 1290 struct GNUNET_CONFIGURATION_Handle *cfg = cls; 1291 struct PostgresClosure *pg; 1292 struct SYNC_DatabasePlugin *plugin; 1293 1294 pg = GNUNET_new (struct PostgresClosure); 1295 pg->cfg = cfg; 1296 if (GNUNET_OK != 1297 GNUNET_CONFIGURATION_get_value_filename (cfg, 1298 "syncdb-postgres", 1299 "SQL_DIR", 1300 &pg->sql_dir)) 1301 { 1302 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1303 "syncdb-postgres", 1304 "SQL_DIR"); 1305 GNUNET_free (pg); 1306 return NULL; 1307 } 1308 if (GNUNET_OK != 1309 GNUNET_CONFIGURATION_get_value_string (cfg, 1310 "sync", 1311 "CURRENCY", 1312 &pg->currency)) 1313 { 1314 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1315 "sync", 1316 "CURRENCY"); 1317 GNUNET_free (pg->sql_dir); 1318 GNUNET_free (pg); 1319 return NULL; 1320 } 1321 plugin = GNUNET_new (struct SYNC_DatabasePlugin); 1322 plugin->cls = pg; 1323 plugin->create_tables = &postgres_create_tables; 1324 plugin->drop_tables = &postgres_drop_tables; 1325 plugin->preflight = &postgres_preflight; 1326 plugin->gc = &postgres_gc; 1327 plugin->store_payment_TR = &postgres_store_payment; 1328 plugin->lookup_pending_payments_by_account_TR = 1329 &postgres_lookup_pending_payments_by_account; 1330 plugin->store_backup_TR = &postgres_store_backup; 1331 plugin->lookup_account_TR = &postgres_lookup_account; 1332 plugin->lookup_backup_TR = &postgres_lookup_backup; 1333 plugin->update_backup_TR = &postgres_update_backup; 1334 plugin->increment_lifetime_TR = &postgres_increment_lifetime; 1335 return plugin; 1336 } 1337 1338 1339 /** 1340 * Shutdown Postgres database subsystem. 1341 * 1342 * @param cls a `struct SYNC_DB_Plugin` 1343 * @return NULL (always) 1344 */ 1345 void * 1346 libsync_plugin_db_postgres_done (void *cls); 1347 1348 /* make compiler happy */ 1349 void * 1350 libsync_plugin_db_postgres_done (void *cls) 1351 { 1352 struct SYNC_DatabasePlugin *plugin = cls; 1353 struct PostgresClosure *pg = plugin->cls; 1354 1355 GNUNET_PQ_disconnect (pg->conn); 1356 GNUNET_free (pg->currency); 1357 GNUNET_free (pg->sql_dir); 1358 GNUNET_free (pg); 1359 GNUNET_free (plugin); 1360 return NULL; 1361 } 1362 1363 1364 /* end of plugin_syncdb_postgres.c */