1 /***************************************************************************** 2 3 Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved. 4 Copyright (c) 2016, 2021, MariaDB Corporation. 5 6 This program is free software; you can redistribute it and/or modify it under 7 the terms of the GNU General Public License as published by the Free Software 8 Foundation; version 2 of the License. 9 10 This program is distributed in the hope that it will be useful, but WITHOUT 11 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License along with 15 this program; if not, write to the Free Software Foundation, Inc., 16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA 17 18 *****************************************************************************/ 19 20 /**************************************************//** 21 @file btr/btr0pcur.cc 22 The index tree persistent cursor 23 24 Created 2/23/1996 Heikki Tuuri 25 *******************************************************/ 26 27 #include "btr0pcur.h" 28 #include "ut0byte.h" 29 #include "rem0cmp.h" 30 #include "trx0trx.h" 31 32 /**************************************************************//** 33 Allocates memory for a persistent cursor object and initializes the cursor. 34 @return own: persistent cursor */ 35 btr_pcur_t* 36 btr_pcur_create_for_mysql(void) 37 /*============================*/ 38 { 39 btr_pcur_t* pcur; 40 DBUG_ENTER("btr_pcur_create_for_mysql"); 41 42 pcur = (btr_pcur_t*) ut_malloc_nokey(sizeof(btr_pcur_t)); 43 44 pcur->btr_cur.index = NULL; 45 btr_pcur_init(pcur); 46 47 DBUG_PRINT("btr_pcur_create_for_mysql", ("pcur: %p", pcur)); 48 DBUG_RETURN(pcur); 49 } 50 51 /**************************************************************//** 52 Resets a persistent cursor object, freeing ::old_rec_buf if it is 53 allocated and resetting the other members to their initial values. */ 54 void 55 btr_pcur_reset( 56 /*===========*/ 57 btr_pcur_t* cursor) /*!< in, out: persistent cursor */ 58 { 59 btr_pcur_free(cursor); 60 cursor->old_rec_buf = NULL; 61 cursor->btr_cur.index = NULL; 62 cursor->btr_cur.page_cur.rec = NULL; 63 cursor->old_rec = NULL; 64 cursor->old_n_core_fields = 0; 65 cursor->old_n_fields = 0; 66 cursor->old_stored = false; 67 68 cursor->latch_mode = BTR_NO_LATCHES; 69 cursor->pos_state = BTR_PCUR_NOT_POSITIONED; 70 } 71 72 /**************************************************************//** 73 Frees the memory for a persistent cursor object. */ 74 void 75 btr_pcur_free_for_mysql( 76 /*====================*/ 77 btr_pcur_t* cursor) /*!< in, own: persistent cursor */ 78 { 79 DBUG_ENTER("btr_pcur_free_for_mysql"); 80 DBUG_PRINT("btr_pcur_free_for_mysql", ("pcur: %p", cursor)); 81 82 btr_pcur_free(cursor); 83 ut_free(cursor); 84 DBUG_VOID_RETURN; 85 } 86 87 /**************************************************************//** 88 The position of the cursor is stored by taking an initial segment of the 89 record the cursor is positioned on, before, or after, and copying it to the 90 cursor data structure, or just setting a flag if the cursor id before the 91 first in an EMPTY tree, or after the last in an EMPTY tree. NOTE that the 92 page where the cursor is positioned must not be empty if the index tree is 93 not totally empty! */ 94 void 95 btr_pcur_store_position( 96 /*====================*/ 97 btr_pcur_t* cursor, /*!< in: persistent cursor */ 98 mtr_t* mtr) /*!< in: mtr */ 99 { 100 page_cur_t* page_cursor; 101 buf_block_t* block; 102 rec_t* rec; 103 dict_index_t* index; 104 ulint offs; 105 106 ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); 107 ut_ad(cursor->latch_mode != BTR_NO_LATCHES); 108 109 block = btr_pcur_get_block(cursor); 110 index = btr_cur_get_index(btr_pcur_get_btr_cur(cursor)); 111 112 page_cursor = btr_pcur_get_page_cur(cursor); 113 114 rec = page_cur_get_rec(page_cursor); 115 offs = rec - block->frame; 116 ut_ad(block->page.id.page_no() == page_get_page_no(block->frame)); 117 ut_ad(block->page.buf_fix_count); 118 /* For spatial index, when we do positioning on parent 119 buffer if necessary, it might not hold latches, but the 120 tree must be locked to prevent change on the page */ 121 ut_ad(mtr_memo_contains_flagged(mtr, block, 122 MTR_MEMO_PAGE_S_FIX 123 | MTR_MEMO_PAGE_X_FIX) 124 || (dict_index_is_spatial(index) 125 && mtr_memo_contains_flagged( 126 mtr, dict_index_get_lock(index), 127 MTR_MEMO_X_LOCK | MTR_MEMO_SX_LOCK))); 128 129 cursor->old_stored = true; 130 131 if (page_is_empty(block->frame)) { 132 ut_ad(block->page.id.page_no() == index->page); 133 empty_table: 134 /* It must be an empty index tree; NOTE that in this case 135 we do not store the modify_clock, but always do a search 136 if we restore the cursor position */ 137 138 ut_a(!page_has_siblings(block->frame)); 139 ut_ad(page_is_leaf(block->frame)); 140 141 if (page_rec_is_supremum_low(offs)) { 142 cursor->rel_pos = BTR_PCUR_AFTER_LAST_IN_TREE; 143 } else { 144 before_first: 145 cursor->rel_pos = BTR_PCUR_BEFORE_FIRST_IN_TREE; 146 } 147 148 return; 149 } 150 151 if (page_rec_is_supremum_low(offs)) { 152 rec = page_rec_get_prev(rec); 153 154 ut_ad(!page_rec_is_infimum(rec)); 155 156 if (UNIV_UNLIKELY(rec_is_metadata(rec, index))) { 157 /* The table may be empty such that it only 158 contains a metadata record, in a leaf page 159 that is not the root page. */ 160 ut_ad(index->is_primary()); 161 ut_ad(block->page.id.page_no() != index->page); 162 goto empty_table; 163 } 164 165 cursor->rel_pos = BTR_PCUR_AFTER; 166 } else if (page_rec_is_infimum_low(offs)) { 167 rec = page_rec_get_next(rec); 168 169 if (rec_is_metadata(rec, index)) { 170 ut_ad(!page_has_prev(block->frame)); 171 rec = page_rec_get_next(rec); 172 if (page_rec_is_supremum(rec)) { 173 goto before_first; 174 } 175 } 176 177 cursor->rel_pos = BTR_PCUR_BEFORE; 178 } else { 179 cursor->rel_pos = BTR_PCUR_ON; 180 } 181 182 UNIV_PREFETCH_R(rec); 183 184 if (index->is_ibuf()) { 185 cursor->old_n_fields = uint16(rec_get_n_fields_old(rec)); 186 } else { 187 cursor->old_n_fields = static_cast<uint16>( 188 dict_index_get_n_unique_in_tree(index)); 189 if (index->is_spatial() && !page_rec_is_leaf(rec)) { 190 ut_ad(dict_index_get_n_unique_in_tree_nonleaf(index) 191 == DICT_INDEX_SPATIAL_NODEPTR_SIZE); 192 /* For R-tree, we have to compare 193 the child page numbers as well. */ 194 cursor->old_n_fields 195 = DICT_INDEX_SPATIAL_NODEPTR_SIZE + 1; 196 } 197 } 198 199 cursor->old_n_core_fields = index->n_core_fields; 200 cursor->old_rec = rec_copy_prefix_to_buf(rec, index, 201 cursor->old_n_fields, 202 &cursor->old_rec_buf, 203 &cursor->buf_size); 204 205 cursor->block_when_stored.store(block); 206 207 /* Function try to check if block is S/X latch. */ 208 cursor->modify_clock = buf_block_get_modify_clock(block); 209 } 210 211 /**************************************************************//** 212 Copies the stored position of a pcur to another pcur. */ 213 void 214 btr_pcur_copy_stored_position( 215 /*==========================*/ 216 btr_pcur_t* pcur_receive, /*!< in: pcur which will receive the 217 position info */ 218 btr_pcur_t* pcur_donate) /*!< in: pcur from which the info is 219 copied */ 220 { 221 ut_free(pcur_receive->old_rec_buf); 222 ut_memcpy(pcur_receive, pcur_donate, sizeof(btr_pcur_t)); 223 224 if (pcur_donate->old_rec_buf) { 225 226 pcur_receive->old_rec_buf = (byte*) 227 ut_malloc_nokey(pcur_donate->buf_size); 228 229 ut_memcpy(pcur_receive->old_rec_buf, pcur_donate->old_rec_buf, 230 pcur_donate->buf_size); 231 pcur_receive->old_rec = pcur_receive->old_rec_buf 232 + (pcur_donate->old_rec - pcur_donate->old_rec_buf); 233 } 234 235 pcur_receive->old_n_core_fields = pcur_donate->old_n_core_fields; 236 pcur_receive->old_n_fields = pcur_donate->old_n_fields; 237 } 238 239 /** Structure acts as functor to do the latching of leaf pages. 240 It returns true if latching of leaf pages succeeded and false 241 otherwise. */ 242 struct optimistic_latch_leaves 243 { 244 btr_pcur_t *const cursor; 245 ulint *latch_mode; 246 mtr_t *const mtr; 247 248 optimistic_latch_leaves(btr_pcur_t *cursor, ulint *latch_mode, mtr_t *mtr) 249 :cursor(cursor), latch_mode(latch_mode), mtr(mtr) {} 250 251 bool operator() (buf_block_t *hint) const 252 { 253 return hint && btr_cur_optimistic_latch_leaves( 254 hint, cursor->modify_clock, latch_mode, 255 btr_pcur_get_btr_cur(cursor), __FILE__, __LINE__, mtr); 256 } 257 }; 258 259 /**************************************************************//** 260 Restores the stored position of a persistent cursor bufferfixing the page and 261 obtaining the specified latches. If the cursor position was saved when the 262 (1) cursor was positioned on a user record: this function restores the position 263 to the last record LESS OR EQUAL to the stored record; 264 (2) cursor was positioned on a page infimum record: restores the position to 265 the last record LESS than the user record which was the successor of the page 266 infimum; 267 (3) cursor was positioned on the page supremum: restores to the first record 268 GREATER than the user record which was the predecessor of the supremum. 269 (4) cursor was positioned before the first or after the last in an empty tree: 270 restores to before first or after the last in the tree. 271 @return TRUE if the cursor position was stored when it was on a user 272 record and it can be restored on a user record whose ordering fields 273 are identical to the ones of the original user record */ 274 ibool 275 btr_pcur_restore_position_func( 276 /*===========================*/ 277 ulint latch_mode, /*!< in: BTR_SEARCH_LEAF, ... */ 278 btr_pcur_t* cursor, /*!< in: detached persistent cursor */ 279 const char* file, /*!< in: file name */ 280 unsigned line, /*!< in: line where called */ 281 mtr_t* mtr) /*!< in: mtr */ 282 { 283 dict_index_t* index; 284 dtuple_t* tuple; 285 page_cur_mode_t mode; 286 page_cur_mode_t old_mode; 287 mem_heap_t* heap; 288 289 ut_ad(mtr->is_active()); 290 //ut_ad(cursor->old_stored); 291 ut_ad(cursor->pos_state == BTR_PCUR_WAS_POSITIONED 292 || cursor->pos_state == BTR_PCUR_IS_POSITIONED); 293 294 index = btr_cur_get_index(btr_pcur_get_btr_cur(cursor)); 295 296 if (UNIV_UNLIKELY 297 (cursor->rel_pos == BTR_PCUR_AFTER_LAST_IN_TREE 298 || cursor->rel_pos == BTR_PCUR_BEFORE_FIRST_IN_TREE)) { 299 dberr_t err = DB_SUCCESS; 300 301 /* In these cases we do not try an optimistic restoration, 302 but always do a search */ 303 304 err = btr_cur_open_at_index_side( 305 cursor->rel_pos == BTR_PCUR_BEFORE_FIRST_IN_TREE, 306 index, latch_mode, 307 btr_pcur_get_btr_cur(cursor), 0, mtr); 308 309 if (err != DB_SUCCESS) { 310 ib::warn() << " Error code: " << err 311 << " btr_pcur_restore_position_func " 312 << " called from file: " 313 << file << " line: " << line 314 << " table: " << index->table->name 315 << " index: " << index->name; 316 } 317 318 cursor->latch_mode = 319 BTR_LATCH_MODE_WITHOUT_INTENTION(latch_mode); 320 cursor->pos_state = BTR_PCUR_IS_POSITIONED; 321 cursor->block_when_stored.clear(); 322 323 return(FALSE); 324 } 325 326 ut_a(cursor->old_rec); 327 ut_a(cursor->old_n_core_fields); 328 ut_a(cursor->old_n_core_fields <= index->n_core_fields); 329 ut_a(cursor->old_n_fields); 330 331 switch (latch_mode) { 332 case BTR_SEARCH_LEAF: 333 case BTR_MODIFY_LEAF: 334 case BTR_SEARCH_PREV: 335 case BTR_MODIFY_PREV: 336 /* Try optimistic restoration. */ 337 338 if (cursor->block_when_stored.run_with_hint( 339 optimistic_latch_leaves(cursor, &latch_mode, 340 mtr))) { 341 cursor->pos_state = BTR_PCUR_IS_POSITIONED; 342 cursor->latch_mode = latch_mode; 343 344 buf_block_dbg_add_level( 345 btr_pcur_get_block(cursor), 346 dict_index_is_ibuf(index) 347 ? SYNC_IBUF_TREE_NODE : SYNC_TREE_NODE); 348 349 if (cursor->rel_pos == BTR_PCUR_ON) { 350 #ifdef UNIV_DEBUG 351 const rec_t* rec; 352 rec_offs offsets1_[REC_OFFS_NORMAL_SIZE]; 353 rec_offs offsets2_[REC_OFFS_NORMAL_SIZE]; 354 rec_offs* offsets1 = offsets1_; 355 rec_offs* offsets2 = offsets2_; 356 rec = btr_pcur_get_rec(cursor); 357 358 rec_offs_init(offsets1_); 359 rec_offs_init(offsets2_); 360 361 heap = mem_heap_create(256); 362 ut_ad(cursor->old_n_core_fields 363 == index->n_core_fields); 364 365 offsets1 = rec_get_offsets( 366 cursor->old_rec, index, offsets1, 367 cursor->old_n_core_fields, 368 cursor->old_n_fields, &heap); 369 offsets2 = rec_get_offsets( 370 rec, index, offsets2, 371 index->n_core_fields, 372 cursor->old_n_fields, &heap); 373 374 ut_ad(!cmp_rec_rec(cursor->old_rec, 375 rec, offsets1, offsets2, 376 index)); 377 mem_heap_free(heap); 378 #endif /* UNIV_DEBUG */ 379 return(TRUE); 380 } 381 /* This is the same record as stored, 382 may need to be adjusted for BTR_PCUR_BEFORE/AFTER, 383 depending on search mode and direction. */ 384 if (btr_pcur_is_on_user_rec(cursor)) { 385 cursor->pos_state 386 = BTR_PCUR_IS_POSITIONED_OPTIMISTIC; 387 } 388 return(FALSE); 389 } 390 } 391 392 /* If optimistic restoration did not succeed, open the cursor anew */ 393 394 heap = mem_heap_create(256); 395 396 tuple = dtuple_create(heap, cursor->old_n_fields); 397 398 dict_index_copy_types(tuple, index, cursor->old_n_fields); 399 400 rec_copy_prefix_to_dtuple(tuple, cursor->old_rec, index, 401 cursor->old_n_core_fields, 402 cursor->old_n_fields, heap); 403 ut_ad(dtuple_check_typed(tuple)); 404 405 /* Save the old search mode of the cursor */ 406 old_mode = cursor->search_mode; 407 408 switch (cursor->rel_pos) { 409 case BTR_PCUR_ON: 410 mode = PAGE_CUR_LE; 411 break; 412 case BTR_PCUR_AFTER: 413 mode = PAGE_CUR_G; 414 break; 415 case BTR_PCUR_BEFORE: 416 mode = PAGE_CUR_L; 417 break; 418 default: 419 ut_error; 420 mode = PAGE_CUR_UNSUPP; 421 } 422 423 btr_pcur_open_with_no_init_func(index, tuple, mode, latch_mode, 424 cursor, 425 #ifdef BTR_CUR_HASH_ADAPT 426 NULL, 427 #endif /* BTR_CUR_HASH_ADAPT */ 428 file, line, mtr); 429 430 /* Restore the old search mode */ 431 cursor->search_mode = old_mode; 432 433 ut_ad(cursor->rel_pos == BTR_PCUR_ON 434 || cursor->rel_pos == BTR_PCUR_BEFORE 435 || cursor->rel_pos == BTR_PCUR_AFTER); 436 rec_offs offsets[REC_OFFS_NORMAL_SIZE]; 437 rec_offs_init(offsets); 438 if (cursor->rel_pos == BTR_PCUR_ON 439 && btr_pcur_is_on_user_rec(cursor) 440 && !cmp_dtuple_rec(tuple, btr_pcur_get_rec(cursor), 441 rec_get_offsets(btr_pcur_get_rec(cursor), 442 index, offsets, 443 index->n_core_fields, 444 ULINT_UNDEFINED, &heap))) { 445 446 /* We have to store the NEW value for the modify clock, 447 since the cursor can now be on a different page! 448 But we can retain the value of old_rec */ 449 450 cursor->block_when_stored.store(btr_pcur_get_block(cursor)); 451 cursor->modify_clock = buf_block_get_modify_clock( 452 cursor->block_when_stored.block()); 453 cursor->old_stored = true; 454 455 mem_heap_free(heap); 456 457 return(TRUE); 458 } 459 460 mem_heap_free(heap); 461 462 /* We have to store new position information, modify_clock etc., 463 to the cursor because it can now be on a different page, the record 464 under it may have been removed, etc. */ 465 466 btr_pcur_store_position(cursor, mtr); 467 468 return(FALSE); 469 } 470 471 /*********************************************************//** 472 Moves the persistent cursor to the first record on the next page. Releases the 473 latch on the current page, and bufferunfixes it. Note that there must not be 474 modifications on the current page, as then the x-latch can be released only in 475 mtr_commit. */ 476 void 477 btr_pcur_move_to_next_page( 478 /*=======================*/ 479 btr_pcur_t* cursor, /*!< in: persistent cursor; must be on the 480 last record of the current page */ 481 mtr_t* mtr) /*!< in: mtr */ 482 { 483 ulint next_page_no; 484 page_t* page; 485 buf_block_t* next_block; 486 page_t* next_page; 487 ulint mode; 488 489 ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); 490 ut_ad(cursor->latch_mode != BTR_NO_LATCHES); 491 ut_ad(btr_pcur_is_after_last_on_page(cursor)); 492 493 cursor->old_stored = false; 494 495 page = btr_pcur_get_page(cursor); 496 497 if (UNIV_UNLIKELY(!page)) { 498 return; 499 } 500 501 next_page_no = btr_page_get_next(page); 502 503 ut_ad(next_page_no != FIL_NULL); 504 505 mode = cursor->latch_mode; 506 switch (mode) { 507 case BTR_SEARCH_TREE: 508 mode = BTR_SEARCH_LEAF; 509 break; 510 case BTR_MODIFY_TREE: 511 mode = BTR_MODIFY_LEAF; 512 } 513 514 buf_block_t* block = btr_pcur_get_block(cursor); 515 516 next_block = btr_block_get( 517 page_id_t(block->page.id.space(), next_page_no), 518 block->page.size, mode, 519 btr_pcur_get_btr_cur(cursor)->index, mtr); 520 521 if (UNIV_UNLIKELY(!next_block)) { 522 return; 523 } 524 525 next_page = buf_block_get_frame(next_block); 526 #ifdef UNIV_BTR_DEBUG 527 ut_a(page_is_comp(next_page) == page_is_comp(page)); 528 ut_a(btr_page_get_prev(next_page) 529 == btr_pcur_get_block(cursor)->page.id.page_no()); 530 #endif /* UNIV_BTR_DEBUG */ 531 532 btr_leaf_page_release(btr_pcur_get_block(cursor), mode, mtr); 533 534 page_cur_set_before_first(next_block, btr_pcur_get_page_cur(cursor)); 535 536 ut_d(page_check_dir(next_page)); 537 } 538 539 /*********************************************************//** 540 Moves the persistent cursor backward if it is on the first record of the page. 541 Commits mtr. Note that to prevent a possible deadlock, the operation 542 first stores the position of the cursor, commits mtr, acquires the necessary 543 latches and restores the cursor position again before returning. The 544 alphabetical position of the cursor is guaranteed to be sensible on 545 return, but it may happen that the cursor is not positioned on the last 546 record of any page, because the structure of the tree may have changed 547 during the time when the cursor had no latches. */ 548 static 549 void 550 btr_pcur_move_backward_from_page( 551 /*=============================*/ 552 btr_pcur_t* cursor, /*!< in: persistent cursor, must be on the first 553 record of the current page */ 554 mtr_t* mtr) /*!< in: mtr */ 555 { 556 ulint prev_page_no; 557 page_t* page; 558 buf_block_t* prev_block; 559 ulint latch_mode; 560 ulint latch_mode2; 561 562 ut_ad(cursor->latch_mode != BTR_NO_LATCHES); 563 ut_ad(btr_pcur_is_before_first_on_page(cursor)); 564 ut_ad(!btr_pcur_is_before_first_in_tree(cursor)); 565 566 latch_mode = cursor->latch_mode; 567 568 if (latch_mode == BTR_SEARCH_LEAF) { 569 570 latch_mode2 = BTR_SEARCH_PREV; 571 572 } else if (latch_mode == BTR_MODIFY_LEAF) { 573 574 latch_mode2 = BTR_MODIFY_PREV; 575 } else { 576 latch_mode2 = 0; /* To eliminate compiler warning */ 577 ut_error; 578 } 579 580 btr_pcur_store_position(cursor, mtr); 581 582 mtr_commit(mtr); 583 584 mtr_start(mtr); 585 586 btr_pcur_restore_position(latch_mode2, cursor, mtr); 587 588 page = btr_pcur_get_page(cursor); 589 590 prev_page_no = btr_page_get_prev(page); 591 592 if (prev_page_no == FIL_NULL) { 593 } else if (btr_pcur_is_before_first_on_page(cursor)) { 594 595 prev_block = btr_pcur_get_btr_cur(cursor)->left_block; 596 597 btr_leaf_page_release(btr_pcur_get_block(cursor), 598 latch_mode, mtr); 599 600 page_cur_set_after_last(prev_block, 601 btr_pcur_get_page_cur(cursor)); 602 } else { 603 604 /* The repositioned cursor did not end on an infimum 605 record on a page. Cursor repositioning acquired a latch 606 also on the previous page, but we do not need the latch: 607 release it. */ 608 609 prev_block = btr_pcur_get_btr_cur(cursor)->left_block; 610 611 btr_leaf_page_release(prev_block, latch_mode, mtr); 612 } 613 614 cursor->latch_mode = latch_mode; 615 cursor->old_stored = false; 616 } 617 618 /*********************************************************//** 619 Moves the persistent cursor to the previous record in the tree. If no records 620 are left, the cursor stays 'before first in tree'. 621 @return TRUE if the cursor was not before first in tree */ 622 ibool 623 btr_pcur_move_to_prev( 624 /*==================*/ 625 btr_pcur_t* cursor, /*!< in: persistent cursor; NOTE that the 626 function may release the page latch */ 627 mtr_t* mtr) /*!< in: mtr */ 628 { 629 ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); 630 ut_ad(cursor->latch_mode != BTR_NO_LATCHES); 631 632 cursor->old_stored = false; 633 634 if (btr_pcur_is_before_first_on_page(cursor)) { 635 636 if (btr_pcur_is_before_first_in_tree(cursor)) { 637 638 return(FALSE); 639 } 640 641 btr_pcur_move_backward_from_page(cursor, mtr); 642 643 return(TRUE); 644 } 645 646 btr_pcur_move_to_prev_on_page(cursor); 647 648 return(TRUE); 649 } 650 651 /**************************************************************//** 652 If mode is PAGE_CUR_G or PAGE_CUR_GE, opens a persistent cursor on the first 653 user record satisfying the search condition, in the case PAGE_CUR_L or 654 PAGE_CUR_LE, on the last user record. If no such user record exists, then 655 in the first case sets the cursor after last in tree, and in the latter case 656 before first in tree. The latching mode must be BTR_SEARCH_LEAF or 657 BTR_MODIFY_LEAF. */ 658 void 659 btr_pcur_open_on_user_rec_func( 660 /*===========================*/ 661 dict_index_t* index, /*!< in: index */ 662 const dtuple_t* tuple, /*!< in: tuple on which search done */ 663 page_cur_mode_t mode, /*!< in: PAGE_CUR_L, ... */ 664 ulint latch_mode, /*!< in: BTR_SEARCH_LEAF or 665 BTR_MODIFY_LEAF */ 666 btr_pcur_t* cursor, /*!< in: memory buffer for persistent 667 cursor */ 668 const char* file, /*!< in: file name */ 669 unsigned line, /*!< in: line where called */ 670 mtr_t* mtr) /*!< in: mtr */ 671 { 672 btr_pcur_open_low(index, 0, tuple, mode, latch_mode, cursor, 673 file, line, 0, mtr); 674 675 if ((mode == PAGE_CUR_GE) || (mode == PAGE_CUR_G)) { 676 677 if (btr_pcur_is_after_last_on_page(cursor)) { 678 679 btr_pcur_move_to_next_user_rec(cursor, mtr); 680 } 681 } else { 682 ut_ad((mode == PAGE_CUR_LE) || (mode == PAGE_CUR_L)); 683 684 /* Not implemented yet */ 685 686 ut_error; 687 } 688 } 689