1 | /***************************************
2 | $Revision: 1.4 $
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) {
339 | tr = NULL;
340 | }
341 | else {
342 | /* Check if there is more than one row */
343 | res = 0;
344 | while(SQ_row_next(result))res = -1;
345 | if(res == -1) die;
346 |
347 |
348 | class_type = tr_get_class_type(result, row);
349 | if ((tr = transaction_new(sql_connection, class_type)) == NULL) die;
350 | tr->object_id = tr_get_object_id(result, row);
351 |
352 | /* Fill in all dummies that were created */
353 | tr_get_dummies(tr);
354 |
355 | tr->sequence_id = tr_get_sequence_id(result, row);
356 | tr->serial_id = tr_get_serial_id(result, row);
357 | tr->save = g_strdup(tr_get_save(result, row));
358 | g_string_sprintf(tr->error_script, tr_get_escript(result, row));
359 |
360 |
361 | /* mode of operation */
362 | tr->mode = tr_get_mode(result, row);
363 | /* indication of success */
364 | tr->succeeded = tr_get_success(result, row);
365 | /* action is low byte */
366 | tr->action = tr_get_action(result, row);
367 | /* status is high byte */
368 | tr->action |= (tr_get_status(result, row) <<8);
369 | tr->action |= (tr_get_clean(result, row) << 8); /* bit0 bears this flag */
370 | }
371 |
372 | SQ_free_result(result);
373 | return(tr);
374 | }
375 |
376 | /************************************************************
377 | * int TR_delete_record() *
378 | * *
379 | * Deletes all associated sql records *
380 | * *
381 | * *
382 | ************************************************************/
383 | void TR_delete_record(Transaction_t *tr)
384 | {
385 | GString *query;
386 | int sql_err;
387 |
388 | if(tr->load_pass != 0) return; /* for fast loader just return */
389 |
390 | /* Delete a record from SQL DB */
391 | if ((query = g_string_sized_new(STR_L)) == NULL){
392 | ER_perror(FAC_UD, UD_MEM, "cannot allocate gstring\n");
393 | die;
394 | }
395 |
396 | /* compose query */
397 | g_string_sprintf(query, "DELETE FROM dummy_rec WHERE transaction_id=%ld", tr->transaction_id);
398 | sql_err=SQ_execute_query(tr->sql_connection, query->str, NULL);
399 | /* in case of an error copy error code and return */
400 | if(sql_err) {
401 | ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str);
402 | die;
403 | }
404 | g_string_sprintf(query, "DELETE FROM transaction_rec WHERE transaction_id=%ld", tr->transaction_id);
405 | sql_err=SQ_execute_query(tr->sql_connection, query->str, NULL);
406 | /* in case of an error copy error code and return */
407 | if(sql_err) {
408 | ER_perror(FAC_UD, UD_SQL, "%s[%s]\n", SQ_error(tr->sql_connection), query->str);
409 | die;
410 | }
411 |
412 | g_string_free(query, TRUE);
413 |
414 | }
415 |
416 |
417 | /************************************************************
418 | * int TR_recover() *
419 | * *
420 | * Cleans up the database after RIP daemon failure *
421 | * *
422 | * Return codes: *
423 | * *
424 | * 0 - everything is clean, no cleanup is needed *
425 | * 1 - the database was recovered successfully *
426 | * *
427 | ************************************************************/
428 | int TR_recover(SQ_connection_t *sql_connection)
429 | {
430 | int res;
431 | Transaction_t * tr;
432 |
433 | /* XXX SQ_db_name() ? */
434 | fprintf(stderr, "Checking the Database [%s]...", sql_connection->db);
435 |
436 | /* Get the transaction record */
437 | /* XXX for NRTM we may specify transaction_id = 0 ? */
438 | if ((tr = TR_get_record(sql_connection, TR_LAST)) == NULL) {
439 | /* everything is clean */
440 | res = 0;
441 | fprintf(stderr, "[OK]\n");
442 | }
443 | else {/* Not everything was perfect :( */
444 | fprintf(stderr, "[FAILED]\n"
445 | "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"
446 | "+ LAST TRANSACTION IS INCOMPLETE. ENTERING CRASH RECOVERY MODE +\n"
447 | "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
448 | /* Failure occured before the ack was sent */
449 | /* Roll back the transaction */
450 | /* Delete transaction record (TR) as if it never happened */
451 | /************************* R O L L B A C K ***************************/
452 | if(TS_ROLLBACK(tr->action)) {
453 | fprintf(stderr, " STATUS: Rollback\n");
454 |
455 | /* don't rollback the transaction if we were to delete the object, but could not */
456 | if(!TS_ROLLBACKED(tr->action)){
457 | fprintf(stderr, " STATUS: Rollback incomplete, completing...");
458 | UD_rollback(tr);
459 | CP_ROLLBACK_PASSED(tr->action); TR_update_status(tr);
460 | fprintf(stderr, "[OK]\n");
461 | } else fprintf(stderr, " STATUS: Rollback complete [PASSED]\n");
462 |
463 |
464 | if(!TS_ROLLBACKED_NH(tr->action)){
465 | fprintf(stderr, " STATUS: NH rollback incomplete, completing...");
466 | NH_rollback(tr->sql_connection);
467 | CP_ROLLBACK_NH_PASSED(tr->action); TR_update_status(tr);
468 | fprintf(stderr, "[OK]\n");
469 | } else fprintf(stderr, " STATUS: NH rollback complete [PASSED]\n");
470 | /* In update mode delete TR record. Next time (if any) DBupdate tries to submit, we'll start from scratch */
471 | /* In NRTM mode we create a serial record even in case of failure (tr->succeeded ==0)*/
472 | /* So in NRTM we need to clean up serials/transaction as well */
473 | if(IS_UPDATE(tr->mode)){
474 | fprintf(stderr, " STATUS: Serial does not need to be restored, deleting TR...");
475 | TR_delete_record(tr);
476 | fprintf(stderr, "[OK]\n");
477 | } else {
478 | fprintf(stderr, " STATUS: Cleaning serial, deleting TR...");
479 | if(!TS_CREATED_S(tr->action))
480 | UD_rollback_serial(tr);
481 | else
482 | UD_commit_serial(tr);
483 | TR_delete_record(tr);
484 | fprintf(stderr, "[OK]\n");
485 | }
486 |
487 | res = 1;
488 | }
489 | /************************* C O M M I T ******************************/
490 | else { /* commit */
491 | /* The ack was sent */
492 | /* Complete the commit */
493 | fprintf(stderr, " STATUS: Commit\n");
494 | /* We keep the transaction record in case DBupdate failed */
495 | /* and requests the same transaction after recovery ? */
496 | /* Such approach will allow us to avoid 3-way handshaking with DBupdate */
497 | /* So we never blocked or timed out during that phase */
498 |
499 | /* XXX But first I implemented another approach (to keep DB tiny/tidy): */
500 | /* 1. Process the transaction */
501 | /* 2. In case of failure - rollback - NACK */
502 | /* 3. Before commit - ACK (UD_ack()) */
503 | /* 4. If UD_ack returns an error preserve a tr_record */
504 | /* 5. Commit */
505 | /* 6. If still alive and UD_ack passed - delete the record - all is clean */
506 | /* Otherwise preserve a tr_record */
507 |
508 | if(ACT_DELETE(tr->action)) {
509 | /* check if we passed deletion process */
510 | if(!TS_DELETED(tr->action)){
511 | fprintf(stderr, " STATUS: Delete incomplete, completing...");
512 | UD_delete(tr);
513 | CP_DELETE_PASSED(tr->action); TR_update_status(tr);
514 | fprintf(stderr, "[OK]\n");
515 | } else fprintf(stderr, " STATUS: Delete complete [PASSED]\n");
516 | }
517 | else { /* update or create */
518 | /* Check if we passed the deletion pass of commit */
519 | if(!TS_COMMITTED_I(tr->action)){
520 | fprintf(stderr, " STATUS: Commit phase I incomplete, completing...");
521 | UD_commit_I(tr);
522 | CP_COMMIT_I_PASSED(tr->action); TR_update_status(tr);
523 | fprintf(stderr, "[OK]\n");
524 | } else fprintf(stderr, " STATUS: Commit phase I complete [PASSED]\n");
525 | /* Check if we passed the second pass of commit */
526 | if(!TS_COMMITTED_II(tr->action)){
527 | fprintf(stderr, " STATUS: Commit phase II incomplete, completing...");
528 | UD_commit_II(tr);
529 | CP_COMMIT_II_PASSED(tr->action); TR_update_status(tr);
530 | fprintf(stderr, "[OK]\n");
531 | } else fprintf(stderr, " STATUS: Commit phase II complete [PASSED]\n");
532 | } /* end of delete, create, update specific operations */
533 |
534 | /* Check if we passed the NH repository commit */
535 | if(!TS_COMMITTED_NH(tr->action)){
536 | fprintf(stderr, " STATUS: NH commit incomplete, completing...");
537 | NH_commit(tr->sql_connection);
538 | CP_COMMIT_NH_PASSED(tr->action); TR_update_status(tr);
539 | fprintf(stderr, "[OK]\n");
540 | } else fprintf(stderr, " STATUS: NH commit complete [PASSED]\n");
541 |
542 |
543 | /* create serial file */
544 | if(!TS_CREATED_S(tr->action))
545 | {
546 | fprintf(stderr, " STATUS: Serial rollback and restore...");
547 | UD_rollback_serial(tr);
548 | if(ACT_UPD_CLLPS(tr->action)) { /* this is a collapsed update (DEL + ADD) */
549 | tr->action=TA_DELETE; UD_create_serial(tr);
550 | tr->sequence_id++;
551 | tr->action=TA_CREATE; UD_create_serial(tr);
552 | }else if(ACT_UPD_DUMMY(tr->action)) { /* this was a dummy update - we need only CREATE serial */
553 | tr->action=TA_CREATE;
554 | tr->sequence_id++; /* because in fact this is an update (sequence_id=2) */
555 | UD_create_serial(tr);
556 | } else UD_create_serial(tr);
557 | CP_CREATE_S_PASSED(tr->action); TR_update_status(tr);
558 | }
559 | fprintf(stderr, "[OK]\n");
560 | UD_commit_serial(tr);
561 |
562 | fprintf(stderr, " STATUS: Marking TR as clean...");
563 | TR_mark_clean(tr);
564 |
565 | fprintf(stderr, "[OK]\n");
566 |
567 | res = 2;
568 | }
569 | }
570 | transaction_free(tr);
571 | fprintf(stderr, " STATUS: The Database is clean \n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\n");
572 |
573 | return(res);
574 | }
575 |
576 | /************************************************************
577 | * int TR_check() *
578 | * *
579 | * Checks if the requested transaction has already been *
580 | * processed. This could happen when DBupdate crashes while *
581 | * RIPupdate successfully completes the transaction. *
582 | * *
583 | * If this is the case, RIPupdate will return an ack to *
584 | * DBupdate as if the transaction was processed again *
585 | * *
586 | * Return codes: *
587 | * 0 - everything is clean - this is a new transaction *
588 | * 1 - the stored transaction was re-played *
589 | * *
590 | ************************************************************/
591 | int TR_check(SQ_connection_t *sql_connection, long transaction_id, int sockfd)
592 | {
593 | Transaction_t * tr;
594 |
595 |
596 | /* transaction_id == 0 means that only one record is maintained */
597 | /* therefore it is not possible to replay the transaction */
598 | /* and transaction_id does not uniquely identify the transaction */
599 | /* suitable for NRTM and for backwards compatibility */
600 | if(transaction_id <=0) return(0);
601 | /* Get the transaction record */
602 | /* XXX for NRTM we may specify transaction_id = 0 ? */
603 | if ((tr = TR_get_record(sql_connection, transaction_id)) == NULL) return(0); /* everything is clean */
604 |
605 | /* Check if the record is clean (it should be ) */
606 | /* that means that either the transaction finished normally */
607 | /* or crash recovery procedure cleaned up the database (and record as well ) */
608 | if (TS_CLEAN(tr->action)) {
609 | /* send an acknowledgement */
610 | /* XXX Wait for ack */
611 | /* XXX if ack is timed out just return, else delete the tr_record */
612 | /* if(UD_ack(tr)==0) TR_delete_record(tr); */
613 |
614 | /* Send an acknowledgement, append note that transaction was rerun */
615 | tr->socket=sockfd;
616 | g_string_sprintfa(tr->error_script,"I[%ld]: requested transaction was processed before\n", transaction_id);
617 | UD_ack(tr);
618 | ER_inf_va(FAC_UD, ASP_UD_UPDLOG, "[%ld] requested transaction was processed before\n", transaction_id);
619 | transaction_free(tr);
620 | }
621 | else {
622 | ER_perror(FAC_UD, UD_SQL, "TR is not clean\n");
623 | die; /* the record should be clean */
624 | }
625 | return(1);
626 | }
627 |
628 |