mirror of
https://github.com/MariaDB/server.git
synced 2025-01-18 04:53:01 +01:00
c2f0c211cd
Fixes: - Bug #23710: crash_commit_before fails if innodb_file_per_table=1 - Bug #28254: innodb crash if shutdown during innodb_table_monitor is running - Bug #28604: innodb_force_recovery restricts data dump - Bug #29097: fsp_get_available_space_in_free_extents() is capped at 4TB - Bug #29155: Innodb "Parallel recovery" is not prevented storage/innobase/Makefile.am: Apply snapshot innodb-51-ss1644 Revision r1632: Add include/lock0priv.h to noinst_HEADERS in Makefile.am. Revision r1636: Move lock_get_type() from lock/lock0lock.c to include/lock0priv.ic: * lock0lock.c: remove lock_get_type() and include include/lock0priv.ic * lock0priv.h: include lock0priv.ic and add lock_get_type() prototype * Makefile.am: add lock0priv.ic to noinst_HEADERS * lock0priv.ic: introduce this new file containing the body of lock_get_type() This move is necessary in order to use lock_get_type() from other lock/ source files (it's going to be used in lock/lock0iter.c). Approved by: Heikki Revision r1638: Introduce a lock queue iterator for easy (and opaque) traversing of lock queues. Supports table and record lock queues via the same interface. There is only "get previous" method because currently there is no need for "get next" - it would be unused. Feel free to add one if needed. Approved by: Heikki storage/innobase/buf/buf0buf.c: Apply snapshot innodb-51-ss1644 Revision r1624: Fix change missed as part of Bug 15815. Use a function to check if a block needs to be made younger. Revision r1581: Port extra Valgrind instrumentation (UNIV_DEBUG_VALGRIND) from branches/zip. storage/innobase/buf/buf0lru.c: Apply snapshot innodb-51-ss1644 Revision r1581: Port extra Valgrind instrumentation (UNIV_DEBUG_VALGRIND) from branches/zip. storage/innobase/fsp/fsp0fsp.c: Apply snapshot innodb-51-ss1644 Revision r1605: Fix Bug#29097 "fsp_get_available_space_in_free_extents() is capped at 4TB" by typecasting the variables before multiplying them, so that the result of the multiplication is of type "unsigned long long". I verified this fix by creating a sparse file of 6TB and forcing InnoDB to use it without overwriting it with zeroes (by commenting the code that overwrites :newraw files). New type ullint is introduced with the sole purpose of shortening "unsigned long long", please do not define it to something else than "unsigned long long". Approved by: Heikki storage/innobase/handler/ha_innodb.cc: Apply snapshot innodb-51-ss1644 Revision r1605: Fix Bug#29097 "fsp_get_available_space_in_free_extents() is capped at 4TB" by typecasting the variables before multiplying them, so that the result of the multiplication is of type "unsigned long long". I verified this fix by creating a sparse file of 6TB and forcing InnoDB to use it without overwriting it with zeroes (by commenting the code that overwrites :newraw files). New type ullint is introduced with the sole purpose of shortening "unsigned long long", please do not define it to something else than "unsigned long long". Approved by: Heikki Revision r1573: create_table_def(): Eliminate the inline function call to dict_table_is_comp() that was introduced in r1571. Inlining is disabled in ha_innodb.cc. Revision r1574: innodb_check_for_record_too_big_error(): Divide the return value of page_get_free_space_of_empty_noninline() by 2. Until r1571, that function did not return the same value as page_get_free_space_of_empty(). Revision r1571: Fix a severe bug that was introduced in r1422 when fixing Bug 21101. When creating an index containing a too long record, InnoDB would dereference a NULL pointer when trying to determine the maximum row length. innodb_check_for_record_too_big_error(): Replace the dict_table_t* parameter with a Boolean flag. There is not always a dict_table_t object when this function is called. page_get_free_space_of_empty_noninline(): Move the definition and declaration from row0mysql (!) to page0page. Make the signature identical with page_get_free_space_of_empty(). create_clustered_index_when_no_primary(): Add the parameter "comp". Remove unnecessary casts. storage/innobase/include/buf0buf.ic: Apply snapshot innodb-51-ss1644 Revision r1624: Fix change missed as part of Bug 15815. Use a function to check if a block needs to be made younger. storage/innobase/include/fsp0fsp.h: Apply snapshot innodb-51-ss1644 Revision r1605: Fix Bug#29097 "fsp_get_available_space_in_free_extents() is capped at 4TB" by typecasting the variables before multiplying them, so that the result of the multiplication is of type "unsigned long long". I verified this fix by creating a sparse file of 6TB and forcing InnoDB to use it without overwriting it with zeroes (by commenting the code that overwrites :newraw files). New type ullint is introduced with the sole purpose of shortening "unsigned long long", please do not define it to something else than "unsigned long long". Approved by: Heikki storage/innobase/include/lock0lock.h: Apply snapshot innodb-51-ss1644 Revision r1623: Fix typo in comment. Revision r1628: lock_has_to_wait() is needed in the INFORMATION_SCHEMA implementation in order to determine which lock is blocking which. Make it non-static and put its definition in include/lock0lock.h. Approved by: Heikki (via IM) storage/innobase/include/mem0mem.ic: Apply snapshot innodb-51-ss1644 Revision r1581: Port extra Valgrind instrumentation (UNIV_DEBUG_VALGRIND) from branches/zip. storage/innobase/include/page0page.h: Apply snapshot innodb-51-ss1644 Revision r1571: Fix a severe bug that was introduced in r1422 when fixing Bug 21101. When creating an index containing a too long record, InnoDB would dereference a NULL pointer when trying to determine the maximum row length. innodb_check_for_record_too_big_error(): Replace the dict_table_t* parameter with a Boolean flag. There is not always a dict_table_t object when this function is called. page_get_free_space_of_empty_noninline(): Move the definition and declaration from row0mysql (!) to page0page. Make the signature identical with page_get_free_space_of_empty(). create_clustered_index_when_no_primary(): Add the parameter "comp". Remove unnecessary casts. storage/innobase/include/row0mysql.h: Apply snapshot innodb-51-ss1644 Revision r1571: Fix a severe bug that was introduced in r1422 when fixing Bug 21101. When creating an index containing a too long record, InnoDB would dereference a NULL pointer when trying to determine the maximum row length. innodb_check_for_record_too_big_error(): Replace the dict_table_t* parameter with a Boolean flag. There is not always a dict_table_t object when this function is called. page_get_free_space_of_empty_noninline(): Move the definition and declaration from row0mysql (!) to page0page. Make the signature identical with page_get_free_space_of_empty(). create_clustered_index_when_no_primary(): Add the parameter "comp". Remove unnecessary casts. storage/innobase/include/univ.i: Apply snapshot innodb-51-ss1644 Revision r1605: Fix Bug#29097 "fsp_get_available_space_in_free_extents() is capped at 4TB" by typecasting the variables before multiplying them, so that the result of the multiplication is of type "unsigned long long". I verified this fix by creating a sparse file of 6TB and forcing InnoDB to use it without overwriting it with zeroes (by commenting the code that overwrites :newraw files). New type ullint is introduced with the sole purpose of shortening "unsigned long long", please do not define it to something else than "unsigned long long". Approved by: Heikki Revision r1581: Port extra Valgrind instrumentation (UNIV_DEBUG_VALGRIND) from branches/zip. storage/innobase/lock/lock0lock.c: Apply snapshot innodb-51-ss1644 Revision r1631: Move lock_rec_find_set_bit() and lock_rec_get_prev() from lock/lock0lock.c to include/lock0priv.h and make them non-static. They will be used in lock/lock0iter.c. Approved by: Heikki Revision r1636: Move lock_get_type() from lock/lock0lock.c to include/lock0priv.ic: * lock0lock.c: remove lock_get_type() and include include/lock0priv.ic * lock0priv.h: include lock0priv.ic and add lock_get_type() prototype * Makefile.am: add lock0priv.ic to noinst_HEADERS * lock0priv.ic: introduce this new file containing the body of lock_get_type() This move is necessary in order to use lock_get_type() from other lock/ source files (it's going to be used in lock/lock0iter.c). Approved by: Heikki Revision r1628: lock_has_to_wait() is needed in the INFORMATION_SCHEMA implementation in order to determine which lock is blocking which. Make it non-static and put its definition in include/lock0lock.h. Approved by: Heikki (via IM) Revision r1629: Add "const" qualifiers to lock_get_type() and lock_get_mode(). Approved by: Sunny Revision r1626: Move lock_*struct structures from lock/lock0lock.c to include/lock0priv.h. This is needed in order to add more code to lock/ that uses members of these structures (internal to the lock module) but in a separate file, rather than lock0lock.c. lock0lock.c is a way too big already. Approved by: Sunny storage/innobase/log/log0recv.c: Apply snapshot innodb-51-ss1644 Revision r1607: Bug#23710 At InnoDB startup consider the case where log scan went beyond checkpoint_lsn as a crash and initiate crash recovery code path. reviewed by: Heikki storage/innobase/mem/mem0mem.c: Apply snapshot innodb-51-ss1644 Revision r1581: Port extra Valgrind instrumentation (UNIV_DEBUG_VALGRIND) from branches/zip. storage/innobase/mem/mem0pool.c: Apply snapshot innodb-51-ss1644 Revision r1581: Port extra Valgrind instrumentation (UNIV_DEBUG_VALGRIND) from branches/zip. storage/innobase/os/os0file.c: Apply snapshot innodb-51-ss1644 Revision r1613: Fix Bug#29155 by enabling file locking on FreeBSD. It has been disabled because InnoDB has refused to start on FreeBSD & LinuxThreads, but now it starts just fine. Approved by: Heikki storage/innobase/page/page0page.c: Apply snapshot innodb-51-ss1644 Revision r1571: Fix a severe bug that was introduced in r1422 when fixing Bug 21101. When creating an index containing a too long record, InnoDB would dereference a NULL pointer when trying to determine the maximum row length. innodb_check_for_record_too_big_error(): Replace the dict_table_t* parameter with a Boolean flag. There is not always a dict_table_t object when this function is called. page_get_free_space_of_empty_noninline(): Move the definition and declaration from row0mysql (!) to page0page. Make the signature identical with page_get_free_space_of_empty(). create_clustered_index_when_no_primary(): Add the parameter "comp". Remove unnecessary casts. storage/innobase/rem/rem0rec.c: Apply snapshot innodb-51-ss1644 Revision r1581: Port extra Valgrind instrumentation (UNIV_DEBUG_VALGRIND) from branches/zip. storage/innobase/row/row0mysql.c: Apply snapshot innodb-51-ss1644 Revision r1571: Fix a severe bug that was introduced in r1422 when fixing Bug 21101. When creating an index containing a too long record, InnoDB would dereference a NULL pointer when trying to determine the maximum row length. innodb_check_for_record_too_big_error(): Replace the dict_table_t* parameter with a Boolean flag. There is not always a dict_table_t object when this function is called. page_get_free_space_of_empty_noninline(): Move the definition and declaration from row0mysql (!) to page0page. Make the signature identical with page_get_free_space_of_empty(). create_clustered_index_when_no_primary(): Add the parameter "comp". Remove unnecessary casts. storage/innobase/sync/sync0rw.c: Apply snapshot innodb-51-ss1644 Revision r1598: Add some comments. Approved by: Heikki (via IM) storage/innobase/sync/sync0sync.c: Apply snapshot innodb-51-ss1644 Revision r1598: Add some comments. Approved by: Heikki (via IM) storage/innobase/trx/trx0sys.c: Apply snapshot innodb-51-ss1644 Revision r1581: Port extra Valgrind instrumentation (UNIV_DEBUG_VALGRIND) from branches/zip. storage/innobase/trx/trx0trx.c: Apply snapshot innodb-51-ss1644 Revision r1595: trx_commit_for_mysql(): Avoid acquiring and releasing kernel_mutex when trx->sess or trx_dummy_sess is non-NULL. storage/innobase/ut/ut0mem.c: Apply snapshot innodb-51-ss1644 Revision r1581: Port extra Valgrind instrumentation (UNIV_DEBUG_VALGRIND) from branches/zip.
2038 lines
51 KiB
C
2038 lines
51 KiB
C
/******************************************************
|
|
Index page routines
|
|
|
|
(c) 1994-1996 Innobase Oy
|
|
|
|
Created 2/2/1994 Heikki Tuuri
|
|
*******************************************************/
|
|
|
|
#define THIS_MODULE
|
|
#include "page0page.h"
|
|
#ifdef UNIV_NONINL
|
|
#include "page0page.ic"
|
|
#endif
|
|
#undef THIS_MODULE
|
|
|
|
#include "page0cur.h"
|
|
#include "lock0lock.h"
|
|
#include "fut0lst.h"
|
|
#include "btr0sea.h"
|
|
#include "buf0buf.h"
|
|
#include "srv0srv.h"
|
|
#include "btr0btr.h"
|
|
|
|
/* THE INDEX PAGE
|
|
==============
|
|
|
|
The index page consists of a page header which contains the page's
|
|
id and other information. On top of it are the the index records
|
|
in a heap linked into a one way linear list according to alphabetic order.
|
|
|
|
Just below page end is an array of pointers which we call page directory,
|
|
to about every sixth record in the list. The pointers are placed in
|
|
the directory in the alphabetical order of the records pointed to,
|
|
enabling us to make binary search using the array. Each slot n:o I
|
|
in the directory points to a record, where a 4-bit field contains a count
|
|
of those records which are in the linear list between pointer I and
|
|
the pointer I - 1 in the directory, including the record
|
|
pointed to by pointer I and not including the record pointed to by I - 1.
|
|
We say that the record pointed to by slot I, or that slot I, owns
|
|
these records. The count is always kept in the range 4 to 8, with
|
|
the exception that it is 1 for the first slot, and 1--8 for the second slot.
|
|
|
|
An essentially binary search can be performed in the list of index
|
|
records, like we could do if we had pointer to every record in the
|
|
page directory. The data structure is, however, more efficient when
|
|
we are doing inserts, because most inserts are just pushed on a heap.
|
|
Only every 8th insert requires block move in the directory pointer
|
|
table, which itself is quite small. A record is deleted from the page
|
|
by just taking it off the linear list and updating the number of owned
|
|
records-field of the record which owns it, and updating the page directory,
|
|
if necessary. A special case is the one when the record owns itself.
|
|
Because the overhead of inserts is so small, we may also increase the
|
|
page size from the projected default of 8 kB to 64 kB without too
|
|
much loss of efficiency in inserts. Bigger page becomes actual
|
|
when the disk transfer rate compared to seek and latency time rises.
|
|
On the present system, the page size is set so that the page transfer
|
|
time (3 ms) is 20 % of the disk random access time (15 ms).
|
|
|
|
When the page is split, merged, or becomes full but contains deleted
|
|
records, we have to reorganize the page.
|
|
|
|
Assuming a page size of 8 kB, a typical index page of a secondary
|
|
index contains 300 index entries, and the size of the page directory
|
|
is 50 x 4 bytes = 200 bytes. */
|
|
|
|
/*******************************************************************
|
|
Looks for the directory slot which owns the given record. */
|
|
|
|
ulint
|
|
page_dir_find_owner_slot(
|
|
/*=====================*/
|
|
/* out: the directory slot number */
|
|
rec_t* rec) /* in: the physical record */
|
|
{
|
|
page_t* page;
|
|
register uint16 rec_offs_bytes;
|
|
register page_dir_slot_t* slot;
|
|
register const page_dir_slot_t* first_slot;
|
|
register rec_t* r = rec;
|
|
|
|
ut_ad(page_rec_check(rec));
|
|
|
|
page = buf_frame_align(rec);
|
|
first_slot = page_dir_get_nth_slot(page, 0);
|
|
slot = page_dir_get_nth_slot(page, page_dir_get_n_slots(page) - 1);
|
|
|
|
if (page_is_comp(page)) {
|
|
while (rec_get_n_owned(r, TRUE) == 0) {
|
|
r = page + rec_get_next_offs(r, TRUE);
|
|
ut_ad(r >= page + PAGE_NEW_SUPREMUM);
|
|
ut_ad(r < page + (UNIV_PAGE_SIZE - PAGE_DIR));
|
|
}
|
|
} else {
|
|
while (rec_get_n_owned(r, FALSE) == 0) {
|
|
r = page + rec_get_next_offs(r, FALSE);
|
|
ut_ad(r >= page + PAGE_OLD_SUPREMUM);
|
|
ut_ad(r < page + (UNIV_PAGE_SIZE - PAGE_DIR));
|
|
}
|
|
}
|
|
|
|
rec_offs_bytes = mach_encode_2(r - page);
|
|
|
|
while (UNIV_LIKELY(*(uint16*) slot != rec_offs_bytes)) {
|
|
|
|
if (UNIV_UNLIKELY(slot == first_slot)) {
|
|
fprintf(stderr,
|
|
"InnoDB: Probable data corruption on"
|
|
" page %lu\n"
|
|
"InnoDB: Original record ",
|
|
(ulong) buf_frame_get_page_no(page));
|
|
|
|
if (page_is_comp(page)) {
|
|
fputs("(compact record)", stderr);
|
|
} else {
|
|
rec_print_old(stderr, rec);
|
|
}
|
|
|
|
fputs("\n"
|
|
"InnoDB: on that page.\n"
|
|
"InnoDB: Cannot find the dir slot for record ",
|
|
stderr);
|
|
if (page_is_comp(page)) {
|
|
fputs("(compact record)", stderr);
|
|
} else {
|
|
rec_print_old(stderr, page
|
|
+ mach_decode_2(rec_offs_bytes));
|
|
}
|
|
fputs("\n"
|
|
"InnoDB: on that page!\n", stderr);
|
|
|
|
buf_page_print(page);
|
|
|
|
ut_error;
|
|
}
|
|
|
|
slot += PAGE_DIR_SLOT_SIZE;
|
|
}
|
|
|
|
return(((ulint) (first_slot - slot)) / PAGE_DIR_SLOT_SIZE);
|
|
}
|
|
|
|
/******************************************************************
|
|
Used to check the consistency of a directory slot. */
|
|
static
|
|
ibool
|
|
page_dir_slot_check(
|
|
/*================*/
|
|
/* out: TRUE if succeed */
|
|
page_dir_slot_t* slot) /* in: slot */
|
|
{
|
|
page_t* page;
|
|
ulint n_slots;
|
|
ulint n_owned;
|
|
|
|
ut_a(slot);
|
|
|
|
page = buf_frame_align(slot);
|
|
|
|
n_slots = page_dir_get_n_slots(page);
|
|
|
|
ut_a(slot <= page_dir_get_nth_slot(page, 0));
|
|
ut_a(slot >= page_dir_get_nth_slot(page, n_slots - 1));
|
|
|
|
ut_a(page_rec_check(page_dir_slot_get_rec(slot)));
|
|
|
|
n_owned = rec_get_n_owned(page_dir_slot_get_rec(slot),
|
|
page_is_comp(page));
|
|
|
|
if (slot == page_dir_get_nth_slot(page, 0)) {
|
|
ut_a(n_owned == 1);
|
|
} else if (slot == page_dir_get_nth_slot(page, n_slots - 1)) {
|
|
ut_a(n_owned >= 1);
|
|
ut_a(n_owned <= PAGE_DIR_SLOT_MAX_N_OWNED);
|
|
} else {
|
|
ut_a(n_owned >= PAGE_DIR_SLOT_MIN_N_OWNED);
|
|
ut_a(n_owned <= PAGE_DIR_SLOT_MAX_N_OWNED);
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
/*****************************************************************
|
|
Sets the max trx id field value. */
|
|
|
|
void
|
|
page_set_max_trx_id(
|
|
/*================*/
|
|
page_t* page, /* in: page */
|
|
dulint trx_id) /* in: transaction id */
|
|
{
|
|
buf_block_t* block;
|
|
|
|
ut_ad(page);
|
|
|
|
block = buf_block_align(page);
|
|
|
|
if (block->is_hashed) {
|
|
rw_lock_x_lock(&btr_search_latch);
|
|
}
|
|
|
|
/* It is not necessary to write this change to the redo log, as
|
|
during a database recovery we assume that the max trx id of every
|
|
page is the maximum trx id assigned before the crash. */
|
|
|
|
mach_write_to_8(page + PAGE_HEADER + PAGE_MAX_TRX_ID, trx_id);
|
|
|
|
if (block->is_hashed) {
|
|
rw_lock_x_unlock(&btr_search_latch);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************
|
|
Calculates free space if a page is emptied. */
|
|
|
|
ulint
|
|
page_get_free_space_of_empty_noninline(
|
|
/*===================================*/
|
|
/* out: free space */
|
|
ulint comp) /* in: nonzero=compact page format */
|
|
{
|
|
return(page_get_free_space_of_empty(comp));
|
|
}
|
|
|
|
/****************************************************************
|
|
Allocates a block of memory from an index page. */
|
|
|
|
byte*
|
|
page_mem_alloc(
|
|
/*===========*/
|
|
/* out: pointer to start of allocated
|
|
buffer, or NULL if allocation fails */
|
|
page_t* page, /* in: index page */
|
|
ulint need, /* in: number of bytes needed */
|
|
dict_index_t* index, /* in: record descriptor */
|
|
ulint* heap_no)/* out: this contains the heap number
|
|
of the allocated record
|
|
if allocation succeeds */
|
|
{
|
|
rec_t* rec;
|
|
byte* block;
|
|
ulint avl_space;
|
|
ulint garbage;
|
|
|
|
ut_ad(page && heap_no);
|
|
|
|
/* If there are records in the free list, look if the first is
|
|
big enough */
|
|
|
|
rec = page_header_get_ptr(page, PAGE_FREE);
|
|
|
|
if (rec) {
|
|
mem_heap_t* heap = NULL;
|
|
ulint offsets_[REC_OFFS_NORMAL_SIZE];
|
|
ulint* offsets = offsets_;
|
|
*offsets_ = (sizeof offsets_) / sizeof *offsets_;
|
|
|
|
offsets = rec_get_offsets(rec, index, offsets,
|
|
ULINT_UNDEFINED, &heap);
|
|
|
|
if (rec_offs_size(offsets) >= need) {
|
|
page_header_set_ptr(page, PAGE_FREE,
|
|
page_rec_get_next(rec));
|
|
|
|
garbage = page_header_get_field(page, PAGE_GARBAGE);
|
|
ut_ad(garbage >= need);
|
|
|
|
page_header_set_field(page, PAGE_GARBAGE,
|
|
garbage - need);
|
|
|
|
*heap_no = rec_get_heap_no(rec, page_is_comp(page));
|
|
|
|
block = rec_get_start(rec, offsets);
|
|
if (UNIV_LIKELY_NULL(heap)) {
|
|
mem_heap_free(heap);
|
|
}
|
|
return(block);
|
|
}
|
|
|
|
if (UNIV_LIKELY_NULL(heap)) {
|
|
mem_heap_free(heap);
|
|
}
|
|
}
|
|
|
|
/* Could not find space from the free list, try top of heap */
|
|
|
|
avl_space = page_get_max_insert_size(page, 1);
|
|
|
|
if (avl_space >= need) {
|
|
block = page_header_get_ptr(page, PAGE_HEAP_TOP);
|
|
|
|
page_header_set_ptr(page, PAGE_HEAP_TOP, block + need);
|
|
*heap_no = page_dir_get_n_heap(page);
|
|
|
|
page_dir_set_n_heap(page, 1 + *heap_no);
|
|
|
|
return(block);
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
/**************************************************************
|
|
Writes a log record of page creation. */
|
|
UNIV_INLINE
|
|
void
|
|
page_create_write_log(
|
|
/*==================*/
|
|
buf_frame_t* frame, /* in: a buffer frame where the page is
|
|
created */
|
|
mtr_t* mtr, /* in: mini-transaction handle */
|
|
ulint comp) /* in: nonzero=compact page format */
|
|
{
|
|
mlog_write_initial_log_record(frame, comp
|
|
? MLOG_COMP_PAGE_CREATE
|
|
: MLOG_PAGE_CREATE, mtr);
|
|
}
|
|
|
|
/***************************************************************
|
|
Parses a redo log record of creating a page. */
|
|
|
|
byte*
|
|
page_parse_create(
|
|
/*==============*/
|
|
/* out: end of log record or NULL */
|
|
byte* ptr, /* in: buffer */
|
|
byte* end_ptr __attribute__((unused)), /* in: buffer end */
|
|
ulint comp, /* in: nonzero=compact page format */
|
|
page_t* page, /* in: page or NULL */
|
|
mtr_t* mtr) /* in: mtr or NULL */
|
|
{
|
|
ut_ad(ptr && end_ptr);
|
|
|
|
/* The record is empty, except for the record initial part */
|
|
|
|
if (page) {
|
|
page_create(page, mtr, comp);
|
|
}
|
|
|
|
return(ptr);
|
|
}
|
|
|
|
/**************************************************************
|
|
The index page creation function. */
|
|
|
|
page_t*
|
|
page_create(
|
|
/*========*/
|
|
/* out: pointer to the page */
|
|
buf_frame_t* frame, /* in: a buffer frame where the page is
|
|
created */
|
|
mtr_t* mtr, /* in: mini-transaction handle */
|
|
ulint comp) /* in: nonzero=compact page format */
|
|
{
|
|
page_dir_slot_t* slot;
|
|
mem_heap_t* heap;
|
|
dtuple_t* tuple;
|
|
dfield_t* field;
|
|
byte* heap_top;
|
|
rec_t* infimum_rec;
|
|
rec_t* supremum_rec;
|
|
page_t* page;
|
|
dict_index_t* index;
|
|
ulint* offsets;
|
|
|
|
index = comp ? srv_sys->dummy_ind2 : srv_sys->dummy_ind1;
|
|
|
|
ut_ad(frame && mtr);
|
|
#if PAGE_BTR_IBUF_FREE_LIST + FLST_BASE_NODE_SIZE > PAGE_DATA
|
|
# error "PAGE_BTR_IBUF_FREE_LIST + FLST_BASE_NODE_SIZE > PAGE_DATA"
|
|
#endif
|
|
#if PAGE_BTR_IBUF_FREE_LIST_NODE + FLST_NODE_SIZE > PAGE_DATA
|
|
# error "PAGE_BTR_IBUF_FREE_LIST_NODE + FLST_NODE_SIZE > PAGE_DATA"
|
|
#endif
|
|
|
|
/* 1. INCREMENT MODIFY CLOCK */
|
|
buf_frame_modify_clock_inc(frame);
|
|
|
|
/* 2. WRITE LOG INFORMATION */
|
|
page_create_write_log(frame, mtr, comp);
|
|
|
|
page = frame;
|
|
|
|
fil_page_set_type(page, FIL_PAGE_INDEX);
|
|
|
|
heap = mem_heap_create(200);
|
|
|
|
/* 3. CREATE THE INFIMUM AND SUPREMUM RECORDS */
|
|
|
|
/* Create first a data tuple for infimum record */
|
|
tuple = dtuple_create(heap, 1);
|
|
dtuple_set_info_bits(tuple, REC_STATUS_INFIMUM);
|
|
field = dtuple_get_nth_field(tuple, 0);
|
|
|
|
dfield_set_data(field, "infimum", 8);
|
|
dtype_set(dfield_get_type(field),
|
|
DATA_VARCHAR, DATA_ENGLISH | DATA_NOT_NULL, 8);
|
|
/* Set the corresponding physical record to its place in the page
|
|
record heap */
|
|
|
|
heap_top = page + PAGE_DATA;
|
|
|
|
infimum_rec = rec_convert_dtuple_to_rec(heap_top, index, tuple);
|
|
|
|
ut_a(infimum_rec == page
|
|
+ (comp ? PAGE_NEW_INFIMUM : PAGE_OLD_INFIMUM));
|
|
|
|
rec_set_n_owned(infimum_rec, comp, 1);
|
|
rec_set_heap_no(infimum_rec, comp, 0);
|
|
offsets = rec_get_offsets(infimum_rec, index, NULL,
|
|
ULINT_UNDEFINED, &heap);
|
|
|
|
heap_top = rec_get_end(infimum_rec, offsets);
|
|
|
|
/* Create then a tuple for supremum */
|
|
|
|
tuple = dtuple_create(heap, 1);
|
|
dtuple_set_info_bits(tuple, REC_STATUS_SUPREMUM);
|
|
field = dtuple_get_nth_field(tuple, 0);
|
|
|
|
dfield_set_data(field, "supremum", comp ? 8 : 9);
|
|
dtype_set(dfield_get_type(field),
|
|
DATA_VARCHAR, DATA_ENGLISH | DATA_NOT_NULL, comp ? 8 : 9);
|
|
|
|
supremum_rec = rec_convert_dtuple_to_rec(heap_top, index, tuple);
|
|
|
|
ut_a(supremum_rec == page
|
|
+ (comp ? PAGE_NEW_SUPREMUM : PAGE_OLD_SUPREMUM));
|
|
|
|
rec_set_n_owned(supremum_rec, comp, 1);
|
|
rec_set_heap_no(supremum_rec, comp, 1);
|
|
|
|
offsets = rec_get_offsets(supremum_rec, index, offsets,
|
|
ULINT_UNDEFINED, &heap);
|
|
heap_top = rec_get_end(supremum_rec, offsets);
|
|
|
|
ut_ad(heap_top == page
|
|
+ (comp ? PAGE_NEW_SUPREMUM_END : PAGE_OLD_SUPREMUM_END));
|
|
|
|
mem_heap_free(heap);
|
|
|
|
/* 4. INITIALIZE THE PAGE */
|
|
|
|
page_header_set_field(page, PAGE_N_DIR_SLOTS, 2);
|
|
page_header_set_ptr(page, PAGE_HEAP_TOP, heap_top);
|
|
page_header_set_field(page, PAGE_N_HEAP, comp ? 0x8002 : 2);
|
|
page_header_set_ptr(page, PAGE_FREE, NULL);
|
|
page_header_set_field(page, PAGE_GARBAGE, 0);
|
|
page_header_set_ptr(page, PAGE_LAST_INSERT, NULL);
|
|
page_header_set_field(page, PAGE_DIRECTION, PAGE_NO_DIRECTION);
|
|
page_header_set_field(page, PAGE_N_DIRECTION, 0);
|
|
page_header_set_field(page, PAGE_N_RECS, 0);
|
|
page_set_max_trx_id(page, ut_dulint_zero);
|
|
memset(heap_top, 0, UNIV_PAGE_SIZE - PAGE_EMPTY_DIR_START
|
|
- (heap_top - page));
|
|
|
|
/* 5. SET POINTERS IN RECORDS AND DIR SLOTS */
|
|
|
|
/* Set the slots to point to infimum and supremum. */
|
|
|
|
slot = page_dir_get_nth_slot(page, 0);
|
|
page_dir_slot_set_rec(slot, infimum_rec);
|
|
|
|
slot = page_dir_get_nth_slot(page, 1);
|
|
page_dir_slot_set_rec(slot, supremum_rec);
|
|
|
|
/* Set the next pointers in infimum and supremum */
|
|
|
|
rec_set_next_offs(infimum_rec, comp, (ulint)(supremum_rec - page));
|
|
rec_set_next_offs(supremum_rec, comp, 0);
|
|
|
|
return(page);
|
|
}
|
|
|
|
/*****************************************************************
|
|
Differs from page_copy_rec_list_end, because this function does not
|
|
touch the lock table and max trx id on page. */
|
|
|
|
void
|
|
page_copy_rec_list_end_no_locks(
|
|
/*============================*/
|
|
page_t* new_page, /* in: index page to copy to */
|
|
page_t* page, /* in: index page */
|
|
rec_t* rec, /* in: record on page */
|
|
dict_index_t* index, /* in: record descriptor */
|
|
mtr_t* mtr) /* in: mtr */
|
|
{
|
|
page_cur_t cur1;
|
|
page_cur_t cur2;
|
|
rec_t* sup;
|
|
mem_heap_t* heap = NULL;
|
|
ulint offsets_[REC_OFFS_NORMAL_SIZE];
|
|
ulint* offsets = offsets_;
|
|
*offsets_ = (sizeof offsets_) / sizeof *offsets_;
|
|
|
|
page_cur_position(rec, &cur1);
|
|
|
|
if (page_cur_is_before_first(&cur1)) {
|
|
|
|
page_cur_move_to_next(&cur1);
|
|
}
|
|
|
|
ut_a((ibool)!!page_is_comp(new_page)
|
|
== dict_table_is_comp(index->table));
|
|
ut_a(page_is_comp(new_page) == page_is_comp(page));
|
|
ut_a(mach_read_from_2(new_page + UNIV_PAGE_SIZE - 10) == (ulint)
|
|
(page_is_comp(new_page)
|
|
? PAGE_NEW_INFIMUM : PAGE_OLD_INFIMUM));
|
|
|
|
page_cur_set_before_first(new_page, &cur2);
|
|
|
|
/* Copy records from the original page to the new page */
|
|
|
|
sup = page_get_supremum_rec(page);
|
|
|
|
for (;;) {
|
|
rec_t* cur1_rec = page_cur_get_rec(&cur1);
|
|
if (cur1_rec == sup) {
|
|
break;
|
|
}
|
|
offsets = rec_get_offsets(cur1_rec, index, offsets,
|
|
ULINT_UNDEFINED, &heap);
|
|
if (UNIV_UNLIKELY(!page_cur_rec_insert(&cur2, cur1_rec, index,
|
|
offsets, mtr))) {
|
|
/* Track an assertion failure reported on the mailing
|
|
list on June 18th, 2003 */
|
|
|
|
buf_page_print(new_page);
|
|
buf_page_print(page);
|
|
ut_print_timestamp(stderr);
|
|
|
|
fprintf(stderr,
|
|
"InnoDB: rec offset %lu, cur1 offset %lu,"
|
|
" cur2 offset %lu\n",
|
|
(ulong)(rec - page),
|
|
(ulong)(page_cur_get_rec(&cur1) - page),
|
|
(ulong)(page_cur_get_rec(&cur2) - new_page));
|
|
|
|
ut_error;
|
|
}
|
|
|
|
page_cur_move_to_next(&cur1);
|
|
page_cur_move_to_next(&cur2);
|
|
}
|
|
|
|
if (UNIV_LIKELY_NULL(heap)) {
|
|
mem_heap_free(heap);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************
|
|
Copies records from page to new_page, from a given record onward,
|
|
including that record. Infimum and supremum records are not copied.
|
|
The records are copied to the start of the record list on new_page. */
|
|
|
|
void
|
|
page_copy_rec_list_end(
|
|
/*===================*/
|
|
page_t* new_page, /* in: index page to copy to */
|
|
page_t* page, /* in: index page */
|
|
rec_t* rec, /* in: record on page */
|
|
dict_index_t* index, /* in: record descriptor */
|
|
mtr_t* mtr) /* in: mtr */
|
|
{
|
|
if (page_dir_get_n_heap(new_page) == 2) {
|
|
page_copy_rec_list_end_to_created_page(new_page, page, rec,
|
|
index, mtr);
|
|
} else {
|
|
page_copy_rec_list_end_no_locks(new_page, page, rec,
|
|
index, mtr);
|
|
}
|
|
|
|
/* Update the lock table, MAX_TRX_ID, and possible hash index */
|
|
|
|
lock_move_rec_list_end(new_page, page, rec);
|
|
|
|
page_update_max_trx_id(new_page, page_get_max_trx_id(page));
|
|
|
|
btr_search_move_or_delete_hash_entries(new_page, page, index);
|
|
}
|
|
|
|
/*****************************************************************
|
|
Copies records from page to new_page, up to the given record,
|
|
NOT including that record. Infimum and supremum records are not copied.
|
|
The records are copied to the end of the record list on new_page. */
|
|
|
|
void
|
|
page_copy_rec_list_start(
|
|
/*=====================*/
|
|
page_t* new_page, /* in: index page to copy to */
|
|
page_t* page, /* in: index page */
|
|
rec_t* rec, /* in: record on page */
|
|
dict_index_t* index, /* in: record descriptor */
|
|
mtr_t* mtr) /* in: mtr */
|
|
{
|
|
page_cur_t cur1;
|
|
page_cur_t cur2;
|
|
rec_t* old_end;
|
|
mem_heap_t* heap = NULL;
|
|
ulint offsets_[REC_OFFS_NORMAL_SIZE];
|
|
ulint* offsets = offsets_;
|
|
*offsets_ = (sizeof offsets_) / sizeof *offsets_;
|
|
|
|
page_cur_set_before_first(page, &cur1);
|
|
|
|
if (rec == page_cur_get_rec(&cur1)) {
|
|
|
|
return;
|
|
}
|
|
|
|
page_cur_move_to_next(&cur1);
|
|
|
|
page_cur_set_after_last(new_page, &cur2);
|
|
page_cur_move_to_prev(&cur2);
|
|
old_end = page_cur_get_rec(&cur2);
|
|
|
|
/* Copy records from the original page to the new page */
|
|
|
|
while (page_cur_get_rec(&cur1) != rec) {
|
|
rec_t* ins_rec;
|
|
rec_t* cur1_rec = page_cur_get_rec(&cur1);
|
|
offsets = rec_get_offsets(cur1_rec, index, offsets,
|
|
ULINT_UNDEFINED, &heap);
|
|
ins_rec = page_cur_rec_insert(&cur2, cur1_rec, index,
|
|
offsets, mtr);
|
|
ut_a(ins_rec);
|
|
|
|
page_cur_move_to_next(&cur1);
|
|
page_cur_move_to_next(&cur2);
|
|
}
|
|
|
|
/* Update the lock table, MAX_TRX_ID, and possible hash index */
|
|
|
|
lock_move_rec_list_start(new_page, page, rec, old_end);
|
|
|
|
page_update_max_trx_id(new_page, page_get_max_trx_id(page));
|
|
|
|
btr_search_move_or_delete_hash_entries(new_page, page, index);
|
|
|
|
if (UNIV_LIKELY_NULL(heap)) {
|
|
mem_heap_free(heap);
|
|
}
|
|
}
|
|
|
|
/**************************************************************
|
|
Writes a log record of a record list end or start deletion. */
|
|
UNIV_INLINE
|
|
void
|
|
page_delete_rec_list_write_log(
|
|
/*===========================*/
|
|
rec_t* rec, /* in: record on page */
|
|
dict_index_t* index, /* in: record descriptor */
|
|
byte type, /* in: operation type:
|
|
MLOG_LIST_END_DELETE, ... */
|
|
mtr_t* mtr) /* in: mtr */
|
|
{
|
|
byte* log_ptr;
|
|
ut_ad(type == MLOG_LIST_END_DELETE
|
|
|| type == MLOG_LIST_START_DELETE
|
|
|| type == MLOG_COMP_LIST_END_DELETE
|
|
|| type == MLOG_COMP_LIST_START_DELETE);
|
|
|
|
log_ptr = mlog_open_and_write_index(mtr, rec, index, type, 2);
|
|
if (log_ptr) {
|
|
/* Write the parameter as a 2-byte ulint */
|
|
mach_write_to_2(log_ptr, page_offset(rec));
|
|
mlog_close(mtr, log_ptr + 2);
|
|
}
|
|
}
|
|
|
|
/**************************************************************
|
|
Parses a log record of a record list end or start deletion. */
|
|
|
|
byte*
|
|
page_parse_delete_rec_list(
|
|
/*=======================*/
|
|
/* out: end of log record or NULL */
|
|
byte type, /* in: MLOG_LIST_END_DELETE,
|
|
MLOG_LIST_START_DELETE,
|
|
MLOG_COMP_LIST_END_DELETE or
|
|
MLOG_COMP_LIST_START_DELETE */
|
|
byte* ptr, /* in: buffer */
|
|
byte* end_ptr,/* in: buffer end */
|
|
dict_index_t* index, /* in: record descriptor */
|
|
page_t* page, /* in: page or NULL */
|
|
mtr_t* mtr) /* in: mtr or NULL */
|
|
{
|
|
ulint offset;
|
|
|
|
ut_ad(type == MLOG_LIST_END_DELETE
|
|
|| type == MLOG_LIST_START_DELETE
|
|
|| type == MLOG_COMP_LIST_END_DELETE
|
|
|| type == MLOG_COMP_LIST_START_DELETE);
|
|
|
|
/* Read the record offset as a 2-byte ulint */
|
|
|
|
if (end_ptr < ptr + 2) {
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
offset = mach_read_from_2(ptr);
|
|
ptr += 2;
|
|
|
|
if (!page) {
|
|
|
|
return(ptr);
|
|
}
|
|
|
|
ut_ad(!!page_is_comp(page) == dict_table_is_comp(index->table));
|
|
|
|
if (type == MLOG_LIST_END_DELETE
|
|
|| type == MLOG_COMP_LIST_END_DELETE) {
|
|
page_delete_rec_list_end(page, page + offset, index,
|
|
ULINT_UNDEFINED,
|
|
ULINT_UNDEFINED, mtr);
|
|
} else {
|
|
page_delete_rec_list_start(page, page + offset, index, mtr);
|
|
}
|
|
|
|
return(ptr);
|
|
}
|
|
|
|
/*****************************************************************
|
|
Deletes records from a page from a given record onward, including that record.
|
|
The infimum and supremum records are not deleted. */
|
|
|
|
void
|
|
page_delete_rec_list_end(
|
|
/*=====================*/
|
|
page_t* page, /* in: index page */
|
|
rec_t* rec, /* in: record on page */
|
|
dict_index_t* index, /* in: record descriptor */
|
|
ulint n_recs, /* in: number of records to delete,
|
|
or ULINT_UNDEFINED if not known */
|
|
ulint size, /* in: the sum of the sizes of the
|
|
records in the end of the chain to
|
|
delete, or ULINT_UNDEFINED if not known */
|
|
mtr_t* mtr) /* in: mtr */
|
|
{
|
|
page_dir_slot_t* slot;
|
|
ulint slot_index;
|
|
rec_t* last_rec;
|
|
rec_t* prev_rec;
|
|
rec_t* free;
|
|
rec_t* rec2;
|
|
ulint count;
|
|
ulint n_owned;
|
|
rec_t* sup;
|
|
ulint comp;
|
|
|
|
/* Reset the last insert info in the page header and increment
|
|
the modify clock for the frame */
|
|
|
|
ut_ad(size == ULINT_UNDEFINED || size < UNIV_PAGE_SIZE);
|
|
page_header_set_ptr(page, PAGE_LAST_INSERT, NULL);
|
|
|
|
/* The page gets invalid for optimistic searches: increment the
|
|
frame modify clock */
|
|
|
|
buf_frame_modify_clock_inc(page);
|
|
|
|
sup = page_get_supremum_rec(page);
|
|
|
|
comp = page_is_comp(page);
|
|
if (page_rec_is_infimum_low(rec - page)) {
|
|
rec = page_rec_get_next(rec);
|
|
}
|
|
|
|
page_delete_rec_list_write_log(rec, index, comp
|
|
? MLOG_COMP_LIST_END_DELETE
|
|
: MLOG_LIST_END_DELETE, mtr);
|
|
|
|
if (rec == sup) {
|
|
|
|
return;
|
|
}
|
|
|
|
prev_rec = page_rec_get_prev(rec);
|
|
|
|
last_rec = page_rec_get_prev(sup);
|
|
|
|
if ((size == ULINT_UNDEFINED) || (n_recs == ULINT_UNDEFINED)) {
|
|
mem_heap_t* heap = NULL;
|
|
ulint offsets_[REC_OFFS_NORMAL_SIZE];
|
|
ulint* offsets = offsets_;
|
|
*offsets_ = (sizeof offsets_) / sizeof *offsets_;
|
|
/* Calculate the sum of sizes and the number of records */
|
|
size = 0;
|
|
n_recs = 0;
|
|
rec2 = rec;
|
|
|
|
while (rec2 != sup) {
|
|
ulint s;
|
|
offsets = rec_get_offsets(rec2, index, offsets,
|
|
ULINT_UNDEFINED, &heap);
|
|
s = rec_offs_size(offsets);
|
|
ut_ad(rec2 - page + s - rec_offs_extra_size(offsets)
|
|
< UNIV_PAGE_SIZE);
|
|
ut_ad(size + s < UNIV_PAGE_SIZE);
|
|
size += s;
|
|
n_recs++;
|
|
|
|
rec2 = page_rec_get_next(rec2);
|
|
}
|
|
|
|
if (UNIV_LIKELY_NULL(heap)) {
|
|
mem_heap_free(heap);
|
|
}
|
|
}
|
|
|
|
ut_ad(size < UNIV_PAGE_SIZE);
|
|
|
|
/* Update the page directory; there is no need to balance the number
|
|
of the records owned by the supremum record, as it is allowed to be
|
|
less than PAGE_DIR_SLOT_MIN_N_OWNED */
|
|
|
|
rec2 = rec;
|
|
count = 0;
|
|
|
|
while (rec_get_n_owned(rec2, comp) == 0) {
|
|
count++;
|
|
|
|
rec2 = page_rec_get_next(rec2);
|
|
}
|
|
|
|
ut_ad(rec_get_n_owned(rec2, comp) - count > 0);
|
|
|
|
n_owned = rec_get_n_owned(rec2, comp) - count;
|
|
|
|
slot_index = page_dir_find_owner_slot(rec2);
|
|
slot = page_dir_get_nth_slot(page, slot_index);
|
|
|
|
page_dir_slot_set_rec(slot, sup);
|
|
page_dir_slot_set_n_owned(slot, n_owned);
|
|
|
|
page_dir_set_n_slots(page, slot_index + 1);
|
|
|
|
/* Remove the record chain segment from the record chain */
|
|
page_rec_set_next(prev_rec, page_get_supremum_rec(page));
|
|
|
|
/* Catenate the deleted chain segment to the page free list */
|
|
|
|
free = page_header_get_ptr(page, PAGE_FREE);
|
|
|
|
page_rec_set_next(last_rec, free);
|
|
page_header_set_ptr(page, PAGE_FREE, rec);
|
|
|
|
page_header_set_field(page, PAGE_GARBAGE, size
|
|
+ page_header_get_field(page, PAGE_GARBAGE));
|
|
|
|
page_header_set_field(page, PAGE_N_RECS,
|
|
(ulint)(page_get_n_recs(page) - n_recs));
|
|
}
|
|
|
|
/*****************************************************************
|
|
Deletes records from page, up to the given record, NOT including
|
|
that record. Infimum and supremum records are not deleted. */
|
|
|
|
void
|
|
page_delete_rec_list_start(
|
|
/*=======================*/
|
|
page_t* page, /* in: index page */
|
|
rec_t* rec, /* in: record on page */
|
|
dict_index_t* index, /* in: record descriptor */
|
|
mtr_t* mtr) /* in: mtr */
|
|
{
|
|
page_cur_t cur1;
|
|
ulint log_mode;
|
|
ulint offsets_[REC_OFFS_NORMAL_SIZE];
|
|
ulint* offsets = offsets_;
|
|
mem_heap_t* heap = NULL;
|
|
byte type;
|
|
*offsets_ = (sizeof offsets_) / sizeof *offsets_;
|
|
|
|
ut_ad(!!page_is_comp(page) == dict_table_is_comp(index->table));
|
|
|
|
if (page_is_comp(page)) {
|
|
type = MLOG_COMP_LIST_START_DELETE;
|
|
} else {
|
|
type = MLOG_LIST_START_DELETE;
|
|
}
|
|
|
|
page_delete_rec_list_write_log(rec, index, type, mtr);
|
|
|
|
page_cur_set_before_first(page, &cur1);
|
|
|
|
if (rec == page_cur_get_rec(&cur1)) {
|
|
|
|
return;
|
|
}
|
|
|
|
page_cur_move_to_next(&cur1);
|
|
|
|
/* Individual deletes are not logged */
|
|
|
|
log_mode = mtr_set_log_mode(mtr, MTR_LOG_NONE);
|
|
|
|
while (page_cur_get_rec(&cur1) != rec) {
|
|
offsets = rec_get_offsets(page_cur_get_rec(&cur1), index,
|
|
offsets, ULINT_UNDEFINED, &heap);
|
|
page_cur_delete_rec(&cur1, index, offsets, mtr);
|
|
}
|
|
|
|
if (UNIV_LIKELY_NULL(heap)) {
|
|
mem_heap_free(heap);
|
|
}
|
|
|
|
/* Restore log mode */
|
|
|
|
mtr_set_log_mode(mtr, log_mode);
|
|
}
|
|
|
|
/*****************************************************************
|
|
Moves record list end to another page. Moved records include
|
|
split_rec. */
|
|
|
|
void
|
|
page_move_rec_list_end(
|
|
/*===================*/
|
|
page_t* new_page, /* in: index page where to move */
|
|
page_t* page, /* in: index page */
|
|
rec_t* split_rec, /* in: first record to move */
|
|
dict_index_t* index, /* in: record descriptor */
|
|
mtr_t* mtr) /* in: mtr */
|
|
{
|
|
ulint old_data_size;
|
|
ulint new_data_size;
|
|
ulint old_n_recs;
|
|
ulint new_n_recs;
|
|
|
|
old_data_size = page_get_data_size(new_page);
|
|
old_n_recs = page_get_n_recs(new_page);
|
|
|
|
page_copy_rec_list_end(new_page, page, split_rec, index, mtr);
|
|
|
|
new_data_size = page_get_data_size(new_page);
|
|
new_n_recs = page_get_n_recs(new_page);
|
|
|
|
ut_ad(new_data_size >= old_data_size);
|
|
|
|
page_delete_rec_list_end(page, split_rec, index,
|
|
new_n_recs - old_n_recs,
|
|
new_data_size - old_data_size, mtr);
|
|
}
|
|
|
|
/*****************************************************************
|
|
Moves record list start to another page. Moved records do not include
|
|
split_rec. */
|
|
|
|
void
|
|
page_move_rec_list_start(
|
|
/*=====================*/
|
|
page_t* new_page, /* in: index page where to move */
|
|
page_t* page, /* in: index page */
|
|
rec_t* split_rec, /* in: first record not to move */
|
|
dict_index_t* index, /* in: record descriptor */
|
|
mtr_t* mtr) /* in: mtr */
|
|
{
|
|
page_copy_rec_list_start(new_page, page, split_rec, index, mtr);
|
|
|
|
page_delete_rec_list_start(page, split_rec, index, mtr);
|
|
}
|
|
|
|
/***************************************************************************
|
|
This is a low-level operation which is used in a database index creation
|
|
to update the page number of a created B-tree to a data dictionary record. */
|
|
|
|
void
|
|
page_rec_write_index_page_no(
|
|
/*=========================*/
|
|
rec_t* rec, /* in: record to update */
|
|
ulint i, /* in: index of the field to update */
|
|
ulint page_no,/* in: value to write */
|
|
mtr_t* mtr) /* in: mtr */
|
|
{
|
|
byte* data;
|
|
ulint len;
|
|
|
|
data = rec_get_nth_field_old(rec, i, &len);
|
|
|
|
ut_ad(len == 4);
|
|
|
|
mlog_write_ulint(data, page_no, MLOG_4BYTES, mtr);
|
|
}
|
|
|
|
/******************************************************************
|
|
Used to delete n slots from the directory. This function updates
|
|
also n_owned fields in the records, so that the first slot after
|
|
the deleted ones inherits the records of the deleted slots. */
|
|
UNIV_INLINE
|
|
void
|
|
page_dir_delete_slots(
|
|
/*==================*/
|
|
page_t* page, /* in: the index page */
|
|
ulint start, /* in: first slot to be deleted */
|
|
ulint n) /* in: number of slots to delete (currently
|
|
only n == 1 allowed) */
|
|
{
|
|
page_dir_slot_t* slot;
|
|
ulint i;
|
|
ulint sum_owned = 0;
|
|
ulint n_slots;
|
|
rec_t* rec;
|
|
|
|
ut_ad(n == 1);
|
|
ut_ad(start > 0);
|
|
ut_ad(start + n < page_dir_get_n_slots(page));
|
|
|
|
n_slots = page_dir_get_n_slots(page);
|
|
|
|
/* 1. Reset the n_owned fields of the slots to be
|
|
deleted */
|
|
for (i = start; i < start + n; i++) {
|
|
slot = page_dir_get_nth_slot(page, i);
|
|
sum_owned += page_dir_slot_get_n_owned(slot);
|
|
page_dir_slot_set_n_owned(slot, 0);
|
|
}
|
|
|
|
/* 2. Update the n_owned value of the first non-deleted slot */
|
|
|
|
slot = page_dir_get_nth_slot(page, start + n);
|
|
page_dir_slot_set_n_owned(slot,
|
|
sum_owned + page_dir_slot_get_n_owned(slot));
|
|
|
|
/* 3. Destroy start and other slots by copying slots */
|
|
for (i = start + n; i < n_slots; i++) {
|
|
slot = page_dir_get_nth_slot(page, i);
|
|
rec = page_dir_slot_get_rec(slot);
|
|
|
|
slot = page_dir_get_nth_slot(page, i - n);
|
|
page_dir_slot_set_rec(slot, rec);
|
|
}
|
|
|
|
/* 4. Update the page header */
|
|
page_header_set_field(page, PAGE_N_DIR_SLOTS, n_slots - n);
|
|
}
|
|
|
|
/******************************************************************
|
|
Used to add n slots to the directory. Does not set the record pointers
|
|
in the added slots or update n_owned values: this is the responsibility
|
|
of the caller. */
|
|
UNIV_INLINE
|
|
void
|
|
page_dir_add_slots(
|
|
/*===============*/
|
|
page_t* page, /* in: the index page */
|
|
ulint start, /* in: the slot above which the new slots are added */
|
|
ulint n) /* in: number of slots to add (currently only n == 1
|
|
allowed) */
|
|
{
|
|
page_dir_slot_t* slot;
|
|
ulint n_slots;
|
|
ulint i;
|
|
rec_t* rec;
|
|
|
|
ut_ad(n == 1);
|
|
|
|
n_slots = page_dir_get_n_slots(page);
|
|
|
|
ut_ad(start < n_slots - 1);
|
|
|
|
/* Update the page header */
|
|
page_dir_set_n_slots(page, n_slots + n);
|
|
|
|
/* Move slots up */
|
|
|
|
for (i = n_slots - 1; i > start; i--) {
|
|
|
|
slot = page_dir_get_nth_slot(page, i);
|
|
rec = page_dir_slot_get_rec(slot);
|
|
|
|
slot = page_dir_get_nth_slot(page, i + n);
|
|
page_dir_slot_set_rec(slot, rec);
|
|
}
|
|
}
|
|
|
|
/********************************************************************
|
|
Splits a directory slot which owns too many records. */
|
|
|
|
void
|
|
page_dir_split_slot(
|
|
/*================*/
|
|
page_t* page, /* in: the index page in question */
|
|
ulint slot_no) /* in: the directory slot */
|
|
{
|
|
rec_t* rec;
|
|
page_dir_slot_t* new_slot;
|
|
page_dir_slot_t* prev_slot;
|
|
page_dir_slot_t* slot;
|
|
ulint i;
|
|
ulint n_owned;
|
|
|
|
ut_ad(page);
|
|
ut_ad(slot_no > 0);
|
|
|
|
slot = page_dir_get_nth_slot(page, slot_no);
|
|
|
|
n_owned = page_dir_slot_get_n_owned(slot);
|
|
ut_ad(n_owned == PAGE_DIR_SLOT_MAX_N_OWNED + 1);
|
|
|
|
/* 1. We loop to find a record approximately in the middle of the
|
|
records owned by the slot. */
|
|
|
|
prev_slot = page_dir_get_nth_slot(page, slot_no - 1);
|
|
rec = page_dir_slot_get_rec(prev_slot);
|
|
|
|
for (i = 0; i < n_owned / 2; i++) {
|
|
rec = page_rec_get_next(rec);
|
|
}
|
|
|
|
ut_ad(n_owned / 2 >= PAGE_DIR_SLOT_MIN_N_OWNED);
|
|
|
|
/* 2. We add one directory slot immediately below the slot to be
|
|
split. */
|
|
|
|
page_dir_add_slots(page, slot_no - 1, 1);
|
|
|
|
/* The added slot is now number slot_no, and the old slot is
|
|
now number slot_no + 1 */
|
|
|
|
new_slot = page_dir_get_nth_slot(page, slot_no);
|
|
slot = page_dir_get_nth_slot(page, slot_no + 1);
|
|
|
|
/* 3. We store the appropriate values to the new slot. */
|
|
|
|
page_dir_slot_set_rec(new_slot, rec);
|
|
page_dir_slot_set_n_owned(new_slot, n_owned / 2);
|
|
|
|
/* 4. Finally, we update the number of records field of the
|
|
original slot */
|
|
|
|
page_dir_slot_set_n_owned(slot, n_owned - (n_owned / 2));
|
|
}
|
|
|
|
/*****************************************************************
|
|
Tries to balance the given directory slot with too few records with the upper
|
|
neighbor, so that there are at least the minimum number of records owned by
|
|
the slot; this may result in the merging of two slots. */
|
|
|
|
void
|
|
page_dir_balance_slot(
|
|
/*==================*/
|
|
page_t* page, /* in: index page */
|
|
ulint slot_no) /* in: the directory slot */
|
|
{
|
|
page_dir_slot_t* slot;
|
|
page_dir_slot_t* up_slot;
|
|
ulint n_owned;
|
|
ulint up_n_owned;
|
|
rec_t* old_rec;
|
|
rec_t* new_rec;
|
|
|
|
ut_ad(page);
|
|
ut_ad(slot_no > 0);
|
|
|
|
slot = page_dir_get_nth_slot(page, slot_no);
|
|
|
|
/* The last directory slot cannot be balanced with the upper
|
|
neighbor, as there is none. */
|
|
|
|
if (slot_no == page_dir_get_n_slots(page) - 1) {
|
|
|
|
return;
|
|
}
|
|
|
|
up_slot = page_dir_get_nth_slot(page, slot_no + 1);
|
|
|
|
n_owned = page_dir_slot_get_n_owned(slot);
|
|
up_n_owned = page_dir_slot_get_n_owned(up_slot);
|
|
|
|
ut_ad(n_owned == PAGE_DIR_SLOT_MIN_N_OWNED - 1);
|
|
|
|
/* If the upper slot has the minimum value of n_owned, we will merge
|
|
the two slots, therefore we assert: */
|
|
ut_ad(2 * PAGE_DIR_SLOT_MIN_N_OWNED - 1 <= PAGE_DIR_SLOT_MAX_N_OWNED);
|
|
|
|
if (up_n_owned > PAGE_DIR_SLOT_MIN_N_OWNED) {
|
|
|
|
/* In this case we can just transfer one record owned
|
|
by the upper slot to the property of the lower slot */
|
|
old_rec = page_dir_slot_get_rec(slot);
|
|
new_rec = page_rec_get_next(old_rec);
|
|
|
|
rec_set_n_owned(old_rec, page_is_comp(page), 0);
|
|
rec_set_n_owned(new_rec, page_is_comp(page), n_owned + 1);
|
|
|
|
page_dir_slot_set_rec(slot, new_rec);
|
|
|
|
page_dir_slot_set_n_owned(up_slot, up_n_owned -1);
|
|
} else {
|
|
/* In this case we may merge the two slots */
|
|
page_dir_delete_slots(page, slot_no, 1);
|
|
}
|
|
}
|
|
|
|
/****************************************************************
|
|
Returns the middle record of the record list. If there are an even number
|
|
of records in the list, returns the first record of the upper half-list. */
|
|
|
|
rec_t*
|
|
page_get_middle_rec(
|
|
/*================*/
|
|
/* out: middle record */
|
|
page_t* page) /* in: page */
|
|
{
|
|
page_dir_slot_t* slot;
|
|
ulint middle;
|
|
ulint i;
|
|
ulint n_owned;
|
|
ulint count;
|
|
rec_t* rec;
|
|
|
|
/* This many records we must leave behind */
|
|
middle = (page_get_n_recs(page) + 2) / 2;
|
|
|
|
count = 0;
|
|
|
|
for (i = 0;; i++) {
|
|
|
|
slot = page_dir_get_nth_slot(page, i);
|
|
n_owned = page_dir_slot_get_n_owned(slot);
|
|
|
|
if (count + n_owned > middle) {
|
|
break;
|
|
} else {
|
|
count += n_owned;
|
|
}
|
|
}
|
|
|
|
ut_ad(i > 0);
|
|
slot = page_dir_get_nth_slot(page, i - 1);
|
|
rec = page_dir_slot_get_rec(slot);
|
|
rec = page_rec_get_next(rec);
|
|
|
|
/* There are now count records behind rec */
|
|
|
|
for (i = 0; i < middle - count; i++) {
|
|
rec = page_rec_get_next(rec);
|
|
}
|
|
|
|
return(rec);
|
|
}
|
|
|
|
/*******************************************************************
|
|
Returns the number of records before the given record in chain.
|
|
The number includes infimum and supremum records. */
|
|
|
|
ulint
|
|
page_rec_get_n_recs_before(
|
|
/*=======================*/
|
|
/* out: number of records */
|
|
rec_t* rec) /* in: the physical record */
|
|
{
|
|
page_dir_slot_t* slot;
|
|
rec_t* slot_rec;
|
|
page_t* page;
|
|
ulint i;
|
|
ulint comp;
|
|
lint n = 0;
|
|
|
|
ut_ad(page_rec_check(rec));
|
|
|
|
page = buf_frame_align(rec);
|
|
comp = page_is_comp(page);
|
|
|
|
while (rec_get_n_owned(rec, comp) == 0) {
|
|
|
|
rec = page_rec_get_next(rec);
|
|
n--;
|
|
}
|
|
|
|
for (i = 0; ; i++) {
|
|
slot = page_dir_get_nth_slot(page, i);
|
|
slot_rec = page_dir_slot_get_rec(slot);
|
|
|
|
n += rec_get_n_owned(slot_rec, comp);
|
|
|
|
if (rec == slot_rec) {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
n--;
|
|
|
|
ut_ad(n >= 0);
|
|
|
|
return((ulint) n);
|
|
}
|
|
|
|
/****************************************************************
|
|
Prints record contents including the data relevant only in
|
|
the index page context. */
|
|
|
|
void
|
|
page_rec_print(
|
|
/*===========*/
|
|
rec_t* rec, /* in: physical record */
|
|
const ulint* offsets)/* in: record descriptor */
|
|
{
|
|
ulint comp = page_is_comp(buf_frame_align(rec));
|
|
|
|
ut_a(!comp == !rec_offs_comp(offsets));
|
|
rec_print_new(stderr, rec, offsets);
|
|
fprintf(stderr,
|
|
" n_owned: %lu; heap_no: %lu; next rec: %lu\n",
|
|
(ulong) rec_get_n_owned(rec, comp),
|
|
(ulong) rec_get_heap_no(rec, comp),
|
|
(ulong) rec_get_next_offs(rec, comp));
|
|
|
|
page_rec_check(rec);
|
|
rec_validate(rec, offsets);
|
|
}
|
|
|
|
/*******************************************************************
|
|
This is used to print the contents of the directory for
|
|
debugging purposes. */
|
|
|
|
void
|
|
page_dir_print(
|
|
/*===========*/
|
|
page_t* page, /* in: index page */
|
|
ulint pr_n) /* in: print n first and n last entries */
|
|
{
|
|
ulint n;
|
|
ulint i;
|
|
page_dir_slot_t* slot;
|
|
|
|
n = page_dir_get_n_slots(page);
|
|
|
|
fprintf(stderr, "--------------------------------\n"
|
|
"PAGE DIRECTORY\n"
|
|
"Page address %p\n"
|
|
"Directory stack top at offs: %lu; number of slots: %lu\n",
|
|
page, (ulong)(page_dir_get_nth_slot(page, n - 1) - page),
|
|
(ulong) n);
|
|
for (i = 0; i < n; i++) {
|
|
slot = page_dir_get_nth_slot(page, i);
|
|
if ((i == pr_n) && (i < n - pr_n)) {
|
|
fputs(" ... \n", stderr);
|
|
}
|
|
if ((i < pr_n) || (i >= n - pr_n)) {
|
|
fprintf(stderr,
|
|
"Contents of slot: %lu: n_owned: %lu,"
|
|
" rec offs: %lu\n",
|
|
(ulong) i,
|
|
(ulong) page_dir_slot_get_n_owned(slot),
|
|
(ulong)(page_dir_slot_get_rec(slot) - page));
|
|
}
|
|
}
|
|
fprintf(stderr, "Total of %lu records\n"
|
|
"--------------------------------\n",
|
|
(ulong) (2 + page_get_n_recs(page)));
|
|
}
|
|
|
|
/*******************************************************************
|
|
This is used to print the contents of the page record list for
|
|
debugging purposes. */
|
|
|
|
void
|
|
page_print_list(
|
|
/*============*/
|
|
page_t* page, /* in: index page */
|
|
dict_index_t* index, /* in: dictionary index of the page */
|
|
ulint pr_n) /* in: print n first and n last entries */
|
|
{
|
|
page_cur_t cur;
|
|
ulint count;
|
|
ulint n_recs;
|
|
mem_heap_t* heap = NULL;
|
|
ulint offsets_[REC_OFFS_NORMAL_SIZE];
|
|
ulint* offsets = offsets_;
|
|
*offsets_ = (sizeof offsets_) / sizeof *offsets_;
|
|
|
|
ut_a((ibool)!!page_is_comp(page) == dict_table_is_comp(index->table));
|
|
|
|
fprintf(stderr,
|
|
"--------------------------------\n"
|
|
"PAGE RECORD LIST\n"
|
|
"Page address %p\n", page);
|
|
|
|
n_recs = page_get_n_recs(page);
|
|
|
|
page_cur_set_before_first(page, &cur);
|
|
count = 0;
|
|
for (;;) {
|
|
offsets = rec_get_offsets(cur.rec, index, offsets,
|
|
ULINT_UNDEFINED, &heap);
|
|
page_rec_print(cur.rec, offsets);
|
|
|
|
if (count == pr_n) {
|
|
break;
|
|
}
|
|
if (page_cur_is_after_last(&cur)) {
|
|
break;
|
|
}
|
|
page_cur_move_to_next(&cur);
|
|
count++;
|
|
}
|
|
|
|
if (n_recs > 2 * pr_n) {
|
|
fputs(" ... \n", stderr);
|
|
}
|
|
|
|
while (!page_cur_is_after_last(&cur)) {
|
|
page_cur_move_to_next(&cur);
|
|
|
|
if (count + pr_n >= n_recs) {
|
|
offsets = rec_get_offsets(cur.rec, index, offsets,
|
|
ULINT_UNDEFINED, &heap);
|
|
page_rec_print(cur.rec, offsets);
|
|
}
|
|
count++;
|
|
}
|
|
|
|
fprintf(stderr,
|
|
"Total of %lu records \n"
|
|
"--------------------------------\n",
|
|
(ulong) (count + 1));
|
|
|
|
if (UNIV_LIKELY_NULL(heap)) {
|
|
mem_heap_free(heap);
|
|
}
|
|
}
|
|
|
|
/*******************************************************************
|
|
Prints the info in a page header. */
|
|
|
|
void
|
|
page_header_print(
|
|
/*==============*/
|
|
page_t* page)
|
|
{
|
|
fprintf(stderr,
|
|
"--------------------------------\n"
|
|
"PAGE HEADER INFO\n"
|
|
"Page address %p, n records %lu (%s)\n"
|
|
"n dir slots %lu, heap top %lu\n"
|
|
"Page n heap %lu, free %lu, garbage %lu\n"
|
|
"Page last insert %lu, direction %lu, n direction %lu\n",
|
|
page, (ulong) page_header_get_field(page, PAGE_N_RECS),
|
|
page_is_comp(page) ? "compact format" : "original format",
|
|
(ulong) page_header_get_field(page, PAGE_N_DIR_SLOTS),
|
|
(ulong) page_header_get_field(page, PAGE_HEAP_TOP),
|
|
(ulong) page_dir_get_n_heap(page),
|
|
(ulong) page_header_get_field(page, PAGE_FREE),
|
|
(ulong) page_header_get_field(page, PAGE_GARBAGE),
|
|
(ulong) page_header_get_field(page, PAGE_LAST_INSERT),
|
|
(ulong) page_header_get_field(page, PAGE_DIRECTION),
|
|
(ulong) page_header_get_field(page, PAGE_N_DIRECTION));
|
|
}
|
|
|
|
/*******************************************************************
|
|
This is used to print the contents of the page for
|
|
debugging purposes. */
|
|
|
|
void
|
|
page_print(
|
|
/*=======*/
|
|
page_t* page, /* in: index page */
|
|
dict_index_t* index, /* in: dictionary index of the page */
|
|
ulint dn, /* in: print dn first and last entries
|
|
in directory */
|
|
ulint rn) /* in: print rn first and last records
|
|
in directory */
|
|
{
|
|
page_header_print(page);
|
|
page_dir_print(page, dn);
|
|
page_print_list(page, index, rn);
|
|
}
|
|
|
|
/*******************************************************************
|
|
The following is used to validate a record on a page. This function
|
|
differs from rec_validate as it can also check the n_owned field and
|
|
the heap_no field. */
|
|
|
|
ibool
|
|
page_rec_validate(
|
|
/*==============*/
|
|
/* out: TRUE if ok */
|
|
rec_t* rec, /* in: physical record */
|
|
const ulint* offsets)/* in: array returned by rec_get_offsets() */
|
|
{
|
|
ulint n_owned;
|
|
ulint heap_no;
|
|
page_t* page;
|
|
ulint comp;
|
|
|
|
page = buf_frame_align(rec);
|
|
comp = page_is_comp(page);
|
|
ut_a(!comp == !rec_offs_comp(offsets));
|
|
|
|
page_rec_check(rec);
|
|
rec_validate(rec, offsets);
|
|
|
|
n_owned = rec_get_n_owned(rec, comp);
|
|
heap_no = rec_get_heap_no(rec, comp);
|
|
|
|
if (!(n_owned <= PAGE_DIR_SLOT_MAX_N_OWNED)) {
|
|
fprintf(stderr,
|
|
"InnoDB: Dir slot of rec %lu, n owned too big %lu\n",
|
|
(ulong)(rec - page), (ulong) n_owned);
|
|
return(FALSE);
|
|
}
|
|
|
|
if (!(heap_no < page_dir_get_n_heap(page))) {
|
|
fprintf(stderr,
|
|
"InnoDB: Heap no of rec %lu too big %lu %lu\n",
|
|
(ulong)(rec - page), (ulong) heap_no,
|
|
(ulong) page_dir_get_n_heap(page));
|
|
return(FALSE);
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
/*******************************************************************
|
|
Checks that the first directory slot points to the infimum record and
|
|
the last to the supremum. This function is intended to track if the
|
|
bug fixed in 4.0.14 has caused corruption to users' databases. */
|
|
|
|
void
|
|
page_check_dir(
|
|
/*===========*/
|
|
page_t* page) /* in: index page */
|
|
{
|
|
ulint n_slots;
|
|
|
|
n_slots = page_dir_get_n_slots(page);
|
|
|
|
if (page_dir_slot_get_rec(page_dir_get_nth_slot(page, 0))
|
|
!= page_get_infimum_rec(page)) {
|
|
|
|
fprintf(stderr,
|
|
"InnoDB: Page directory corruption:"
|
|
" infimum not pointed to\n");
|
|
buf_page_print(page);
|
|
}
|
|
|
|
if (page_dir_slot_get_rec(page_dir_get_nth_slot(page, n_slots - 1))
|
|
!= page_get_supremum_rec(page)) {
|
|
|
|
fprintf(stderr,
|
|
"InnoDB: Page directory corruption:"
|
|
" supremum not pointed to\n");
|
|
buf_page_print(page);
|
|
}
|
|
}
|
|
|
|
/*******************************************************************
|
|
This function checks the consistency of an index page when we do not
|
|
know the index. This is also resilient so that this should never crash
|
|
even if the page is total garbage. */
|
|
|
|
ibool
|
|
page_simple_validate(
|
|
/*=================*/
|
|
/* out: TRUE if ok */
|
|
page_t* page) /* in: index page */
|
|
{
|
|
page_cur_t cur;
|
|
page_dir_slot_t* slot;
|
|
ulint slot_no;
|
|
ulint n_slots;
|
|
rec_t* rec;
|
|
byte* rec_heap_top;
|
|
ulint count;
|
|
ulint own_count;
|
|
ibool ret = FALSE;
|
|
ulint comp = page_is_comp(page);
|
|
|
|
/* Check first that the record heap and the directory do not
|
|
overlap. */
|
|
|
|
n_slots = page_dir_get_n_slots(page);
|
|
|
|
if (n_slots > UNIV_PAGE_SIZE / 4) {
|
|
fprintf(stderr,
|
|
"InnoDB: Nonsensical number %lu of page dir slots\n",
|
|
(ulong) n_slots);
|
|
|
|
goto func_exit;
|
|
}
|
|
|
|
rec_heap_top = page_header_get_ptr(page, PAGE_HEAP_TOP);
|
|
|
|
if (rec_heap_top > page_dir_get_nth_slot(page, n_slots - 1)) {
|
|
|
|
fprintf(stderr,
|
|
"InnoDB: Record heap and dir overlap on a page,"
|
|
" heap top %lu, dir %lu\n",
|
|
(ulong)
|
|
(page_header_get_ptr(page, PAGE_HEAP_TOP) - page),
|
|
(ulong)
|
|
(page_dir_get_nth_slot(page, n_slots - 1) - page));
|
|
|
|
goto func_exit;
|
|
}
|
|
|
|
/* Validate the record list in a loop checking also that it is
|
|
consistent with the page record directory. */
|
|
|
|
count = 0;
|
|
own_count = 1;
|
|
slot_no = 0;
|
|
slot = page_dir_get_nth_slot(page, slot_no);
|
|
|
|
page_cur_set_before_first(page, &cur);
|
|
|
|
for (;;) {
|
|
rec = (&cur)->rec;
|
|
|
|
if (rec > rec_heap_top) {
|
|
fprintf(stderr,
|
|
"InnoDB: Record %lu is above"
|
|
" rec heap top %lu\n",
|
|
(ulong)(rec - page),
|
|
(ulong)(rec_heap_top - page));
|
|
|
|
goto func_exit;
|
|
}
|
|
|
|
if (rec_get_n_owned(rec, comp) != 0) {
|
|
/* This is a record pointed to by a dir slot */
|
|
if (rec_get_n_owned(rec, comp) != own_count) {
|
|
|
|
fprintf(stderr,
|
|
"InnoDB: Wrong owned count %lu, %lu,"
|
|
" rec %lu\n",
|
|
(ulong) rec_get_n_owned(rec, comp),
|
|
(ulong) own_count,
|
|
(ulong)(rec - page));
|
|
|
|
goto func_exit;
|
|
}
|
|
|
|
if (page_dir_slot_get_rec(slot) != rec) {
|
|
fprintf(stderr,
|
|
"InnoDB: Dir slot does not point"
|
|
" to right rec %lu\n",
|
|
(ulong)(rec - page));
|
|
|
|
goto func_exit;
|
|
}
|
|
|
|
own_count = 0;
|
|
|
|
if (!page_cur_is_after_last(&cur)) {
|
|
slot_no++;
|
|
slot = page_dir_get_nth_slot(page, slot_no);
|
|
}
|
|
}
|
|
|
|
if (page_cur_is_after_last(&cur)) {
|
|
|
|
break;
|
|
}
|
|
|
|
if (rec_get_next_offs(rec, comp) < FIL_PAGE_DATA
|
|
|| rec_get_next_offs(rec, comp) >= UNIV_PAGE_SIZE) {
|
|
fprintf(stderr,
|
|
"InnoDB: Next record offset"
|
|
" nonsensical %lu for rec %lu\n",
|
|
(ulong) rec_get_next_offs(rec, comp),
|
|
(ulong)(rec - page));
|
|
|
|
goto func_exit;
|
|
}
|
|
|
|
count++;
|
|
|
|
if (count > UNIV_PAGE_SIZE) {
|
|
fprintf(stderr,
|
|
"InnoDB: Page record list appears"
|
|
" to be circular %lu\n",
|
|
(ulong) count);
|
|
goto func_exit;
|
|
}
|
|
|
|
page_cur_move_to_next(&cur);
|
|
own_count++;
|
|
}
|
|
|
|
if (rec_get_n_owned(rec, comp) == 0) {
|
|
fprintf(stderr, "InnoDB: n owned is zero in a supremum rec\n");
|
|
|
|
goto func_exit;
|
|
}
|
|
|
|
if (slot_no != n_slots - 1) {
|
|
fprintf(stderr, "InnoDB: n slots wrong %lu, %lu\n",
|
|
(ulong) slot_no, (ulong) (n_slots - 1));
|
|
goto func_exit;
|
|
}
|
|
|
|
if (page_header_get_field(page, PAGE_N_RECS) + 2 != count + 1) {
|
|
fprintf(stderr, "InnoDB: n recs wrong %lu %lu\n",
|
|
(ulong) page_header_get_field(page, PAGE_N_RECS) + 2,
|
|
(ulong) (count + 1));
|
|
|
|
goto func_exit;
|
|
}
|
|
|
|
/* Check then the free list */
|
|
rec = page_header_get_ptr(page, PAGE_FREE);
|
|
|
|
while (rec != NULL) {
|
|
if (rec < page + FIL_PAGE_DATA
|
|
|| rec >= page + UNIV_PAGE_SIZE) {
|
|
fprintf(stderr,
|
|
"InnoDB: Free list record has"
|
|
" a nonsensical offset %lu\n",
|
|
(ulong) (rec - page));
|
|
|
|
goto func_exit;
|
|
}
|
|
|
|
if (rec > rec_heap_top) {
|
|
fprintf(stderr,
|
|
"InnoDB: Free list record %lu"
|
|
" is above rec heap top %lu\n",
|
|
(ulong) (rec - page),
|
|
(ulong) (rec_heap_top - page));
|
|
|
|
goto func_exit;
|
|
}
|
|
|
|
count++;
|
|
|
|
if (count > UNIV_PAGE_SIZE) {
|
|
fprintf(stderr,
|
|
"InnoDB: Page free list appears"
|
|
" to be circular %lu\n",
|
|
(ulong) count);
|
|
goto func_exit;
|
|
}
|
|
|
|
rec = page_rec_get_next(rec);
|
|
}
|
|
|
|
if (page_dir_get_n_heap(page) != count + 1) {
|
|
|
|
fprintf(stderr, "InnoDB: N heap is wrong %lu, %lu\n",
|
|
(ulong) page_dir_get_n_heap(page),
|
|
(ulong) (count + 1));
|
|
|
|
goto func_exit;
|
|
}
|
|
|
|
ret = TRUE;
|
|
|
|
func_exit:
|
|
return(ret);
|
|
}
|
|
|
|
/*******************************************************************
|
|
This function checks the consistency of an index page. */
|
|
|
|
ibool
|
|
page_validate(
|
|
/*==========*/
|
|
/* out: TRUE if ok */
|
|
page_t* page, /* in: index page */
|
|
dict_index_t* index) /* in: data dictionary index containing
|
|
the page record type definition */
|
|
{
|
|
page_dir_slot_t* slot;
|
|
mem_heap_t* heap;
|
|
page_cur_t cur;
|
|
byte* buf;
|
|
ulint count;
|
|
ulint own_count;
|
|
ulint slot_no;
|
|
ulint data_size;
|
|
rec_t* rec;
|
|
rec_t* old_rec = NULL;
|
|
ulint offs;
|
|
ulint n_slots;
|
|
ibool ret = FALSE;
|
|
ulint i;
|
|
ulint comp = page_is_comp(page);
|
|
ulint* offsets = NULL;
|
|
ulint* old_offsets = NULL;
|
|
|
|
if ((ibool)!!comp != dict_table_is_comp(index->table)) {
|
|
fputs("InnoDB: 'compact format' flag mismatch\n", stderr);
|
|
goto func_exit2;
|
|
}
|
|
if (!page_simple_validate(page)) {
|
|
goto func_exit2;
|
|
}
|
|
|
|
heap = mem_heap_create(UNIV_PAGE_SIZE + 200);
|
|
|
|
/* The following buffer is used to check that the
|
|
records in the page record heap do not overlap */
|
|
|
|
buf = mem_heap_alloc(heap, UNIV_PAGE_SIZE);
|
|
memset(buf, 0, UNIV_PAGE_SIZE);
|
|
|
|
/* Check first that the record heap and the directory do not
|
|
overlap. */
|
|
|
|
n_slots = page_dir_get_n_slots(page);
|
|
|
|
if (!(page_header_get_ptr(page, PAGE_HEAP_TOP)
|
|
<= page_dir_get_nth_slot(page, n_slots - 1))) {
|
|
|
|
fputs("InnoDB: Record heap and dir overlap on a page ",
|
|
stderr);
|
|
dict_index_name_print(stderr, NULL, index);
|
|
fprintf(stderr, ", %p, %p\n",
|
|
page_header_get_ptr(page, PAGE_HEAP_TOP),
|
|
page_dir_get_nth_slot(page, n_slots - 1));
|
|
|
|
goto func_exit;
|
|
}
|
|
|
|
/* Validate the record list in a loop checking also that
|
|
it is consistent with the directory. */
|
|
count = 0;
|
|
data_size = 0;
|
|
own_count = 1;
|
|
slot_no = 0;
|
|
slot = page_dir_get_nth_slot(page, slot_no);
|
|
|
|
page_cur_set_before_first(page, &cur);
|
|
|
|
for (;;) {
|
|
rec = cur.rec;
|
|
offsets = rec_get_offsets(rec, index, offsets,
|
|
ULINT_UNDEFINED, &heap);
|
|
|
|
if (comp && page_rec_is_user_rec(rec)
|
|
&& rec_get_node_ptr_flag(rec)
|
|
!= (ibool)
|
|
(btr_page_get_level_low(page) != 0)) {
|
|
fputs("InnoDB: node_ptr flag mismatch\n", stderr);
|
|
goto func_exit;
|
|
}
|
|
|
|
if (!page_rec_validate(rec, offsets)) {
|
|
goto func_exit;
|
|
}
|
|
|
|
/* Check that the records are in the ascending order */
|
|
if ((count >= 2) && (!page_cur_is_after_last(&cur))) {
|
|
if (!(1 == cmp_rec_rec(rec, old_rec,
|
|
offsets, old_offsets, index))) {
|
|
fprintf(stderr,
|
|
"InnoDB: Records in wrong order"
|
|
" on page %lu ",
|
|
(ulong) buf_frame_get_page_no(page));
|
|
dict_index_name_print(stderr, NULL, index);
|
|
fputs("\nInnoDB: previous record ", stderr);
|
|
rec_print_new(stderr, old_rec, old_offsets);
|
|
fputs("\nInnoDB: record ", stderr);
|
|
rec_print_new(stderr, rec, offsets);
|
|
putc('\n', stderr);
|
|
|
|
goto func_exit;
|
|
}
|
|
}
|
|
|
|
if (page_rec_is_user_rec(rec)) {
|
|
|
|
data_size += rec_offs_size(offsets);
|
|
}
|
|
|
|
offs = rec_get_start(rec, offsets) - page;
|
|
|
|
for (i = 0; i < rec_offs_size(offsets); i++) {
|
|
if (!buf[offs + i] == 0) {
|
|
/* No other record may overlap this */
|
|
|
|
fputs("InnoDB: Record overlaps another\n",
|
|
stderr);
|
|
goto func_exit;
|
|
}
|
|
|
|
buf[offs + i] = 1;
|
|
}
|
|
|
|
if (rec_get_n_owned(rec, comp) != 0) {
|
|
/* This is a record pointed to by a dir slot */
|
|
if (rec_get_n_owned(rec, comp) != own_count) {
|
|
fprintf(stderr,
|
|
"InnoDB: Wrong owned count %lu, %lu\n",
|
|
(ulong) rec_get_n_owned(rec, comp),
|
|
(ulong) own_count);
|
|
goto func_exit;
|
|
}
|
|
|
|
if (page_dir_slot_get_rec(slot) != rec) {
|
|
fputs("InnoDB: Dir slot does not"
|
|
" point to right rec\n",
|
|
stderr);
|
|
goto func_exit;
|
|
}
|
|
|
|
page_dir_slot_check(slot);
|
|
|
|
own_count = 0;
|
|
if (!page_cur_is_after_last(&cur)) {
|
|
slot_no++;
|
|
slot = page_dir_get_nth_slot(page, slot_no);
|
|
}
|
|
}
|
|
|
|
if (page_cur_is_after_last(&cur)) {
|
|
break;
|
|
}
|
|
|
|
if (rec_get_next_offs(rec, comp) < FIL_PAGE_DATA
|
|
|| rec_get_next_offs(rec, comp) >= UNIV_PAGE_SIZE) {
|
|
fprintf(stderr,
|
|
"InnoDB: Next record offset wrong %lu\n",
|
|
(ulong) rec_get_next_offs(rec, comp));
|
|
goto func_exit;
|
|
}
|
|
|
|
count++;
|
|
page_cur_move_to_next(&cur);
|
|
own_count++;
|
|
old_rec = rec;
|
|
/* set old_offsets to offsets; recycle offsets */
|
|
{
|
|
ulint* offs = old_offsets;
|
|
old_offsets = offsets;
|
|
offsets = offs;
|
|
}
|
|
}
|
|
|
|
if (rec_get_n_owned(rec, comp) == 0) {
|
|
fputs("InnoDB: n owned is zero\n", stderr);
|
|
goto func_exit;
|
|
}
|
|
|
|
if (slot_no != n_slots - 1) {
|
|
fprintf(stderr, "InnoDB: n slots wrong %lu %lu\n",
|
|
(ulong) slot_no, (ulong) (n_slots - 1));
|
|
goto func_exit;
|
|
}
|
|
|
|
if (page_header_get_field(page, PAGE_N_RECS) + 2 != count + 1) {
|
|
fprintf(stderr, "InnoDB: n recs wrong %lu %lu\n",
|
|
(ulong) page_header_get_field(page, PAGE_N_RECS) + 2,
|
|
(ulong) (count + 1));
|
|
goto func_exit;
|
|
}
|
|
|
|
if (data_size != page_get_data_size(page)) {
|
|
fprintf(stderr,
|
|
"InnoDB: Summed data size %lu, returned by func %lu\n",
|
|
(ulong) data_size, (ulong) page_get_data_size(page));
|
|
goto func_exit;
|
|
}
|
|
|
|
/* Check then the free list */
|
|
rec = page_header_get_ptr(page, PAGE_FREE);
|
|
|
|
while (rec != NULL) {
|
|
offsets = rec_get_offsets(rec, index, offsets,
|
|
ULINT_UNDEFINED, &heap);
|
|
if (!page_rec_validate(rec, offsets)) {
|
|
|
|
goto func_exit;
|
|
}
|
|
|
|
count++;
|
|
offs = rec_get_start(rec, offsets) - page;
|
|
|
|
for (i = 0; i < rec_offs_size(offsets); i++) {
|
|
|
|
if (buf[offs + i] != 0) {
|
|
fputs("InnoDB: Record overlaps another"
|
|
" in free list\n", stderr);
|
|
goto func_exit;
|
|
}
|
|
|
|
buf[offs + i] = 1;
|
|
}
|
|
|
|
rec = page_rec_get_next(rec);
|
|
}
|
|
|
|
if (page_dir_get_n_heap(page) != count + 1) {
|
|
fprintf(stderr, "InnoDB: N heap is wrong %lu %lu\n",
|
|
(ulong) page_dir_get_n_heap(page),
|
|
(ulong) count + 1);
|
|
goto func_exit;
|
|
}
|
|
|
|
ret = TRUE;
|
|
|
|
func_exit:
|
|
mem_heap_free(heap);
|
|
|
|
if (ret == FALSE) {
|
|
func_exit2:
|
|
fprintf(stderr, "InnoDB: Apparent corruption in page %lu in ",
|
|
(ulong) buf_frame_get_page_no(page));
|
|
dict_index_name_print(stderr, NULL, index);
|
|
putc('\n', stderr);
|
|
buf_page_print(page);
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
/*******************************************************************
|
|
Looks in the page record list for a record with the given heap number. */
|
|
|
|
rec_t*
|
|
page_find_rec_with_heap_no(
|
|
/*=======================*/
|
|
/* out: record, NULL if not found */
|
|
page_t* page, /* in: index page */
|
|
ulint heap_no)/* in: heap number */
|
|
{
|
|
page_cur_t cur;
|
|
|
|
page_cur_set_before_first(page, &cur);
|
|
|
|
for (;;) {
|
|
if (rec_get_heap_no(cur.rec, page_is_comp(page)) == heap_no) {
|
|
|
|
return(cur.rec);
|
|
}
|
|
|
|
if (page_cur_is_after_last(&cur)) {
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
page_cur_move_to_next(&cur);
|
|
}
|
|
}
|