Drizzled Public API Documentation

row0purge.cc

00001 /*****************************************************************************
00002 
00003 Copyright (C) 1997, 2010, Innobase Oy. All Rights Reserved.
00004 
00005 This program is free software; you can redistribute it and/or modify it under
00006 the terms of the GNU General Public License as published by the Free Software
00007 Foundation; version 2 of the License.
00008 
00009 This program is distributed in the hope that it will be useful, but WITHOUT
00010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00011 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
00012 
00013 You should have received a copy of the GNU General Public License along with
00014 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
00015 St, Fifth Floor, Boston, MA 02110-1301 USA
00016 
00017 *****************************************************************************/
00018 
00019 /**************************************************/
00026 #include "row0purge.h"
00027 
00028 #ifdef UNIV_NONINL
00029 #include "row0purge.ic"
00030 #endif
00031 
00032 #include "fsp0fsp.h"
00033 #include "mach0data.h"
00034 #include "trx0rseg.h"
00035 #include "trx0trx.h"
00036 #include "trx0roll.h"
00037 #include "trx0undo.h"
00038 #include "trx0purge.h"
00039 #include "trx0rec.h"
00040 #include "que0que.h"
00041 #include "row0row.h"
00042 #include "row0upd.h"
00043 #include "row0vers.h"
00044 #include "row0mysql.h"
00045 #include "log0log.h"
00046 
00047 /*************************************************************************
00048 IMPORTANT NOTE: Any operation that generates redo MUST check that there
00049 is enough space in the redo log before for that operation. This is
00050 done by calling log_free_check(). The reason for checking the
00051 availability of the redo log space before the start of the operation is
00052 that we MUST not hold any synchonization objects when performing the
00053 check.
00054 If you make a change in this module make sure that no codepath is
00055 introduced where a call to log_free_check() is bypassed. */
00056 
00057 /*************************************************************************
00058 IMPORTANT NOTE: Any operation that generates redo MUST check that there
00059 is enough space in the redo log before for that operation. This is
00060 done by calling log_free_check(). The reason for checking the
00061 availability of the redo log space before the start of the operation is
00062 that we MUST not hold any synchonization objects when performing the
00063 check.
00064 If you make a change in this module make sure that no codepath is
00065 introduced where a call to log_free_check() is bypassed. */
00066 
00067 /********************************************************************/
00070 UNIV_INTERN
00071 purge_node_t*
00072 row_purge_node_create(
00073 /*==================*/
00074   que_thr_t*  parent, 
00075   mem_heap_t* heap) 
00076 {
00077   purge_node_t* node;
00078 
00079   ut_ad(parent && heap);
00080 
00081   node = static_cast<purge_node_t *>(mem_heap_alloc(heap, sizeof(purge_node_t)));
00082 
00083   node->common.type = QUE_NODE_PURGE;
00084   node->common.parent = parent;
00085 
00086   node->heap = mem_heap_create(256);
00087 
00088   return(node);
00089 }
00090 
00091 /***********************************************************/
00095 static
00096 ibool
00097 row_purge_reposition_pcur(
00098 /*======================*/
00099   ulint   mode, 
00100   purge_node_t* node, 
00101   mtr_t*    mtr)  
00102 {
00103   ibool found;
00104 
00105   if (node->found_clust) {
00106     found = btr_pcur_restore_position(mode, &(node->pcur), mtr);
00107 
00108     return(found);
00109   }
00110 
00111   found = row_search_on_row_ref(&(node->pcur), mode, node->table,
00112               node->ref, mtr);
00113   node->found_clust = found;
00114 
00115   if (found) {
00116     btr_pcur_store_position(&(node->pcur), mtr);
00117   }
00118 
00119   return(found);
00120 }
00121 
00122 /***********************************************************/
00126 static
00127 ibool
00128 row_purge_remove_clust_if_poss_low(
00129 /*===============================*/
00130   purge_node_t* node, 
00131   ulint   mode) 
00132 {
00133   dict_index_t* index;
00134   btr_pcur_t* pcur;
00135   btr_cur_t*  btr_cur;
00136   ibool   success;
00137   ulint   err;
00138   mtr_t   mtr;
00139   rec_t*    rec;
00140   mem_heap_t* heap    = NULL;
00141   ulint   offsets_[REC_OFFS_NORMAL_SIZE];
00142   rec_offs_init(offsets_);
00143 
00144   index = dict_table_get_first_index(node->table);
00145 
00146   pcur = &(node->pcur);
00147   btr_cur = btr_pcur_get_btr_cur(pcur);
00148 
00149   log_free_check();
00150   mtr_start(&mtr);
00151 
00152   success = row_purge_reposition_pcur(mode, node, &mtr);
00153 
00154   if (!success) {
00155     /* The record is already removed */
00156 
00157     btr_pcur_commit_specify_mtr(pcur, &mtr);
00158 
00159     return(TRUE);
00160   }
00161 
00162   rec = btr_pcur_get_rec(pcur);
00163 
00164   if (node->roll_ptr != row_get_rec_roll_ptr(
00165         rec, index, rec_get_offsets(rec, index, offsets_,
00166             ULINT_UNDEFINED, &heap))) {
00167     if (UNIV_LIKELY_NULL(heap)) {
00168       mem_heap_free(heap);
00169     }
00170     /* Someone else has modified the record later: do not remove */
00171     btr_pcur_commit_specify_mtr(pcur, &mtr);
00172 
00173     return(TRUE);
00174   }
00175 
00176   if (UNIV_LIKELY_NULL(heap)) {
00177     mem_heap_free(heap);
00178   }
00179 
00180   if (mode == BTR_MODIFY_LEAF) {
00181     success = btr_cur_optimistic_delete(btr_cur, &mtr);
00182   } else {
00183     ut_ad(mode == BTR_MODIFY_TREE);
00184     btr_cur_pessimistic_delete(&err, FALSE, btr_cur,
00185              RB_NONE, &mtr);
00186 
00187     if (err == DB_SUCCESS) {
00188       success = TRUE;
00189     } else if (err == DB_OUT_OF_FILE_SPACE) {
00190       success = FALSE;
00191     } else {
00192       ut_error;
00193     }
00194   }
00195 
00196   btr_pcur_commit_specify_mtr(pcur, &mtr);
00197 
00198   return(success);
00199 }
00200 
00201 /***********************************************************/
00204 static
00205 void
00206 row_purge_remove_clust_if_poss(
00207 /*===========================*/
00208   purge_node_t* node) 
00209 {
00210   ibool success;
00211   ulint n_tries = 0;
00212 
00213   /*  fputs("Purge: Removing clustered record\n", stderr); */
00214 
00215   success = row_purge_remove_clust_if_poss_low(node, BTR_MODIFY_LEAF);
00216   if (success) {
00217 
00218     return;
00219   }
00220 retry:
00221   success = row_purge_remove_clust_if_poss_low(node, BTR_MODIFY_TREE);
00222   /* The delete operation may fail if we have little
00223   file space left: TODO: easiest to crash the database
00224   and restart with more file space */
00225 
00226   if (!success && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) {
00227     n_tries++;
00228 
00229     os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME);
00230 
00231     goto retry;
00232   }
00233 
00234   ut_a(success);
00235 }
00236 
00237 /***********************************************************/
00252 UNIV_INTERN
00253 ibool
00254 row_purge_poss_sec(
00255 /*===============*/
00256   purge_node_t* node, 
00257   dict_index_t* index,  
00258   const dtuple_t* entry)  
00259 {
00260   ibool can_delete;
00261   mtr_t mtr;
00262 
00263   ut_ad(!dict_index_is_clust(index));
00264   mtr_start(&mtr);
00265 
00266   can_delete = !row_purge_reposition_pcur(BTR_SEARCH_LEAF, node, &mtr)
00267     || !row_vers_old_has_index_entry(TRUE,
00268              btr_pcur_get_rec(&node->pcur),
00269              &mtr, index, entry);
00270 
00271   btr_pcur_commit_specify_mtr(&node->pcur, &mtr);
00272 
00273   return(can_delete);
00274 }
00275 
00276 /***************************************************************
00277 Removes a secondary index entry if possible, by modifying the
00278 index tree.  Does not try to buffer the delete.
00279 @return TRUE if success or if not found */
00280 static
00281 ibool
00282 row_purge_remove_sec_if_poss_tree(
00283 /*==============================*/
00284   purge_node_t* node, 
00285   dict_index_t* index,  
00286   const dtuple_t* entry)  
00287 {
00288   btr_pcur_t    pcur;
00289   btr_cur_t*    btr_cur;
00290   ibool     success = TRUE;
00291   ulint     err;
00292   mtr_t     mtr;
00293   enum row_search_result  search_result;
00294 
00295   log_free_check();
00296   mtr_start(&mtr);
00297 
00298   search_result = row_search_index_entry(index, entry, BTR_MODIFY_TREE,
00299                  &pcur, &mtr);
00300 
00301   switch (search_result) {
00302   case ROW_NOT_FOUND:
00303     /* Not found.  This is a legitimate condition.  In a
00304     rollback, InnoDB will remove secondary recs that would
00305     be purged anyway.  Then the actual purge will not find
00306     the secondary index record.  Also, the purge itself is
00307     eager: if it comes to consider a secondary index
00308     record, and notices it does not need to exist in the
00309     index, it will remove it.  Then if/when the purge
00310     comes to consider the secondary index record a second
00311     time, it will not exist any more in the index. */
00312 
00313     /* fputs("PURGE:........sec entry not found\n", stderr); */
00314     /* dtuple_print(stderr, entry); */
00315     goto func_exit;
00316   case ROW_FOUND:
00317     break;
00318   case ROW_BUFFERED:
00319   case ROW_NOT_DELETED_REF:
00320     /* These are invalid outcomes, because the mode passed
00321     to row_search_index_entry() did not include any of the
00322     flags BTR_INSERT, BTR_DELETE, or BTR_DELETE_MARK. */
00323     ut_error;
00324   }
00325 
00326   btr_cur = btr_pcur_get_btr_cur(&pcur);
00327 
00328   /* We should remove the index record if no later version of the row,
00329   which cannot be purged yet, requires its existence. If some requires,
00330   we should do nothing. */
00331 
00332   if (row_purge_poss_sec(node, index, entry)) {
00333     /* Remove the index record, which should have been
00334     marked for deletion. */
00335     ut_ad(REC_INFO_DELETED_FLAG
00336           & rec_get_info_bits(btr_cur_get_rec(btr_cur),
00337             dict_table_is_comp(index->table)));
00338 
00339     btr_cur_pessimistic_delete(&err, FALSE, btr_cur,
00340              RB_NONE, &mtr);
00341     switch (UNIV_EXPECT(err, DB_SUCCESS)) {
00342     case DB_SUCCESS:
00343       break;
00344     case DB_OUT_OF_FILE_SPACE:
00345       success = FALSE;
00346       break;
00347     default:
00348       ut_error;
00349     }
00350   }
00351 
00352 func_exit:
00353   btr_pcur_close(&pcur);
00354   mtr_commit(&mtr);
00355 
00356   return(success);
00357 }
00358 
00359 /***************************************************************
00360 Removes a secondary index entry without modifying the index tree,
00361 if possible.
00362 @return TRUE if success or if not found */
00363 static
00364 ibool
00365 row_purge_remove_sec_if_poss_leaf(
00366 /*==============================*/
00367   purge_node_t* node, 
00368   dict_index_t* index,  
00369   const dtuple_t* entry)  
00370 {
00371   mtr_t     mtr;
00372   btr_pcur_t    pcur;
00373   enum row_search_result  search_result;
00374 
00375   log_free_check();
00376 
00377   mtr_start(&mtr);
00378 
00379   /* Set the purge node for the call to row_purge_poss_sec(). */
00380   pcur.btr_cur.purge_node = node;
00381   /* Set the query thread, so that ibuf_insert_low() will be
00382   able to invoke thd_get_trx(). */
00383   pcur.btr_cur.thr = static_cast<que_thr_t *>(que_node_get_parent(node));
00384 
00385   search_result = row_search_index_entry(
00386     index, entry, BTR_MODIFY_LEAF | BTR_DELETE, &pcur, &mtr);
00387 
00388   switch (search_result) {
00389     ibool success;
00390   case ROW_FOUND:
00391     /* Before attempting to purge a record, check
00392     if it is safe to do so. */
00393     if (row_purge_poss_sec(node, index, entry)) {
00394       btr_cur_t* btr_cur = btr_pcur_get_btr_cur(&pcur);
00395 
00396       /* Only delete-marked records should be purged. */
00397       ut_ad(REC_INFO_DELETED_FLAG
00398             & rec_get_info_bits(
00399               btr_cur_get_rec(btr_cur),
00400               dict_table_is_comp(index->table)));
00401 
00402       if (!btr_cur_optimistic_delete(btr_cur, &mtr)) {
00403 
00404         /* The index entry could not be deleted. */
00405         success = FALSE;
00406         goto func_exit;
00407       }
00408     }
00409     /* fall through (the index entry is still needed,
00410     or the deletion succeeded) */
00411   case ROW_NOT_DELETED_REF:
00412     /* The index entry is still needed. */
00413   case ROW_BUFFERED:
00414     /* The deletion was buffered. */
00415   case ROW_NOT_FOUND:
00416     /* The index entry does not exist, nothing to do. */
00417     success = TRUE;
00418   func_exit:
00419     btr_pcur_close(&pcur);
00420     mtr_commit(&mtr);
00421     return(success);
00422   }
00423 
00424   ut_error;
00425   return(FALSE);
00426 }
00427 
00428 /***********************************************************/
00430 UNIV_INLINE
00431 void
00432 row_purge_remove_sec_if_poss(
00433 /*=========================*/
00434   purge_node_t* node, 
00435   dict_index_t* index,  
00436   dtuple_t* entry)  
00437 {
00438   ibool success;
00439   ulint n_tries   = 0;
00440 
00441   /*  fputs("Purge: Removing secondary record\n", stderr); */
00442 
00443   if (row_purge_remove_sec_if_poss_leaf(node, index, entry)) {
00444 
00445     return;
00446   }
00447 retry:
00448   success = row_purge_remove_sec_if_poss_tree(node, index, entry);
00449   /* The delete operation may fail if we have little
00450   file space left: TODO: easiest to crash the database
00451   and restart with more file space */
00452 
00453   if (!success && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) {
00454 
00455     n_tries++;
00456 
00457     os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME);
00458 
00459     goto retry;
00460   }
00461 
00462   ut_a(success);
00463 }
00464 
00465 /***********************************************************/
00467 static
00468 void
00469 row_purge_del_mark(
00470 /*===============*/
00471   purge_node_t* node) 
00472 {
00473   mem_heap_t* heap;
00474   dtuple_t* entry;
00475   dict_index_t* index;
00476 
00477   ut_ad(node);
00478 
00479   heap = mem_heap_create(1024);
00480 
00481   while (node->index != NULL) {
00482     index = node->index;
00483 
00484     /* Build the index entry */
00485     entry = row_build_index_entry(node->row, NULL, index, heap);
00486     ut_a(entry);
00487     row_purge_remove_sec_if_poss(node, index, entry);
00488 
00489     node->index = dict_table_get_next_index(node->index);
00490   }
00491 
00492   mem_heap_free(heap);
00493 
00494   row_purge_remove_clust_if_poss(node);
00495 }
00496 
00497 /***********************************************************/
00500 static
00501 void
00502 row_purge_upd_exist_or_extern(
00503 /*==========================*/
00504   purge_node_t* node) 
00505 {
00506   mem_heap_t* heap;
00507   dtuple_t* entry;
00508   dict_index_t* index;
00509   ibool   is_insert;
00510   ulint   rseg_id;
00511   ulint   page_no;
00512   ulint   offset;
00513   ulint   i;
00514   mtr_t   mtr;
00515 
00516   ut_ad(node);
00517 
00518   if (node->rec_type == TRX_UNDO_UPD_DEL_REC) {
00519 
00520     goto skip_secondaries;
00521   }
00522 
00523   heap = mem_heap_create(1024);
00524 
00525   while (node->index != NULL) {
00526     index = node->index;
00527 
00528     if (row_upd_changes_ord_field_binary(NULL, node->index,
00529                  node->update)) {
00530       /* Build the older version of the index entry */
00531       entry = row_build_index_entry(node->row, NULL,
00532                   index, heap);
00533       ut_a(entry);
00534       row_purge_remove_sec_if_poss(node, index, entry);
00535     }
00536 
00537     node->index = dict_table_get_next_index(node->index);
00538   }
00539 
00540   mem_heap_free(heap);
00541 
00542 skip_secondaries:
00543   /* Free possible externally stored fields */
00544   for (i = 0; i < upd_get_n_fields(node->update); i++) {
00545 
00546     const upd_field_t*  ufield
00547       = upd_get_nth_field(node->update, i);
00548 
00549     if (dfield_is_ext(&ufield->new_val)) {
00550       buf_block_t*  block;
00551       ulint   internal_offset;
00552       byte*   data_field;
00553 
00554       /* We use the fact that new_val points to
00555       node->undo_rec and get thus the offset of
00556       dfield data inside the undo record. Then we
00557       can calculate from node->roll_ptr the file
00558       address of the new_val data */
00559 
00560       internal_offset
00561         = ((const byte*)
00562            dfield_get_data(&ufield->new_val))
00563         - node->undo_rec;
00564 
00565       ut_a(internal_offset < UNIV_PAGE_SIZE);
00566 
00567       trx_undo_decode_roll_ptr(node->roll_ptr,
00568              &is_insert, &rseg_id,
00569              &page_no, &offset);
00570       mtr_start(&mtr);
00571 
00572       /* We have to acquire an X-latch to the clustered
00573       index tree */
00574 
00575       index = dict_table_get_first_index(node->table);
00576 
00577       mtr_x_lock(dict_index_get_lock(index), &mtr);
00578 
00579       /* NOTE: we must also acquire an X-latch to the
00580       root page of the tree. We will need it when we
00581       free pages from the tree. If the tree is of height 1,
00582       the tree X-latch does NOT protect the root page,
00583       because it is also a leaf page. Since we will have a
00584       latch on an undo log page, we would break the
00585       latching order if we would only later latch the
00586       root page of such a tree! */
00587 
00588       btr_root_get(index, &mtr);
00589 
00590       /* We assume in purge of externally stored fields
00591       that the space id of the undo log record is 0! */
00592 
00593       block = buf_page_get(0, 0, page_no, RW_X_LATCH, &mtr);
00594       buf_block_dbg_add_level(block, SYNC_TRX_UNDO_PAGE);
00595 
00596       data_field = buf_block_get_frame(block)
00597         + offset + internal_offset;
00598 
00599       ut_a(dfield_get_len(&ufield->new_val)
00600            >= BTR_EXTERN_FIELD_REF_SIZE);
00601       btr_free_externally_stored_field(
00602         index,
00603         data_field + dfield_get_len(&ufield->new_val)
00604         - BTR_EXTERN_FIELD_REF_SIZE,
00605         NULL, NULL, NULL, 0, RB_NONE, &mtr);
00606       mtr_commit(&mtr);
00607     }
00608   }
00609 }
00610 
00611 /***********************************************************/
00615 static
00616 ibool
00617 row_purge_parse_undo_rec(
00618 /*=====================*/
00619   purge_node_t* node, 
00620   ibool*    updated_extern,
00623   que_thr_t*  thr)  
00624 {
00625   dict_index_t* clust_index;
00626   byte*   ptr;
00627   trx_t*    trx;
00628   undo_no_t undo_no;
00629   table_id_t  table_id;
00630   trx_id_t  trx_id;
00631   roll_ptr_t  roll_ptr;
00632   ulint   info_bits;
00633   ulint   type;
00634   ulint   cmpl_info;
00635 
00636   ut_ad(node && thr);
00637 
00638   trx = thr_get_trx(thr);
00639 
00640   ptr = trx_undo_rec_get_pars(node->undo_rec, &type, &cmpl_info,
00641             updated_extern, &undo_no, &table_id);
00642   node->rec_type = type;
00643 
00644   if (type == TRX_UNDO_UPD_DEL_REC && !(*updated_extern)) {
00645 
00646     return(FALSE);
00647   }
00648 
00649   ptr = trx_undo_update_rec_get_sys_cols(ptr, &trx_id, &roll_ptr,
00650                  &info_bits);
00651   node->table = NULL;
00652 
00653   if (type == TRX_UNDO_UPD_EXIST_REC
00654       && cmpl_info & UPD_NODE_NO_ORD_CHANGE && !(*updated_extern)) {
00655 
00656     /* Purge requires no changes to indexes: we may return */
00657 
00658     return(FALSE);
00659   }
00660 
00661   /* Prevent DROP TABLE etc. from running when we are doing the purge
00662   for this row */
00663 
00664   row_mysql_freeze_data_dictionary(trx);
00665 
00666   mutex_enter(&(dict_sys->mutex));
00667 
00668   node->table = dict_table_get_on_id_low(table_id);
00669 
00670   mutex_exit(&(dict_sys->mutex));
00671 
00672   if (node->table == NULL) {
00673     /* The table has been dropped: no need to do purge */
00674 err_exit:
00675     row_mysql_unfreeze_data_dictionary(trx);
00676     return(FALSE);
00677   }
00678 
00679   if (node->table->ibd_file_missing) {
00680     /* We skip purge of missing .ibd files */
00681 
00682     node->table = NULL;
00683 
00684     goto err_exit;
00685   }
00686 
00687   clust_index = dict_table_get_first_index(node->table);
00688 
00689   if (clust_index == NULL) {
00690     /* The table was corrupt in the data dictionary */
00691 
00692     goto err_exit;
00693   }
00694 
00695   ptr = trx_undo_rec_get_row_ref(ptr, clust_index, &(node->ref),
00696                node->heap);
00697 
00698   ptr = trx_undo_update_rec_get_update(ptr, clust_index, type, trx_id,
00699                roll_ptr, info_bits, trx,
00700                node->heap, &(node->update));
00701 
00702   /* Read to the partial row the fields that occur in indexes */
00703 
00704   if (!(cmpl_info & UPD_NODE_NO_ORD_CHANGE)) {
00705     ptr = trx_undo_rec_get_partial_row(
00706       ptr, clust_index, &node->row,
00707       type == TRX_UNDO_UPD_DEL_REC,
00708       node->heap);
00709   }
00710 
00711   return(TRUE);
00712 }
00713 
00714 /***********************************************************/
00719 static
00720 ulint
00721 row_purge(
00722 /*======*/
00723   purge_node_t* node, 
00724   que_thr_t*  thr)  
00725 {
00726   roll_ptr_t  roll_ptr;
00727   ibool   purge_needed;
00728   ibool   updated_extern;
00729   trx_t*    trx;
00730 
00731   ut_ad(node && thr);
00732 
00733   trx = thr_get_trx(thr);
00734 
00735   node->undo_rec = trx_purge_fetch_next_rec(&roll_ptr,
00736               &(node->reservation),
00737               node->heap);
00738   if (!node->undo_rec) {
00739     /* Purge completed for this query thread */
00740 
00741     thr->run_node = que_node_get_parent(node);
00742 
00743     return(DB_SUCCESS);
00744   }
00745 
00746   node->roll_ptr = roll_ptr;
00747 
00748   if (node->undo_rec == &trx_purge_dummy_rec) {
00749     purge_needed = FALSE;
00750   } else {
00751     purge_needed = row_purge_parse_undo_rec(node, &updated_extern,
00752               thr);
00753     /* If purge_needed == TRUE, we must also remember to unfreeze
00754     data dictionary! */
00755   }
00756 
00757   if (purge_needed) {
00758     node->found_clust = FALSE;
00759 
00760     node->index = dict_table_get_next_index(
00761       dict_table_get_first_index(node->table));
00762 
00763     if (node->rec_type == TRX_UNDO_DEL_MARK_REC) {
00764       row_purge_del_mark(node);
00765 
00766     } else if (updated_extern
00767          || node->rec_type == TRX_UNDO_UPD_EXIST_REC) {
00768 
00769       row_purge_upd_exist_or_extern(node);
00770     }
00771 
00772     if (node->found_clust) {
00773       btr_pcur_close(&(node->pcur));
00774     }
00775 
00776     row_mysql_unfreeze_data_dictionary(trx);
00777   }
00778 
00779   /* Do some cleanup */
00780   trx_purge_rec_release(node->reservation);
00781   mem_heap_empty(node->heap);
00782 
00783   thr->run_node = node;
00784 
00785   return(DB_SUCCESS);
00786 }
00787 
00788 /***********************************************************/
00792 UNIV_INTERN
00793 que_thr_t*
00794 row_purge_step(
00795 /*===========*/
00796   que_thr_t*  thr)  
00797 {
00798   purge_node_t* node;
00799 #ifdef UNIV_DEBUG
00800   ulint   err;
00801 #endif /* UNIV_DEBUG */
00802 
00803   ut_ad(thr);
00804 
00805   node = static_cast<purge_node_t *>(thr->run_node);
00806 
00807   ut_ad(que_node_get_type(node) == QUE_NODE_PURGE);
00808 
00809 #ifdef UNIV_DEBUG
00810   err =
00811 #endif /* UNIV_DEBUG */
00812   row_purge(node, thr);
00813 
00814 #ifdef UNIV_DEBUG
00815   ut_a(err == DB_SUCCESS);
00816 #endif /* UNIV_DEBUG */
00817 
00818   return(thr);
00819 }