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