1 | /*************************************** 2 | $Revision: 1.2 $ 3 | 4 | Functions to keep records for crash recovery 5 | 6 | Status: NOT REVUED, NOT TESTED 7 | 8 | Author(s): Andrei Robachevsky 9 | 10 | ******************/ /****************** 11 | Modification History: 12 | andrei (11/08/2000) Created. 13 | ******************/ /****************** 14 | Copyright (c) 2000 RIPE NCC 15 | 16 | All Rights Reserved 17 | 18 | Permission to use, copy, modify, and distribute this software and its 19 | documentation for any purpose and without fee is hereby granted, 20 | provided that the above copyright notice appear in all copies and that 21 | both that copyright notice and this permission notice appear in 22 | supporting documentation, and that the name of the author not be 23 | used in advertising or publicity pertaining to distribution of the 24 | software without specific, written prior permission. 25 | 26 | THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 27 | ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL 28 | AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 29 | DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 30 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 31 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 32 | ***************************************/ 33 | 34 | #include "ud_tr.h" 35 | #include "ud.h" 36 | 37 | /************************************************************* 38 | 39 | SQL Tables used to keep records needed for crash recovery 40 | 41 | CREATE TABLE transaction_rec ( 42 | 0 transaction_id int(11) DEFAULT '0' NOT NULL auto_increment, 43 | 1 object_id int(10) unsigned DEFAULT '0' NOT NULL, 44 | 2 sequence_id int(10) unsigned DEFAULT '1' NOT NULL, 45 | 3 object_type tinyint(3) unsigned DEFAULT '0' NOT NULL, 46 | 4 save varchar(256) DEFAULT '' NOT NULL, 47 | 5 error_script blob DEFAULT '' NOT NULL, 48 | 6 mode tinyint(4) unsigned DEFAULT '0' NOT NULL, 49 | 7 succeeded tinyint(4) unsigned DEFAULT '0' NOT NULL, 50 | 8 action tinyint(4) unsigned DEFAULT '0' NOT NULL, 51 | 9 status tinyint(10) unsigned DEFAULT '0' NOT NULL, 52 | 10 clean tinyint(3) DEFAULT '0' NOT NULL, 53 | PRIMARY KEY (transaction_id) 54 | ); 55 | 56 | 57 | 58 | CREATE TABLE dummy_rec ( 59 | transaction_id int(11) DEFAULT '0' NOT NULL, 60 | object_id int(10) unsigned DEFAULT '0' NOT NULL, 61 | PRIMARY KEY (transaction_id, object_id) 62 | ); 63 | 64 | *************************************************************/ 65 | 66 | /************************************************************ 67 | * int TR_create_record() * 68 | * * 69 | * Create TR record * 70 | * * 71 | * First tries to delete record with the same transaction_id * 72 | * ( transaction_id == tr->transaction_id ) * 73 | * Then creates a new record in transaction_rec table * 74 | * * 75 | * Returns: transaction_id * 76 | * * 77 | ************************************************************/ 78 | 79 | long TR_create_record(Transaction_t *tr) 80 | { 81 | SQ_result_set_t *sql_result; 82 | GString *query; 83 | int sql_err; 84 | 85 | if(tr->load_pass != 0) return(0); /* for fast loader just return */ 86 | 87 | if ((query = g_string_sized_new(STR_L)) == NULL){ 88 | ER_perror(FAC_UD, UD_MEM, "cannot allocate gstring\n"); 89 | die; 90 | } 91 | /* delete record if exists*/ 92 | 93 | TR_delete_record(tr); 94 | 95 | 96 | /* compose record */ 97 | 98 | tr->action = TR_ACTION(tr->action) + TCP_ROLLBACK; 99 | 100 | g_string_sprintf(query, "INSERT transaction_rec " 101 | "SET transaction_id=%ld, " 102 | "object_id=%ld, " 103 | "sequence_id=%ld, " 104 | "object_type=%d, " 105 | "mode=%d, " 106 | "action=%d, " 107 | "status=%d ", 108 | tr->transaction_id, tr->object_id, tr->sequence_id, tr->class_type, tr->mode, TR_ACTION(tr->action), TR_STATUS(TCP_ROLLBACK)); 109 | sql_err=SQ_execute_query(tr->sql_connection, query->str, &sql_result); 110 | 111 | 112 | /* in case of an error copy error code and return */ 113 | if(sql_err) { 114 | ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str); 115 | die; 116 | } 117 | g_string_free(query, TRUE); 118 | return(tr->transaction_id); 119 | } 120 | 121 | 122 | /************************************************************ 123 | * int TR_update_record() * 124 | * * 125 | * UPdates TR record (transaction_rec or dummy_rec tables) * 126 | * * 127 | * Updates the following fields: * 128 | * TF_DUMMY - dummy_rec, adding ID's as dummies are created * 129 | * TF_SAVE - writes down tr->save * 130 | * TF_STATUS - updates status (checkpointing) * 131 | * TF_ESCRIPT - saves error script tr->error_script * 132 | * * 133 | * Returns: transaction_id * 134 | * * 135 | ************************************************************/ 136 | 137 | long TR_update_record(Transaction_t *tr, int field) 138 | { 139 | SQ_result_set_t *sql_result; 140 | GString *query; 141 | int sql_err; 142 | 143 | if(tr->load_pass != 0) return(0); /* for fast loader just return */ 144 | 145 | if ((query = g_string_sized_new(STR_L)) == NULL){ 146 | ER_perror(FAC_UD, UD_MEM, "cannot allocate gstring\n"); 147 | die; 148 | } 149 | 150 | switch(field){ 151 | case TF_DUMMY: 152 | g_string_sprintf(query, "INSERT dummy_rec " 153 | "SET transaction_id=%ld, " 154 | "object_id=%ld ", 155 | tr->transaction_id, tr->dummy_id[tr->ndummy-1]); 156 | break; 157 | 158 | case TF_STATUS: 159 | g_string_sprintf(query, "UPDATE transaction_rec " 160 | "SET status=%d " 161 | "WHERE transaction_id=%ld ", 162 | TR_STATUS(tr->action), tr->transaction_id); 163 | break; 164 | 165 | case TF_SAVE: 166 | g_string_sprintf(query, "UPDATE transaction_rec " 167 | "SET save='%s' " 168 | "WHERE transaction_id=%ld ", 169 | tr->save, tr->transaction_id); 170 | break; 171 | 172 | case TF_ESCRIPT: 173 | g_string_sprintf(query, "UPDATE transaction_rec " 174 | "SET error_script='%s' " 175 | "WHERE transaction_id=%ld ", 176 | (tr->error_script)->str, tr->transaction_id); 177 | break; 178 | 179 | case TF_ID: 180 | g_string_sprintf(query, "UPDATE transaction_rec " 181 | "SET object_id=%ld, sequence_id=%ld, serial_id=%ld, succeeded=%d " 182 | "WHERE transaction_id=%ld ", 183 | tr->object_id, tr->sequence_id, tr->serial_id, tr->succeeded, tr->transaction_id); 184 | break; 185 | 186 | case TF_CLEAN: 187 | g_string_sprintf(query, "UPDATE transaction_rec " 188 | "SET clean=1 " 189 | "WHERE transaction_id=%ld ", 190 | tr->transaction_id); 191 | break; 192 | 193 | default: die; break; 194 | } 195 | 196 | sql_err=SQ_execute_query(tr->sql_connection, query->str, &sql_result); 197 | 198 | 199 | /* in case of an error copy error code and return */ 200 | if(sql_err) { 201 | ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str); 202 | die; 203 | } 204 | g_string_free(query, TRUE); 205 | return(tr->transaction_id); 206 | } 207 | 208 | /* Query the database for transaction record */ 209 | /* if there is no record with the specified ID - this is a new transaction */ 210 | /************************************************************/ 211 | SQ_result_set_t *tr_get_sql_record(SQ_connection_t *sql_connection, long transaction_id) 212 | { 213 | SQ_result_set_t *sql_result; 214 | GString *query; 215 | int sql_err; 216 | 217 | if ((query = g_string_sized_new(STR_L)) == NULL){ 218 | ER_perror(FAC_UD, UD_MEM, "cannot allocate gstring\n"); 219 | die; 220 | } 221 | 222 | /* compose query */ 223 | if (transaction_id == TR_LAST) 224 | g_string_sprintf(query, "SELECT * FROM transaction_rec WHERE clean=%d", TCP_UNCLEAN); 225 | else 226 | g_string_sprintf(query, "SELECT * FROM transaction_rec WHERE transaction_id=%ld", transaction_id); 227 | 228 | /* execute query */ 229 | sql_err=SQ_execute_query(sql_connection, query->str, &sql_result); 230 | 231 | 232 | /* in case of an error copy error code and return */ 233 | if(sql_err) { 234 | ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(sql_connection), query->str); 235 | die; 236 | } 237 | g_string_free(query, TRUE); 238 | return(sql_result); 239 | } 240 | 241 | 242 | /************************************************************/ 243 | long tr_get_long(SQ_result_set_t *result, SQ_row_t *row, int col) 244 | { 245 | long val; 246 | if( sscanf(SQ_get_column_string_nocopy(result, row, col), "%ld", &val) < 1 ) { die; } 247 | return(val); 248 | } 249 | /************************************************************/ 250 | int tr_get_int(SQ_result_set_t *result, SQ_row_t *row, int col) 251 | { 252 | int val; 253 | if( sscanf(SQ_get_column_string_nocopy(result, row, col), "%d", &val) < 1 ) { die; } 254 | return(val); 255 | } 256 | /************************************************************/ 257 | char *tr_get_str(SQ_result_set_t *result, SQ_row_t *row, int col) 258 | { 259 | return(SQ_get_column_string_nocopy(result, row, col)); 260 | } 261 | /************************************************************/ 262 | int tr_get_dummies(Transaction_t *tr) 263 | { 264 | SQ_result_set_t *sql_result; 265 | GString *query; 266 | int sql_err; 267 | SQ_row_t *sql_row; 268 | 269 | if ((query = g_string_sized_new(STR_L)) == NULL){ 270 | ER_perror(FAC_UD, UD_MEM, "cannot allocate gstring\n"); 271 | die; 272 | } 273 | 274 | /* compose query */ 275 | g_string_sprintf(query, "SELECT * FROM dummy_rec WHERE transaction_id=%ld", tr->transaction_id); 276 | sql_err=SQ_execute_query(tr->sql_connection, query->str, &sql_result); 277 | 278 | 279 | /* in case of an error copy error code and return */ 280 | if(sql_err) { 281 | ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str); 282 | die; 283 | } 284 | g_string_free(query, TRUE); 285 | 286 | tr->ndummy=0; 287 | while ( (sql_row = SQ_row_next(sql_result)) != NULL) { 288 | if( sscanf(SQ_get_column_string_nocopy(sql_result, sql_row, DUMMY_OBJECT_ID), "%ld", &(tr->dummy_id[tr->ndummy])) < 1 ) { die; } 289 | tr->ndummy++; 290 | } 291 | 292 | SQ_free_result(sql_result); 293 | return(tr->ndummy); 294 | } 295 | 296 | /************************************************************ 297 | * Transaction_t * TR_get_record() * 298 | * * 299 | * Get the record left from the failed transaction * 300 | * and fill the tr structure * 301 | * * 302 | * The following fields from transaction are essential: * 303 | * * 304 | * class_type * 305 | * action * 306 | * object_id * 307 | * sequesnce_id * 308 | * save * 309 | * ndummy * 310 | * dummy_id[] * 311 | * error_script * 312 | * * 313 | * The following fields are filled in by transaction_new() * 314 | * thread_upd * 315 | * thread_ins * 316 | * standalone * 317 | * 318 | * Return codes: * 319 | * * 320 | * NULL - everything is clean, no cleanup is needed * 321 | * 1 - the database was recovered successfully * 322 | * * 323 | ************************************************************/ 324 | Transaction_t *TR_get_record(SQ_connection_t *sql_connection, long transaction_id) 325 | { 326 | Transaction_t *tr; 327 | /* get the record from SQL table */ 328 | SQ_result_set_t *result; 329 | SQ_row_t *row; 330 | C_Type_t class_type; 331 | int res; 332 | 333 | 334 | result = tr_get_sql_record(sql_connection, transaction_id); 335 | if (result == NULL) return (NULL); /* no further actions */ 336 | 337 | /* fill in the Transaction structure */ 338 | if ((row = SQ_row_next(result))== NULL) return (NULL); /* no further actions */ 339 | 340 | 341 | /* Check if there is more than one row */ 342 | res = 0; 343 | while(SQ_row_next(result))res = -1; 344 | if(res == -1) die; 345 | 346 | 347 | class_type = tr_get_class_type(result, row); 348 | if ((tr = transaction_new(sql_connection, class_type)) == NULL) die; 349 | tr->object_id = tr_get_object_id(result, row); 350 | 351 | /* Fill in all dummies that were created */ 352 | tr_get_dummies(tr); 353 | 354 | tr->sequence_id = tr_get_sequence_id(result, row); 355 | tr->serial_id = tr_get_serial_id(result, row); 356 | tr->save = g_strdup(tr_get_save(result, row)); 357 | g_string_sprintf(tr->error_script, tr_get_escript(result, row)); 358 | 359 | 360 | /* mode of operation */ 361 | tr->mode = tr_get_mode(result, row); 362 | /* indication of success */ 363 | tr->succeeded = tr_get_success(result, row); 364 | /* action is low byte */ 365 | tr->action = tr_get_action(result, row); 366 | /* status is high byte */ 367 | tr->action |= (tr_get_status(result, row) <<8); 368 | tr->action |= (tr_get_clean(result, row) << 8); /* bit0 bears this flag */ 369 | 370 | SQ_free_result(result); 371 | return(tr); 372 | } 373 | 374 | /************************************************************ 375 | * int TR_delete_record() * 376 | * * 377 | * Deletes all associated sql records * 378 | * * 379 | * * 380 | ************************************************************/ 381 | void TR_delete_record(Transaction_t *tr) 382 | { 383 | GString *query; 384 | int sql_err; 385 | 386 | if(tr->load_pass != 0) return; /* for fast loader just return */ 387 | 388 | /* Delete a record from SQL DB */ 389 | if ((query = g_string_sized_new(STR_L)) == NULL){ 390 | ER_perror(FAC_UD, UD_MEM, "cannot allocate gstring\n"); 391 | die; 392 | } 393 | 394 | /* compose query */ 395 | g_string_sprintf(query, "DELETE FROM dummy_rec WHERE transaction_id=%ld", tr->transaction_id); 396 | sql_err=SQ_execute_query(tr->sql_connection, query->str, NULL); 397 | /* in case of an error copy error code and return */ 398 | if(sql_err) { 399 | ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str); 400 | die; 401 | } 402 | g_string_sprintf(query, "DELETE FROM transaction_rec WHERE transaction_id=%ld", tr->transaction_id); 403 | sql_err=SQ_execute_query(tr->sql_connection, query->str, NULL); 404 | /* in case of an error copy error code and return */ 405 | if(sql_err) { 406 | ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str); 407 | die; 408 | } 409 | 410 | g_string_free(query, TRUE); 411 | 412 | } 413 | 414 | 415 | /************************************************************ 416 | * int TR_recover() * 417 | * * 418 | * Cleans up the database after RIP daemon failure * 419 | * * 420 | * Return codes: * 421 | * * 422 | * 0 - everything is clean, no cleanup is needed * 423 | * 1 - the database was recovered successfully * 424 | * * 425 | ************************************************************/ 426 | int TR_recover(SQ_connection_t *sql_connection) 427 | { 428 | int res; 429 | Transaction_t * tr; 430 | 431 | /* XXX SQ_db_name() ? */ 432 | fprintf(stderr, "Checking the Database [%s]...", sql_connection->db); 433 | 434 | /* Get the transaction record */ 435 | /* XXX for NRTM we may specify transaction_id = 0 ? */ 436 | if ((tr = TR_get_record(sql_connection, TR_LAST)) == NULL) { 437 | /* everything is clean */ 438 | res = 0; 439 | fprintf(stderr, "[OK]\n"); 440 | } 441 | else {/* Not everything was perfect :( */ 442 | fprintf(stderr, "[FAILED]\n" 443 | "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n" 444 | "+ LAST TRANSACTION IS INCOMPLETE. ENTERING CRASH RECOVERY MODE +\n" 445 | "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); 446 | /* Failure occured before the ack was sent */ 447 | /* Roll back the transaction */ 448 | /* Delete transaction record (TR) as if it never happened */ 449 | /************************* R O L L B A C K ***************************/ 450 | if(TS_ROLLBACK(tr->action)) { 451 | fprintf(stderr, " STATUS: Rollback\n"); 452 | 453 | /* don't rollback the transaction if we were to delete the object, but could not */ 454 | if(!TS_ROLLBACKED(tr->action)){ 455 | fprintf(stderr, " STATUS: Rollback incomplete, completing..."); 456 | UD_rollback(tr); 457 | CP_ROLLBACK_PASSED(tr->action); TR_update_status(tr); 458 | fprintf(stderr, "[OK]\n"); 459 | } else fprintf(stderr, " STATUS: Rollback complete [PASSED]\n"); 460 | 461 | 462 | if(!TS_ROLLBACKED_NH(tr->action)){ 463 | fprintf(stderr, " STATUS: NH rollback incomplete, completing..."); 464 | NH_rollback(tr->sql_connection); 465 | CP_ROLLBACK_NH_PASSED(tr->action); TR_update_status(tr); 466 | fprintf(stderr, "[OK]\n"); 467 | } else fprintf(stderr, " STATUS: NH rollback complete [PASSED]\n"); 468 | /* In update mode delete TR record. Next time (if any) DBupdate tries to submit, we'll start from scratch */ 469 | /* In NRTM mode we create a serial record even in case of failure (tr->succeeded ==0)*/ 470 | /* So in NRTM we need to clean up serials/transaction as well */ 471 | if(IS_UPDATE(tr->mode)){ 472 | fprintf(stderr, " STATUS: Serial does not need to be restored, deleting TR..."); 473 | TR_delete_record(tr); 474 | fprintf(stderr, "[OK]\n"); 475 | } else { 476 | fprintf(stderr, " STATUS: Cleaning serial, deleting TR..."); 477 | if(!TS_CREATED_S(tr->action)) 478 | UD_rollback_serial(tr); 479 | else 480 | UD_commit_serial(tr); 481 | TR_delete_record(tr); 482 | fprintf(stderr, "[OK]\n"); 483 | } 484 | 485 | res = 1; 486 | } 487 | /************************* C O M M I T ******************************/ 488 | else { /* commit */ 489 | /* The ack was sent */ 490 | /* Complete the commit */ 491 | fprintf(stderr, " STATUS: Commit\n"); 492 | /* We keep the transaction record in case DBupdate failed */ 493 | /* and requests the same transaction after recovery ? */ 494 | /* Such approach will allow us to avoid 3-way handshaking with DBupdate */ 495 | /* So we never blocked or timed out during that phase */ 496 | 497 | /* XXX But first I implemented another approach (to keep DB tiny/tidy): */ 498 | /* 1. Process the transaction */ 499 | /* 2. In case of failure - rollback - NACK */ 500 | /* 3. Before commit - ACK (UD_ack()) */ 501 | /* 4. If UD_ack returns an error preserve a tr_record */ 502 | /* 5. Commit */ 503 | /* 6. If still alive and UD_ack passed - delete the record - all is clean */ 504 | /* Otherwise preserve a tr_record */ 505 | 506 | if(ACT_DELETE(tr->action)) { 507 | /* check if we passed deletion process */ 508 | if(!TS_DELETED(tr->action)){ 509 | fprintf(stderr, " STATUS: Delete incomplete, completing..."); 510 | UD_delete(tr); 511 | CP_DELETE_PASSED(tr->action); TR_update_status(tr); 512 | fprintf(stderr, "[OK]\n"); 513 | } else fprintf(stderr, " STATUS: Delete complete [PASSED]\n"); 514 | } 515 | else { /* update or create */ 516 | /* Check if we passed the deletion pass of commit */ 517 | if(!TS_COMMITTED_I(tr->action)){ 518 | fprintf(stderr, " STATUS: Commit phase I incomplete, completing..."); 519 | UD_commit_I(tr); 520 | CP_COMMIT_I_PASSED(tr->action); TR_update_status(tr); 521 | fprintf(stderr, "[OK]\n"); 522 | } else fprintf(stderr, " STATUS: Commit phase I complete [PASSED]\n"); 523 | /* Check if we passed the second pass of commit */ 524 | if(!TS_COMMITTED_II(tr->action)){ 525 | fprintf(stderr, " STATUS: Commit phase II incomplete, completing..."); 526 | UD_commit_II(tr); 527 | CP_COMMIT_II_PASSED(tr->action); TR_update_status(tr); 528 | fprintf(stderr, "[OK]\n"); 529 | } else fprintf(stderr, " STATUS: Commit phase II complete [PASSED]\n"); 530 | } /* end of delete, create, update specific operations */ 531 | 532 | /* Check if we passed the NH repository commit */ 533 | if(!TS_COMMITTED_NH(tr->action)){ 534 | fprintf(stderr, " STATUS: NH commit incomplete, completing..."); 535 | NH_commit(tr->sql_connection); 536 | CP_COMMIT_NH_PASSED(tr->action); TR_update_status(tr); 537 | fprintf(stderr, "[OK]\n"); 538 | } else fprintf(stderr, " STATUS: NH commit complete [PASSED]\n"); 539 | 540 | 541 | /* create serial file */ 542 | if(!TS_CREATED_S(tr->action)) 543 | { 544 | fprintf(stderr, " STATUS: Serial rollback and restore..."); 545 | UD_rollback_serial(tr); 546 | if(ACT_UPD_CLLPS(tr->action)) { /* this is a collapsed update (DEL + ADD) */ 547 | tr->action=TA_DELETE; UD_create_serial(tr); 548 | tr->sequence_id++; 549 | tr->action=TA_CREATE; UD_create_serial(tr); 550 | }else if(ACT_UPD_DUMMY(tr->action)) { /* this was a dummy update - we need only CREATE serial */ 551 | tr->action=TA_CREATE; 552 | tr->sequence_id++; /* because in fact this is an update (sequence_id=2) */ 553 | UD_create_serial(tr); 554 | } else UD_create_serial(tr); 555 | CP_CREATE_S_PASSED(tr->action); TR_update_status(tr); 556 | } 557 | fprintf(stderr, "[OK]\n"); 558 | UD_commit_serial(tr); 559 | 560 | fprintf(stderr, " STATUS: Marking TR as clean..."); 561 | TR_mark_clean(tr); 562 | 563 | fprintf(stderr, "[OK]\n"); 564 | 565 | res = 2; 566 | } 567 | } 568 | transaction_free(tr); 569 | fprintf(stderr, " STATUS: The Database is clean \n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\n"); 570 | 571 | return(res); 572 | } 573 | 574 | /************************************************************ 575 | * int TR_check() * 576 | * * 577 | * Checks if the requested transaction has already been * 578 | * processed. This could happen when DBupdate crashes while * 579 | * RIPupdate successfully completes the transaction. * 580 | * * 581 | * If this is the case, RIPupdate will return an ack to * 582 | * DBupdate as if the transaction was processed again * 583 | * * 584 | * Return codes: * 585 | * 0 - everything is clean - this is a new transaction * 586 | * 1 - the stored transaction was re-played * 587 | * * 588 | ************************************************************/ 589 | int TR_check(SQ_connection_t *sql_connection, long transaction_id, int sockfd) 590 | { 591 | Transaction_t * tr; 592 | 593 | 594 | /* transaction_id == 0 means that only one record is maintained */ 595 | /* therefore it is not possible to replay the transaction */ 596 | /* and transaction_id does not uniquely identify the transaction */ 597 | /* suitable for NRTM and for backwards compatibility */ 598 | if(transaction_id <=0) return(0); 599 | /* Get the transaction record */ 600 | /* XXX for NRTM we may specify transaction_id = 0 ? */ 601 | if ((tr = TR_get_record(sql_connection, transaction_id)) == NULL) return(0); /* everything is clean */ 602 | 603 | /* Check if the record is clean (it should be ) */ 604 | /* that means that either the transaction finished normally */ 605 | /* or crash recovery procedure cleaned up the database (and record as well ) */ 606 | if (TS_CLEAN(tr->action)) { 607 | /* send an acknowledgement */ 608 | /* XXX Wait for ack */ 609 | /* XXX if ack is timed out just return, else delete the tr_record */ 610 | /* if(UD_ack(tr)==0) TR_delete_record(tr); */ 611 | 612 | /* Send an acknowledgement, append note that transaction was rerun */ 613 | tr->socket=sockfd; 614 | g_string_sprintfa(tr->error_script,"I[%ld]: requested transaction was processed before\n", transaction_id); 615 | UD_ack(tr); 616 | ER_inf_va(FAC_UD, ASP_UD_UPDLOG, "%s requested transaction was processed before [%d]\n", UD_TAG, transaction_id); 617 | transaction_free(tr); 618 | } 619 | else { 620 | ER_perror(FAC_UD, UD_SQL, "TR is not clean\n"); 621 | die; /* the record should be clean */ 622 | } 623 | return(1); 624 | } 625 | 626 |