2007-04-04 13:05:33 +02:00
|
|
|
/******************************************************
|
|
|
|
New index creation routines using a merge sort
|
|
|
|
|
|
|
|
(c) 2005 Innobase Oy
|
|
|
|
|
|
|
|
Created 12/4/2005 Jan Lindstrom
|
|
|
|
*******************************************************/
|
|
|
|
|
|
|
|
/******************************************************
|
|
|
|
TODO:
|
|
|
|
|
|
|
|
1. Run test with purify and valgrind and fix possible
|
|
|
|
errors found.
|
|
|
|
|
|
|
|
2. Add more test cases and fix bugs founds.
|
|
|
|
|
|
|
|
3. If we are using variable length keys, then in
|
|
|
|
some cases these keys do not fit into two empty blocks
|
|
|
|
in a different order. Therefore, some empty space is
|
|
|
|
left in every block. However, it has not been shown
|
|
|
|
that this empty space is enough for all cases. Therefore,
|
|
|
|
in the above case these overloaded records should be put
|
|
|
|
on another block.
|
|
|
|
|
|
|
|
4. Run benchmarks.
|
|
|
|
*******************************************************/
|
|
|
|
|
|
|
|
#include "row0merge.h"
|
|
|
|
#include "row0ext.h"
|
|
|
|
#include "row0row.h"
|
|
|
|
#include "row0upd.h"
|
|
|
|
#include "row0ins.h"
|
|
|
|
#include "row0sel.h"
|
|
|
|
#include "dict0dict.h"
|
|
|
|
#include "dict0mem.h"
|
|
|
|
#include "dict0boot.h"
|
|
|
|
#include "dict0crea.h"
|
|
|
|
#include "dict0load.h"
|
|
|
|
#include "btr0btr.h"
|
|
|
|
#include "mach0data.h"
|
|
|
|
#include "trx0rseg.h"
|
|
|
|
#include "trx0trx.h"
|
|
|
|
#include "trx0roll.h"
|
|
|
|
#include "trx0undo.h"
|
|
|
|
#include "trx0purge.h"
|
|
|
|
#include "trx0rec.h"
|
|
|
|
#include "que0que.h"
|
|
|
|
#include "rem0cmp.h"
|
|
|
|
#include "read0read.h"
|
|
|
|
#include "os0file.h"
|
|
|
|
#include "lock0lock.h"
|
|
|
|
#include "data0data.h"
|
|
|
|
#include "data0type.h"
|
|
|
|
#include "que0que.h"
|
|
|
|
#include "pars0pars.h"
|
|
|
|
#include "mem0mem.h"
|
|
|
|
#include "log0log.h"
|
|
|
|
|
2007-04-11 10:16:14 +02:00
|
|
|
/* Records are stored in the memory for main memory linked list
|
|
|
|
to this structure */
|
|
|
|
|
|
|
|
struct merge_rec_struct {
|
|
|
|
struct merge_rec_struct *next; /* Pointer to next record
|
|
|
|
in the list */
|
|
|
|
rec_t* rec; /* Record */
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct merge_rec_struct merge_rec_t;
|
|
|
|
|
|
|
|
/* This structure is head element for main memory linked list
|
|
|
|
used for main memory linked list merge sort */
|
|
|
|
|
|
|
|
struct merge_rec_list_struct {
|
|
|
|
merge_rec_t* head; /* Pointer to head of the
|
|
|
|
list */
|
|
|
|
merge_rec_t* tail; /* Pointer to tail of the
|
|
|
|
list */
|
2007-04-16 09:11:00 +02:00
|
|
|
#ifdef UNIV_DEBUG
|
2007-04-11 10:16:14 +02:00
|
|
|
ulint n_records; /* Number of records in
|
|
|
|
the list */
|
2007-04-16 09:11:00 +02:00
|
|
|
#endif /* UNIV_DEBUG */
|
2007-04-11 10:16:14 +02:00
|
|
|
ulint total_size; /* Total size of all records in
|
|
|
|
the list */
|
|
|
|
mem_heap_t* heap; /* Heap where memory for this
|
|
|
|
list is allocated */
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct merge_rec_list_struct merge_rec_list_t;
|
|
|
|
|
2007-04-11 14:17:55 +02:00
|
|
|
/* Block size for I/O operations in merge sort */
|
|
|
|
|
|
|
|
#define MERGE_BLOCK_SIZE 1048576 /* 1M */
|
|
|
|
|
|
|
|
/* Intentional free space on every block */
|
|
|
|
#define MERGE_BLOCK_SAFETY_MARGIN 128
|
|
|
|
|
|
|
|
/* Enable faster index creation debug code */
|
|
|
|
/* #define UNIV_DEBUG_INDEX_CREATE 1 */
|
|
|
|
|
|
|
|
/* This block header structure is used to create linked list of the
|
|
|
|
blocks to the disk. Every block contains one header.*/
|
|
|
|
|
|
|
|
struct merge_block_header_struct {
|
|
|
|
ulint n_records; /* Number of records in the block. */
|
2007-04-12 12:32:16 +02:00
|
|
|
ulint offset; /* Offset of this block */
|
|
|
|
ulint next; /* Offset of next block */
|
2007-04-11 14:17:55 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct merge_block_header_struct merge_block_header_t;
|
|
|
|
|
|
|
|
/* This block structure is used to hold index records in the disk
|
|
|
|
and the memory */
|
|
|
|
|
|
|
|
struct merge_block_struct {
|
|
|
|
merge_block_header_t header; /* Block header information */
|
|
|
|
char data[MERGE_BLOCK_SIZE - sizeof(merge_block_header_t)];/* Data area i.e. heap */
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct merge_block_struct merge_block_t;
|
|
|
|
|
2007-04-16 09:10:00 +02:00
|
|
|
/**************************************************************************
|
|
|
|
Search an index object by name and column names. If several indexes match,
|
|
|
|
return the index with the max id. */
|
2007-04-04 13:05:33 +02:00
|
|
|
static
|
|
|
|
dict_index_t*
|
|
|
|
row_merge_dict_table_get_index(
|
|
|
|
/*===========================*/
|
2007-04-16 09:10:00 +02:00
|
|
|
/* out: matching index,
|
|
|
|
NULL if not found */
|
|
|
|
dict_table_t* table, /* in: table */
|
|
|
|
const merge_index_def_t*index_def) /* in: index definition */
|
2007-04-04 13:05:33 +02:00
|
|
|
{
|
|
|
|
ulint i;
|
|
|
|
dict_index_t* index;
|
|
|
|
const char** column_names;
|
|
|
|
|
2007-04-16 09:10:00 +02:00
|
|
|
column_names = mem_alloc(index_def->n_fields * sizeof *column_names);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
for (i = 0; i < index_def->n_fields; ++i) {
|
2007-04-11 14:17:55 +02:00
|
|
|
column_names[i] = index_def->fields[i].field_name;
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
index = dict_table_get_index_by_max_id(
|
|
|
|
table, index_def->name, column_names, index_def->n_fields);
|
|
|
|
|
2007-04-16 09:10:00 +02:00
|
|
|
mem_free(column_names);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
return(index);
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
Creates and initializes a merge block */
|
|
|
|
static
|
|
|
|
merge_block_t*
|
|
|
|
row_merge_block_create(void)
|
|
|
|
/*========================*/
|
|
|
|
/* out: pointer to block */
|
|
|
|
{
|
|
|
|
merge_block_t* mblock;
|
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
mblock = mem_alloc(sizeof *mblock);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
memset(&mblock->header, 0, sizeof mblock->header);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
return(mblock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************
|
2007-04-12 12:32:16 +02:00
|
|
|
Read a merge block from the file system. */
|
2007-04-04 13:05:33 +02:00
|
|
|
static
|
2007-04-12 12:32:16 +02:00
|
|
|
ibool
|
|
|
|
row_merge_read(
|
|
|
|
/*===========*/
|
|
|
|
/* out: TRUE if request was
|
|
|
|
successful, FALSE if fail */
|
|
|
|
os_file_t file, /* in: file handle */
|
|
|
|
ulint offset, /* in: offset where to read */
|
|
|
|
void* buf, /* out: data */
|
|
|
|
ulint size) /* in: number of bytes to read */
|
|
|
|
{
|
|
|
|
ib_uint64_t ofs = ((ib_uint64_t) offset) * MERGE_BLOCK_SIZE;
|
|
|
|
|
|
|
|
ut_ad(size <= MERGE_BLOCK_SIZE);
|
|
|
|
|
2007-04-12 14:58:09 +02:00
|
|
|
return(UNIV_LIKELY(os_file_read(file, buf,
|
|
|
|
(ulint) (ofs & 0xFFFFFFFF),
|
|
|
|
(ulint) (ofs >> 32),
|
|
|
|
size)));
|
2007-04-12 12:32:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
Read a merge block from the file system. */
|
|
|
|
static
|
|
|
|
ibool
|
2007-04-04 13:05:33 +02:00
|
|
|
row_merge_block_read(
|
|
|
|
/*=================*/
|
|
|
|
/* out: TRUE if request was
|
|
|
|
successful, FALSE if fail */
|
2007-04-12 12:32:16 +02:00
|
|
|
os_file_t file, /* in: file handle */
|
|
|
|
ulint offset, /* in: offset where to read */
|
|
|
|
merge_block_t* block) /* out: merge block */
|
2007-04-04 13:05:33 +02:00
|
|
|
{
|
2007-04-12 12:32:16 +02:00
|
|
|
return(row_merge_read(file, offset, block, sizeof *block));
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
Read a merge block header from the disk */
|
|
|
|
static
|
2007-04-12 12:32:16 +02:00
|
|
|
ibool
|
2007-04-04 13:05:33 +02:00
|
|
|
row_merge_block_header_read(
|
|
|
|
/*========================*/
|
|
|
|
/* out: TRUE if request was
|
|
|
|
successful, FALSE if fail */
|
|
|
|
os_file_t file, /* in: handle to a file */
|
2007-04-12 12:32:16 +02:00
|
|
|
ulint offset, /* in: offset where to read */
|
|
|
|
merge_block_header_t* header) /* out: merge block header */
|
|
|
|
{
|
|
|
|
return(row_merge_read(file, offset, header, sizeof *header));
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
Read a merge block from the file system. */
|
|
|
|
static
|
|
|
|
ibool
|
|
|
|
row_merge_write(
|
|
|
|
/*============*/
|
|
|
|
/* out: TRUE if request was
|
|
|
|
successful, FALSE if fail */
|
|
|
|
os_file_t file, /* in: file handle */
|
|
|
|
ulint offset, /* in: offset where to write */
|
|
|
|
const void* buf, /* in: data */
|
|
|
|
ulint size) /* in: number of bytes to write */
|
2007-04-04 13:05:33 +02:00
|
|
|
{
|
2007-04-12 12:32:16 +02:00
|
|
|
ib_uint64_t ofs = ((ib_uint64_t) offset) * MERGE_BLOCK_SIZE;
|
|
|
|
|
|
|
|
ut_ad(size <= MERGE_BLOCK_SIZE);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-12 14:58:09 +02:00
|
|
|
return(UNIV_LIKELY(os_file_write("(merge)", file, buf,
|
|
|
|
(ulint) (ofs & 0xFFFFFFFF),
|
|
|
|
(ulint) (ofs >> 32),
|
|
|
|
size)));
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
Write a merge block header to the disk */
|
|
|
|
static
|
2007-04-12 12:32:16 +02:00
|
|
|
ibool
|
2007-04-04 13:05:33 +02:00
|
|
|
row_merge_block_header_write(
|
|
|
|
/*=========================*/
|
2007-04-12 12:32:16 +02:00
|
|
|
/* out: TRUE if request was
|
|
|
|
successful, FALSE if fail */
|
|
|
|
os_file_t file, /* in: handle to a file */
|
|
|
|
const merge_block_header_t* header) /* in: block header */
|
2007-04-04 13:05:33 +02:00
|
|
|
{
|
2007-04-12 12:32:16 +02:00
|
|
|
return(row_merge_write(file, header->offset, header, sizeof *header));
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
Write a merge block to the disk */
|
|
|
|
static
|
2007-04-12 12:32:16 +02:00
|
|
|
ibool
|
2007-04-04 13:05:33 +02:00
|
|
|
row_merge_block_write(
|
|
|
|
/*==================*/
|
2007-04-12 12:32:16 +02:00
|
|
|
/* out: TRUE if request was
|
|
|
|
successful, FALSE if fail */
|
|
|
|
os_file_t file, /* in: handle to a file */
|
|
|
|
ulint offset, /* in: file offset */
|
|
|
|
const merge_block_t* block) /* in: block header */
|
2007-04-04 13:05:33 +02:00
|
|
|
{
|
2007-04-12 12:32:16 +02:00
|
|
|
ut_ad(offset == block->header.offset);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
return(row_merge_write(file, offset, block, sizeof *block));
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**************************************************************
|
|
|
|
Create a merge record and copy a index data tuple to the merge
|
|
|
|
record */
|
|
|
|
static
|
|
|
|
merge_rec_t*
|
|
|
|
row_merge_rec_create(
|
|
|
|
/*=================*/
|
|
|
|
/* out: merge record */
|
|
|
|
const dtuple_t* dtuple, /* in: data tuple */
|
|
|
|
const ulint* ext, /* in: array of extern field numbers */
|
|
|
|
ulint n_ext, /* in: number of elements in ext */
|
|
|
|
dict_index_t* index, /* in: index record descriptor */
|
|
|
|
mem_heap_t* heap) /* in: heap where memory is allocated */
|
|
|
|
{
|
|
|
|
merge_rec_t* m_rec;
|
|
|
|
ulint rec_size;
|
|
|
|
byte* buf;
|
|
|
|
|
|
|
|
ut_ad(dtuple && index && heap);
|
|
|
|
ut_ad(dtuple_validate(dtuple));
|
|
|
|
|
|
|
|
m_rec = (merge_rec_t*) mem_heap_alloc(heap, sizeof(merge_rec_t));
|
|
|
|
|
|
|
|
rec_size = rec_get_converted_size(index, dtuple, ext, n_ext);
|
|
|
|
buf = mem_heap_alloc(heap, rec_size);
|
|
|
|
|
|
|
|
m_rec->rec = rec_convert_dtuple_to_rec(buf, index, dtuple,
|
|
|
|
ext, n_ext);
|
|
|
|
m_rec->next = NULL;
|
|
|
|
|
|
|
|
return(m_rec);
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
Checks that a record fits to a block */
|
|
|
|
static
|
|
|
|
ibool
|
|
|
|
row_merge_rec_fits_to_block(
|
|
|
|
/*========================*/
|
|
|
|
/* out: TRUE if record fits to merge block,
|
|
|
|
FALSE if record does not fit to block */
|
2007-04-16 11:13:44 +02:00
|
|
|
const ulint* offsets,/* in: record offsets */
|
2007-04-04 13:05:33 +02:00
|
|
|
ulint offset) /* in: offset where to store in the block */
|
|
|
|
{
|
|
|
|
ulint rec_len;
|
|
|
|
|
|
|
|
ut_ad(offsets);
|
|
|
|
|
|
|
|
rec_len = mach_get_compressed_size(rec_offs_extra_size(offsets))
|
|
|
|
+ rec_offs_size(offsets);
|
|
|
|
|
|
|
|
/* Note that we intentionally leave free space on
|
|
|
|
every block. This free space might be later needed when two
|
|
|
|
blocks are merged and variable length keys are used. Variable
|
|
|
|
length keys on two blocks might be interleaved on such a manner
|
|
|
|
that they do not fit on two blocks if blocks are too full */
|
|
|
|
|
|
|
|
return((offset + rec_len) < (MERGE_BLOCK_SIZE
|
|
|
|
- MERGE_BLOCK_SAFETY_MARGIN
|
|
|
|
- sizeof(merge_block_header_t)));
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
Store a record to a merge file block. Note that this function does
|
|
|
|
not check that the record fits to the block. */
|
|
|
|
static
|
|
|
|
ulint
|
|
|
|
row_merge_store_rec_to_block(
|
|
|
|
/*=========================*/
|
|
|
|
/* out: offset for next data tuple */
|
2007-04-16 11:13:44 +02:00
|
|
|
const rec_t* rec, /* in: record to be stored in the memory */
|
|
|
|
const ulint* offsets,/* in: record offsets */
|
2007-04-04 13:05:33 +02:00
|
|
|
merge_block_t* mblock, /* in: block where data tuple is stored */
|
|
|
|
ulint offset) /* in: offset where to store */
|
|
|
|
{
|
|
|
|
char* dest_data;
|
|
|
|
ulint rec_len;
|
|
|
|
ulint extra_len;
|
|
|
|
ulint storage_size;
|
|
|
|
|
|
|
|
ut_ad(rec && mblock && offsets);
|
|
|
|
ut_ad(rec_validate(rec, offsets));
|
|
|
|
|
|
|
|
/* Find the position in the block where this data tuple is stored.
|
|
|
|
If we are at the start of the block, remember to add size of header
|
|
|
|
to the offset */
|
|
|
|
|
|
|
|
if (offset == 0) {
|
|
|
|
dest_data = mblock->data;
|
|
|
|
} else {
|
|
|
|
dest_data = ((char *)mblock + offset);
|
|
|
|
}
|
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
ut_ad(dest_data < (char*) &mblock[1]);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
extra_len = rec_offs_extra_size(offsets);
|
|
|
|
rec_len = rec_offs_size(offsets);
|
|
|
|
|
|
|
|
/* 1. Store the extra_len */
|
|
|
|
storage_size = mach_write_compressed((byte *)dest_data, extra_len);
|
|
|
|
dest_data+=storage_size;
|
2007-04-12 12:32:16 +02:00
|
|
|
ut_ad(dest_data < (char*) &mblock[1]);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
/* 2. Store the record */
|
|
|
|
memcpy(dest_data, rec - extra_len, rec_len);
|
|
|
|
dest_data+=rec_len;
|
2007-04-12 12:32:16 +02:00
|
|
|
ut_ad(dest_data < (char*) &mblock[1]);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
mblock->header.n_records++;
|
|
|
|
|
|
|
|
/* Return next offset */
|
|
|
|
return((char *)dest_data - (char *)mblock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
Read a record from the block */
|
|
|
|
static
|
|
|
|
merge_rec_t*
|
|
|
|
row_merge_read_rec_from_block(
|
|
|
|
/*==========================*/
|
|
|
|
/* out: record or NULL*/
|
|
|
|
merge_block_t* mblock, /* in: memory block where to read */
|
|
|
|
ulint* offset, /* in/out: offset where to read a record */
|
|
|
|
mem_heap_t* heap, /* in: heap were this memory for this record
|
|
|
|
is allocated */
|
|
|
|
dict_index_t* index) /* in: index record desriptor */
|
|
|
|
{
|
|
|
|
merge_rec_t* mrec;
|
|
|
|
char* from_data;
|
|
|
|
ulint extra_len;
|
|
|
|
ulint data_len;
|
|
|
|
ulint tmp_offset;
|
|
|
|
ulint storage_len;
|
|
|
|
rec_t* rec;
|
|
|
|
mem_heap_t* offset_heap = NULL;
|
|
|
|
ulint sec_offsets_[REC_OFFS_SMALL_SIZE];
|
|
|
|
ulint* sec_offs = sec_offsets_;
|
|
|
|
|
|
|
|
*sec_offsets_ = (sizeof sec_offsets_) / sizeof *sec_offsets_;
|
|
|
|
|
|
|
|
ut_ad(mblock && offset && heap);
|
|
|
|
|
|
|
|
tmp_offset = *offset;
|
|
|
|
|
|
|
|
/* Find the position in the block where this data tuple is stored.
|
|
|
|
If we are at the start of the block, remember to add size of header
|
|
|
|
to the offset */
|
|
|
|
|
|
|
|
if (tmp_offset == 0) {
|
|
|
|
from_data = mblock->data;
|
|
|
|
} else {
|
|
|
|
from_data = ((char *)mblock + tmp_offset);
|
|
|
|
}
|
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
ut_ad(from_data < (const char*) &mblock[1]);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
mrec = mem_heap_alloc(heap, sizeof(merge_rec_t));
|
|
|
|
|
|
|
|
/* 1. Read the extra len and calculate its storage length */
|
|
|
|
extra_len = mach_read_compressed((byte *)from_data);
|
|
|
|
storage_len = mach_get_compressed_size(extra_len);
|
|
|
|
from_data+=storage_len;
|
2007-04-12 12:32:16 +02:00
|
|
|
ut_ad(from_data < (const char*) &mblock[1]);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
/* 2. Read the record */
|
|
|
|
rec = (rec_t*)(from_data + extra_len);
|
|
|
|
mrec->rec = rec;
|
|
|
|
sec_offs = rec_get_offsets(mrec->rec, index, sec_offs, ULINT_UNDEFINED,
|
|
|
|
&offset_heap);
|
|
|
|
data_len = rec_offs_size(sec_offs);
|
|
|
|
ut_ad(rec_validate(rec, sec_offs));
|
|
|
|
|
|
|
|
from_data+=data_len;
|
2007-04-12 12:32:16 +02:00
|
|
|
ut_ad(from_data < (const char*) &mblock[1]);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
/* Return also start offset of the next data tuple */
|
|
|
|
*offset = ((char *)from_data - (char *)mblock);
|
|
|
|
|
|
|
|
if (offset_heap) {
|
|
|
|
mem_heap_free(offset_heap);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(mrec);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************
|
2007-05-03 09:42:54 +02:00
|
|
|
Compare two merge records. */
|
2007-04-04 13:05:33 +02:00
|
|
|
static
|
2007-04-16 11:13:44 +02:00
|
|
|
int
|
|
|
|
row_merge_cmp(
|
|
|
|
/*==========*/
|
|
|
|
/* out: 1, 0, -1 if mrec1 is
|
|
|
|
greater, equal, less,
|
|
|
|
respectively, than mrec2 */
|
2007-04-04 13:05:33 +02:00
|
|
|
merge_rec_t* mrec1, /* in: first merge record to be
|
|
|
|
compared */
|
|
|
|
merge_rec_t* mrec2, /* in: second merge record to be
|
|
|
|
compared */
|
2007-04-16 11:13:44 +02:00
|
|
|
const ulint* offsets1, /* in: first record offsets */
|
|
|
|
const ulint* offsets2, /* in: second record offsets */
|
|
|
|
dict_index_t* index) /* in: index */
|
2007-04-04 13:05:33 +02:00
|
|
|
{
|
2007-04-17 10:53:47 +02:00
|
|
|
ut_ad(mrec1 && mrec2 && offsets1 && offsets2 && index);
|
2007-04-04 13:05:33 +02:00
|
|
|
ut_ad(rec_validate(mrec1->rec, offsets1));
|
|
|
|
ut_ad(rec_validate(mrec2->rec, offsets2));
|
|
|
|
|
2007-04-16 11:13:44 +02:00
|
|
|
return(cmp_rec_rec(mrec1->rec, mrec2->rec, offsets1, offsets2, index));
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************
|
|
|
|
Merge sort for linked list in memory.
|
|
|
|
|
|
|
|
Merge sort takes the input list and makes log N passes along
|
|
|
|
the list and in each pass it combines each adjacent pair of
|
2007-04-17 10:53:47 +02:00
|
|
|
small sorted lists into one larger sorted list. When only one
|
|
|
|
pass is needed the whole output list must have been sorted.
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
In each pass, two lists of size block_size are merged into lists of
|
|
|
|
size block_size*2. Initially block_size=1. Merge starts by pointing
|
|
|
|
a temporary pointer list1 at the head of the list and also preparing
|
2007-04-12 12:32:16 +02:00
|
|
|
an empty list list_tail where elements will be appended. Then:
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
1) If list1 is NULL we terminate this pass.
|
|
|
|
|
|
|
|
2) Otherwise, there is at least one element in the next
|
|
|
|
pair of block_size lists therefore, increase the number of
|
|
|
|
merges performed in this pass.
|
|
|
|
|
|
|
|
3) Point another temporary pointer list2 as the same
|
|
|
|
place as list1. Iterate list2 by block_size elements
|
|
|
|
or until the end of the list. Let the list_size1 be the
|
|
|
|
number of elements in the list2.
|
|
|
|
|
|
|
|
4) Let list_size1=merge_size. Now we merge list starting at
|
|
|
|
list1 of length list_size2 with a list starting at list2 of
|
|
|
|
length at most list_size1.
|
|
|
|
|
|
|
|
5) So, as long as either the list1 is non-empty (list_size1)
|
|
|
|
or the list2 is non-empty (list_size2 and list2 pointing to
|
|
|
|
a element):
|
|
|
|
|
|
|
|
5.1) Select which list to take the next element from.
|
|
|
|
If either lists is empty, we choose from the other one.
|
|
|
|
If both lists are non-empty, compare the first element
|
|
|
|
of each and choose the lower one.
|
|
|
|
|
|
|
|
5.2) Remove that element, tmp, from the start of its
|
|
|
|
lists, by advancing list1 or list2 to next element
|
|
|
|
and decreasing list1_size or list2_size.
|
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
5.3) Append tmp to list_tail
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
6) At this point, we have advanced list1 until it is where
|
|
|
|
list2 started out and we have advanced list2 until it is
|
|
|
|
pointing at the next pair of block_size lists to merge.
|
|
|
|
Thus, set list1 to the value of list2 and go back to the
|
|
|
|
start of this loop.
|
|
|
|
|
|
|
|
As soon as a pass like this is performed with only one merge, the
|
|
|
|
algorithm terminates and output list list_head is sorted. Otherwise,
|
|
|
|
double the value of block_size and go back to the beginning. */
|
|
|
|
static
|
2007-04-16 11:13:44 +02:00
|
|
|
ibool
|
2007-04-04 13:05:33 +02:00
|
|
|
row_merge_sort_linked_list(
|
|
|
|
/*=======================*/
|
2007-04-16 11:13:44 +02:00
|
|
|
/* out: FALSE on error */
|
2007-04-04 13:05:33 +02:00
|
|
|
dict_index_t* index, /* in: index to be created */
|
|
|
|
merge_rec_list_t* list) /* in: Pointer to head element */
|
|
|
|
{
|
2007-04-16 11:13:44 +02:00
|
|
|
ibool success;
|
2007-04-04 13:05:33 +02:00
|
|
|
merge_rec_t* list1;
|
|
|
|
merge_rec_t* list2;
|
|
|
|
merge_rec_t* list_head;
|
|
|
|
merge_rec_t* list_tail;
|
|
|
|
ulint block_size;
|
|
|
|
ulint list1_size;
|
|
|
|
ulint list2_size;
|
|
|
|
ulint i;
|
2007-04-16 11:13:44 +02:00
|
|
|
mem_heap_t* heap = NULL;
|
|
|
|
ulint offsets1_[REC_OFFS_SMALL_SIZE];
|
|
|
|
ulint* offsets1 = offsets1_;
|
|
|
|
ulint offsets2_[REC_OFFS_SMALL_SIZE];
|
|
|
|
ulint* offsets2 = offsets2_;
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
ut_ad(list && list->head && index);
|
|
|
|
|
2007-04-16 11:13:44 +02:00
|
|
|
*offsets1_ = (sizeof offsets1_) / sizeof *offsets1_;
|
|
|
|
*offsets2_ = (sizeof offsets2_) / sizeof *offsets2_;
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
list_head = list->head;
|
|
|
|
|
2007-04-17 10:53:47 +02:00
|
|
|
for (block_size = 1;; block_size *= 2) {
|
2007-04-17 11:44:29 +02:00
|
|
|
ibool sorted = TRUE;
|
|
|
|
|
2007-04-04 13:05:33 +02:00
|
|
|
list1 = list_head;
|
|
|
|
list_head = NULL;
|
|
|
|
list_tail = NULL;
|
|
|
|
|
2007-04-17 10:53:47 +02:00
|
|
|
for (;;) {
|
2007-04-04 13:05:33 +02:00
|
|
|
list2 = list1;
|
|
|
|
list1_size = 0;
|
2007-04-17 10:53:47 +02:00
|
|
|
list2_size = block_size;
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
/* Step at most block_size elements along from
|
|
|
|
list2. */
|
|
|
|
|
|
|
|
for (i = 0; i < block_size; i++) {
|
|
|
|
list1_size++;
|
|
|
|
list2 = list2->next;
|
|
|
|
|
|
|
|
if (!list2) {
|
2007-04-17 10:53:47 +02:00
|
|
|
list2_size = 0;
|
2007-04-04 13:05:33 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If list2 is not NULL, we have two lists to merge.
|
2007-04-17 10:53:47 +02:00
|
|
|
Otherwise, we have a sorted list. */
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-17 10:53:47 +02:00
|
|
|
while (list1_size || list2_size) {
|
2007-04-16 11:13:44 +02:00
|
|
|
merge_rec_t* tmp;
|
2007-04-04 13:05:33 +02:00
|
|
|
/* Merge sort two lists by deciding whether
|
|
|
|
next element of merge comes from list1 or
|
|
|
|
list2. */
|
|
|
|
|
|
|
|
if (list1_size == 0) {
|
|
|
|
/* First list is empty, next element
|
|
|
|
must come from the second list. */
|
2007-04-16 11:13:44 +02:00
|
|
|
goto pick2;
|
|
|
|
}
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-17 10:53:47 +02:00
|
|
|
if (list2_size == 0) {
|
2007-04-04 13:05:33 +02:00
|
|
|
/* Second list is empty, next element
|
|
|
|
must come from the first list. */
|
2007-04-16 11:13:44 +02:00
|
|
|
goto pick1;
|
|
|
|
}
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-16 11:13:44 +02:00
|
|
|
offsets1 = rec_get_offsets(list1->rec, index,
|
|
|
|
offsets1,
|
|
|
|
ULINT_UNDEFINED,
|
|
|
|
&heap);
|
|
|
|
offsets2 = rec_get_offsets(list2->rec, index,
|
|
|
|
offsets2,
|
|
|
|
ULINT_UNDEFINED,
|
|
|
|
&heap);
|
|
|
|
|
|
|
|
switch (row_merge_cmp(list1, list2,
|
|
|
|
offsets1, offsets2,
|
|
|
|
index)) {
|
|
|
|
case 0:
|
|
|
|
if (UNIV_UNLIKELY
|
|
|
|
(dict_index_is_unique(index))) {
|
|
|
|
success = FALSE;
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
/* fall through */
|
|
|
|
case -1:
|
|
|
|
pick1:
|
2007-04-04 13:05:33 +02:00
|
|
|
tmp = list1;
|
|
|
|
list1 = list1->next;
|
|
|
|
list1_size--;
|
2007-04-16 11:13:44 +02:00
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
pick2:
|
|
|
|
tmp = list2;
|
|
|
|
list2 = list2->next;
|
2007-04-17 10:53:47 +02:00
|
|
|
if (list2) {
|
|
|
|
list2_size--;
|
|
|
|
} else {
|
|
|
|
list2_size = 0;
|
|
|
|
}
|
2007-04-16 11:13:44 +02:00
|
|
|
break;
|
2007-04-30 09:02:28 +02:00
|
|
|
default:
|
|
|
|
ut_error;
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
2007-04-16 11:13:44 +02:00
|
|
|
/* Append the element to the merged list */
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
if (list_tail) {
|
|
|
|
list_tail->next = tmp;
|
|
|
|
} else {
|
|
|
|
list_head = tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_tail = tmp;
|
|
|
|
}
|
|
|
|
|
2007-04-17 10:53:47 +02:00
|
|
|
if (!list2) {
|
2007-04-17 11:44:29 +02:00
|
|
|
if (!sorted) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2007-04-17 10:53:47 +02:00
|
|
|
list->head = list_head;
|
|
|
|
list_tail->next = NULL;
|
|
|
|
success = TRUE;
|
|
|
|
goto func_exit;
|
|
|
|
}
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-17 11:44:29 +02:00
|
|
|
sorted = FALSE;
|
2007-04-04 13:05:33 +02:00
|
|
|
list1 = list2;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_tail->next = NULL;
|
|
|
|
}
|
2007-04-16 11:13:44 +02:00
|
|
|
|
|
|
|
func_exit:
|
|
|
|
if (UNIV_LIKELY_NULL(heap)) {
|
|
|
|
mem_heap_free(heap);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(success);
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************
|
|
|
|
Create and initialize record list used for in-memory merge sort */
|
|
|
|
static
|
|
|
|
merge_rec_list_t*
|
|
|
|
row_merge_create_list(void)
|
|
|
|
/*=======================*/
|
|
|
|
/* out: pointer to list */
|
|
|
|
{
|
|
|
|
merge_rec_list_t* list_header;
|
|
|
|
mem_heap_t* heap = NULL;
|
|
|
|
|
|
|
|
/* Create list header */
|
|
|
|
heap = mem_heap_create((MERGE_BLOCK_SIZE + sizeof(merge_rec_list_t)));
|
|
|
|
|
|
|
|
list_header = mem_heap_alloc(heap, sizeof(merge_rec_list_t));
|
|
|
|
|
|
|
|
list_header->head = NULL;
|
|
|
|
list_header->tail = NULL;
|
2007-04-16 09:11:00 +02:00
|
|
|
ut_d(list_header->n_records = 0);
|
2007-04-04 13:05:33 +02:00
|
|
|
list_header->total_size = sizeof(merge_rec_list_t);
|
|
|
|
list_header->heap = heap;
|
|
|
|
|
|
|
|
return(list_header);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************
|
|
|
|
Add one record to the merge list */
|
|
|
|
static
|
|
|
|
void
|
|
|
|
row_merge_list_add(
|
|
|
|
/*===============*/
|
|
|
|
merge_rec_t* m_rec, /* in: record to be
|
|
|
|
inserted to the list */
|
|
|
|
ulint rec_len, /* in: record length */
|
|
|
|
merge_rec_list_t* list_header) /* in/out: list header */
|
|
|
|
{
|
|
|
|
ut_ad(m_rec && list_header);
|
|
|
|
|
|
|
|
m_rec->next = NULL;
|
|
|
|
list_header->total_size+=rec_len;
|
|
|
|
|
|
|
|
if (list_header->tail == NULL) {
|
|
|
|
|
|
|
|
list_header->tail = list_header->head = m_rec;
|
|
|
|
} else {
|
|
|
|
list_header->tail->next = m_rec;
|
|
|
|
list_header->tail = m_rec;
|
|
|
|
}
|
|
|
|
|
2007-04-16 09:11:00 +02:00
|
|
|
ut_d(list_header->n_records++);
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************
|
|
|
|
Write records from the list to the merge block */
|
|
|
|
static
|
|
|
|
merge_rec_list_t*
|
|
|
|
row_merge_write_list_to_block(
|
|
|
|
/*==========================*/
|
|
|
|
/* out: pointer to a new list
|
|
|
|
where rest of the items are stored */
|
|
|
|
merge_rec_list_t* list, /* in: Record list */
|
|
|
|
merge_block_t* output, /* in: Pointer to block */
|
|
|
|
dict_index_t* index) /* in: Record descriptor */
|
|
|
|
{
|
|
|
|
ulint offset = 0;
|
|
|
|
merge_rec_t* m_rec = NULL;
|
|
|
|
merge_rec_list_t* new_list = NULL;
|
|
|
|
mem_heap_t* heap = NULL;
|
|
|
|
ulint sec_offsets_[REC_OFFS_SMALL_SIZE];
|
|
|
|
ulint* sec_offs = sec_offsets_;
|
|
|
|
|
|
|
|
ut_ad(list && output && index);
|
|
|
|
|
|
|
|
*sec_offsets_ = (sizeof sec_offsets_) / sizeof *sec_offsets_;
|
|
|
|
output->header.n_records = 0;
|
|
|
|
|
|
|
|
/* Write every record which fits to block to the block */
|
|
|
|
|
|
|
|
m_rec = list->head;
|
|
|
|
|
|
|
|
while (m_rec) {
|
|
|
|
|
|
|
|
sec_offs = rec_get_offsets(m_rec->rec, index, sec_offs,
|
|
|
|
ULINT_UNDEFINED, &heap);
|
|
|
|
|
|
|
|
if (!row_merge_rec_fits_to_block(sec_offs, offset)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
offset = row_merge_store_rec_to_block(m_rec->rec,
|
|
|
|
sec_offs, output, offset);
|
|
|
|
|
|
|
|
m_rec = m_rec->next;
|
2007-04-16 09:11:00 +02:00
|
|
|
ut_d(list->n_records--);
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Now create a new list and store rest of the records there.
|
|
|
|
Note that records must be copied because we deallocate memory
|
|
|
|
allocated for the original list. */
|
|
|
|
|
|
|
|
new_list = row_merge_create_list();
|
|
|
|
|
|
|
|
while (m_rec) {
|
|
|
|
rec_t* rec;
|
|
|
|
merge_rec_t* n_rec;
|
|
|
|
void* buff;
|
|
|
|
|
|
|
|
*sec_offsets_ = (sizeof sec_offsets_) / sizeof *sec_offsets_;
|
|
|
|
|
|
|
|
sec_offs = rec_get_offsets(m_rec->rec, index, sec_offs,
|
|
|
|
ULINT_UNDEFINED, &heap);
|
|
|
|
|
|
|
|
buff = mem_heap_alloc(new_list->heap,
|
|
|
|
rec_offs_size(sec_offs));
|
|
|
|
|
|
|
|
n_rec = mem_heap_alloc(new_list->heap, sizeof(merge_rec_t));
|
|
|
|
rec = rec_copy(buff, m_rec->rec, sec_offs);
|
|
|
|
n_rec->rec = rec;
|
|
|
|
row_merge_list_add(n_rec, rec_offs_size(sec_offs), new_list);
|
|
|
|
m_rec = m_rec->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We can now free original list */
|
|
|
|
mem_heap_free(list->heap);
|
|
|
|
|
|
|
|
if (heap) {
|
|
|
|
mem_heap_free(heap);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(new_list);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
/*************************************************************************
|
|
|
|
Validate contents of the block */
|
|
|
|
static
|
|
|
|
ibool
|
|
|
|
row_merge_block_validate(
|
|
|
|
/*=====================*/
|
|
|
|
merge_block_t* block, /* in: block to be printed */
|
|
|
|
dict_index_t* index) /* in: record descriptor */
|
|
|
|
{
|
|
|
|
merge_rec_t* mrec;
|
|
|
|
ulint offset = 0;
|
|
|
|
ulint n_recs = 0;
|
|
|
|
mem_heap_t* heap;
|
|
|
|
ulint sec_offsets1_[REC_OFFS_SMALL_SIZE];
|
|
|
|
ulint* sec_offs1 = sec_offsets1_;
|
|
|
|
*sec_offsets1_ = (sizeof sec_offsets1_) / sizeof *sec_offsets1_;
|
|
|
|
|
|
|
|
ut_a(block && index);
|
|
|
|
|
|
|
|
heap = mem_heap_create(1024);
|
|
|
|
|
|
|
|
fprintf(stderr,
|
|
|
|
"Block validate %lu records, "
|
2007-04-12 12:32:16 +02:00
|
|
|
"offset %lu, next %lu\n",
|
2007-04-04 13:05:33 +02:00
|
|
|
block->header.n_records,
|
2007-04-12 12:32:16 +02:00
|
|
|
block->header.offset, block->header.next);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
ut_a(block->header.n_records > 0);
|
|
|
|
|
|
|
|
for (n_recs = 0; n_recs < block->header.n_records; n_recs++) {
|
|
|
|
|
|
|
|
mrec = row_merge_read_rec_from_block(block, &offset, heap,
|
|
|
|
index);
|
|
|
|
|
|
|
|
sec_offs1 = rec_get_offsets(mrec->rec, index, sec_offs1,
|
|
|
|
ULINT_UNDEFINED, &heap);
|
|
|
|
|
|
|
|
ut_a(rec_validate(mrec->rec, sec_offs1));
|
|
|
|
|
|
|
|
mem_heap_empty(heap);
|
|
|
|
}
|
|
|
|
|
|
|
|
mem_heap_free(heap);
|
|
|
|
|
|
|
|
return(TRUE);
|
|
|
|
}
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
Merge two blocks resulting a two sorted blocks. */
|
|
|
|
static
|
|
|
|
merge_block_t*
|
|
|
|
row_merge_block_merge(
|
|
|
|
/*==================*/
|
|
|
|
/* out: Pointer to first sorted block
|
|
|
|
or NULL in case of error */
|
|
|
|
merge_block_t* block1, /* in: First block to be merged */
|
|
|
|
merge_block_t** block2, /* in/out: Second block to be merged.
|
|
|
|
Note that contents of the second sorted
|
|
|
|
block is returned with this parameter.*/
|
|
|
|
dict_index_t* index) /* in: Index to be created */
|
|
|
|
{
|
|
|
|
merge_block_t* new_block1;
|
|
|
|
merge_block_t* new_block2;
|
|
|
|
merge_block_t* tmp;
|
|
|
|
ulint nth_rec1 = 0;
|
|
|
|
ulint nth_rec2 = 0;
|
|
|
|
ulint offset1 = 0;
|
|
|
|
ulint offset2 = 0;
|
|
|
|
ulint offset3 = 0;
|
|
|
|
ulint offset4 = 0;
|
|
|
|
ibool fits_to_new = TRUE;
|
|
|
|
mem_heap_t* heap;
|
|
|
|
mem_heap_t* offset_heap = NULL;
|
|
|
|
ulint sec_offsets1_[REC_OFFS_SMALL_SIZE];
|
|
|
|
ulint* sec_offs1 = sec_offsets1_;
|
|
|
|
ulint sec_offsets2_[REC_OFFS_SMALL_SIZE];
|
|
|
|
ulint* sec_offs2 = sec_offsets2_;
|
|
|
|
|
|
|
|
ut_ad(block1 && block2 && *block2 && index);
|
|
|
|
ut_ad(row_merge_block_validate(block1, index));
|
|
|
|
ut_ad(row_merge_block_validate(*block2, index));
|
|
|
|
|
|
|
|
*sec_offsets1_ = (sizeof sec_offsets1_) / sizeof *sec_offsets1_;
|
|
|
|
*sec_offsets2_ = (sizeof sec_offsets2_) / sizeof *sec_offsets2_;
|
|
|
|
|
|
|
|
new_block1 = row_merge_block_create();
|
|
|
|
new_block2 = row_merge_block_create();
|
|
|
|
tmp = *block2;
|
|
|
|
heap = mem_heap_create(256);
|
|
|
|
|
|
|
|
/* Copy block offset and next block offset to new blocks */
|
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
new_block1->header = block1->header;
|
|
|
|
new_block2->header = tmp->header;
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-30 08:25:47 +02:00
|
|
|
new_block1->header.n_records = 0;
|
|
|
|
new_block2->header.n_records = 0;
|
|
|
|
|
2007-04-04 13:05:33 +02:00
|
|
|
/* Merge all records from both blocks */
|
|
|
|
|
|
|
|
while (nth_rec1 < block1->header.n_records ||
|
|
|
|
nth_rec2 < tmp->header.n_records) {
|
2007-04-30 08:25:47 +02:00
|
|
|
merge_rec_t* mrec1 = NULL;
|
|
|
|
merge_rec_t* mrec2 = NULL;
|
2007-04-16 11:13:44 +02:00
|
|
|
const ulint* rec_offsets;
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
mem_heap_empty(heap);
|
|
|
|
|
|
|
|
if (nth_rec1 < block1->header.n_records &&
|
|
|
|
nth_rec2 >= tmp->header.n_records) {
|
|
|
|
|
|
|
|
/* If the second block is empty read record from
|
|
|
|
the first block */
|
|
|
|
|
|
|
|
mrec1 = row_merge_read_rec_from_block(
|
|
|
|
block1, &offset1, heap, index);
|
|
|
|
|
|
|
|
sec_offs1 = rec_get_offsets(
|
|
|
|
mrec1->rec, index, sec_offs1, ULINT_UNDEFINED,
|
|
|
|
&offset_heap);
|
|
|
|
|
|
|
|
rec_offsets = sec_offs1;
|
|
|
|
|
|
|
|
ut_ad(rec_validate(mrec1->rec, sec_offs1));
|
|
|
|
|
|
|
|
nth_rec1++;
|
|
|
|
|
|
|
|
} else if (nth_rec2 < tmp->header.n_records &&
|
|
|
|
nth_rec1 >= block1->header.n_records) {
|
|
|
|
|
|
|
|
/* If the first block is empty read data tuple from
|
|
|
|
the second block */
|
|
|
|
|
|
|
|
mrec1 = row_merge_read_rec_from_block(
|
|
|
|
tmp, &offset2, heap, index);
|
|
|
|
|
|
|
|
sec_offs1 = rec_get_offsets(
|
|
|
|
mrec1->rec, index, sec_offs1, ULINT_UNDEFINED,
|
|
|
|
&offset_heap);
|
|
|
|
|
|
|
|
rec_offsets = sec_offs1;
|
|
|
|
|
|
|
|
ut_ad(rec_validate(mrec1->rec, sec_offs1));
|
|
|
|
|
|
|
|
nth_rec2++;
|
|
|
|
} else {
|
|
|
|
ulint tmp_offset1 = offset1;
|
|
|
|
ulint tmp_offset2 = offset2;
|
|
|
|
|
|
|
|
/* Both blocks contain record and thus they must
|
|
|
|
be compared */
|
|
|
|
|
|
|
|
mrec1 = row_merge_read_rec_from_block(
|
|
|
|
block1, &offset1, heap, index);
|
|
|
|
|
|
|
|
sec_offs1 = rec_get_offsets(
|
|
|
|
mrec1->rec, index, sec_offs1, ULINT_UNDEFINED,
|
|
|
|
&offset_heap);
|
|
|
|
|
|
|
|
ut_ad(rec_validate(mrec1->rec, sec_offs1));
|
|
|
|
|
|
|
|
mrec2 = row_merge_read_rec_from_block(
|
|
|
|
tmp, &offset2, heap, index);
|
|
|
|
|
|
|
|
sec_offs2 = rec_get_offsets(
|
|
|
|
mrec2->rec, index, sec_offs2, ULINT_UNDEFINED,
|
|
|
|
&offset_heap);
|
|
|
|
|
|
|
|
ut_ad(rec_validate(mrec2->rec, sec_offs2));
|
|
|
|
|
2007-04-16 11:13:44 +02:00
|
|
|
switch (row_merge_cmp(mrec1, mrec2,
|
|
|
|
sec_offs1, sec_offs2, index)) {
|
|
|
|
case 0:
|
|
|
|
if (UNIV_UNLIKELY
|
|
|
|
(dict_index_is_unique(index))) {
|
|
|
|
goto error_handling;
|
|
|
|
}
|
|
|
|
/* fall through */
|
2007-04-30 08:25:47 +02:00
|
|
|
case -1:
|
2007-04-04 13:05:33 +02:00
|
|
|
rec_offsets = sec_offs1;
|
|
|
|
nth_rec1++;
|
|
|
|
offset2 = tmp_offset2;
|
2007-04-16 11:13:44 +02:00
|
|
|
break;
|
2007-04-30 08:25:47 +02:00
|
|
|
case 1:
|
2007-04-16 11:13:44 +02:00
|
|
|
mrec1 = mrec2;
|
2007-04-04 13:05:33 +02:00
|
|
|
rec_offsets = sec_offs2;
|
|
|
|
nth_rec2++;
|
|
|
|
offset1 = tmp_offset1;
|
2007-04-16 11:13:44 +02:00
|
|
|
break;
|
2007-04-30 08:25:47 +02:00
|
|
|
default:
|
|
|
|
ut_error;
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ut_ad(mrec1);
|
|
|
|
ut_ad(rec_validate(mrec1->rec, rec_offsets));
|
|
|
|
|
|
|
|
/* If the first output block is not yet full test whether this
|
|
|
|
new data tuple fits to block. If not this new data tuple must
|
|
|
|
be inserted to second output block */
|
|
|
|
|
|
|
|
if (fits_to_new) {
|
|
|
|
fits_to_new = row_merge_rec_fits_to_block(
|
|
|
|
rec_offsets, offset3);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fits_to_new) {
|
|
|
|
offset3 = row_merge_store_rec_to_block(
|
|
|
|
mrec1->rec, rec_offsets, new_block1, offset3);
|
|
|
|
} else {
|
2007-05-18 13:37:05 +02:00
|
|
|
ut_a(row_merge_rec_fits_to_block(rec_offsets,
|
|
|
|
offset4));
|
|
|
|
|
2007-04-04 13:05:33 +02:00
|
|
|
offset4 = row_merge_store_rec_to_block(
|
|
|
|
mrec1->rec, rec_offsets, new_block2, offset4);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TODO: If we are using variable length keys, then in
|
|
|
|
some cases these keys do not fit to two empty blocks
|
|
|
|
in a different order. Therefore, some empty space is
|
|
|
|
left to every block. However, it has not been prooven
|
2007-04-12 12:32:16 +02:00
|
|
|
that this empty space is enough in all cases. Therefore,
|
2007-04-04 13:05:33 +02:00
|
|
|
here these overloaded records should be put on another
|
|
|
|
block. */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Free memory from old blocks and return pointers to new blocks */
|
|
|
|
|
|
|
|
if (offset_heap) {
|
|
|
|
mem_heap_free(offset_heap);
|
|
|
|
}
|
|
|
|
|
|
|
|
mem_heap_free(heap);
|
|
|
|
mem_free(block1);
|
|
|
|
mem_free(tmp);
|
|
|
|
|
|
|
|
ut_ad(row_merge_block_validate(new_block1, index));
|
|
|
|
ut_ad(row_merge_block_validate(new_block2, index));
|
|
|
|
|
|
|
|
*block2 = new_block2;
|
|
|
|
|
|
|
|
return(new_block1);
|
|
|
|
|
|
|
|
error_handling:
|
|
|
|
/* Duplicate key was found and unique key was requested. Free all
|
|
|
|
allocated memory and return NULL */
|
|
|
|
|
|
|
|
if (offset_heap) {
|
|
|
|
mem_heap_free(offset_heap);
|
|
|
|
}
|
|
|
|
|
|
|
|
mem_heap_free(heap);
|
|
|
|
mem_free(block1);
|
|
|
|
mem_free(tmp);
|
|
|
|
mem_free(new_block1);
|
|
|
|
mem_free(new_block2);
|
|
|
|
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************
|
|
|
|
Merge sort for linked list in the disk.
|
|
|
|
|
|
|
|
Merge sort takes the input list and makes log N passes along
|
|
|
|
the list and in each pass it combines each adjacent pair of
|
2007-04-17 10:53:47 +02:00
|
|
|
small sorted lists into one larger sorted list. When only one
|
|
|
|
pass is needed the whole output list must have been sorted.
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
The linked list is stored in the file system. File blocks represent
|
|
|
|
items of linked list. The list is singly linked by the next offset
|
|
|
|
stored in block header. Offset is calculated from the start of the
|
|
|
|
file. Thus whenever next item in the list is requested this item is
|
|
|
|
read from the disk. Similarly every item is witten back to the disk
|
|
|
|
when we have sorted two blocks in the memory.
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
In each pass, two lists of size block_size are merged into lists of
|
|
|
|
size block_size*2. Initially block_size=1. Merge starts by pointing
|
|
|
|
a temporary pointer list1 at the head of the list and also preparing
|
2007-04-12 12:32:16 +02:00
|
|
|
an empty list list_tail where elements will be appended. Then:
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
1) If block1 is NULL we terminate this pass.
|
|
|
|
|
|
|
|
2) Otherwise, there is at least one element in the next
|
|
|
|
pair of block_size lists therefore, increase the number of
|
|
|
|
merges performed in this pass.
|
|
|
|
|
|
|
|
3) Point another temporary pointer list2 as the same
|
|
|
|
place as list1. Iterate list2 by block_size elements
|
|
|
|
or until the end of the list. Let the list_size1 be the
|
|
|
|
number of elements in the list2.
|
|
|
|
|
|
|
|
4) Let list_size1=merge_size. Now we merge list starting at
|
|
|
|
list1 of length list_size2 with a list starting at list2 of
|
|
|
|
length at most list_size1.
|
|
|
|
|
|
|
|
5) So, as long as either the list1 is non-empty (list_size1)
|
|
|
|
or the list2 is non-empty (list_size2 and list2 pointing to
|
|
|
|
a element):
|
|
|
|
|
|
|
|
5.1) Select which list to take the next element from.
|
|
|
|
If either lists is empty, we choose from the other one.
|
|
|
|
If both lists are non-empty, compare the first element
|
|
|
|
of each and choose the lower one.
|
|
|
|
|
|
|
|
5.2) Remove that element, tmp, from the start of its
|
|
|
|
lists, by advancing list1 or list2 to next element
|
|
|
|
and decreasing list1_size or list2_size.
|
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
5.3) Append tmp to list_tail
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
6) At this point, we have advanced list1 until it is where
|
|
|
|
list2 started out and we have advanced list2 until it is
|
|
|
|
pointing at the next pair of block_size lists to merge.
|
|
|
|
Thus, set list1 to the value of list2 and go back to the
|
|
|
|
start of this loop.
|
|
|
|
|
|
|
|
As soon as a pass like this is performed with only one merge, the
|
|
|
|
algorithm terminates. Otherwise, double the value of block_size
|
|
|
|
and go back to the beginning. */
|
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
ulint
|
2007-04-04 13:05:33 +02:00
|
|
|
row_merge_sort_linked_list_in_disk(
|
|
|
|
/*===============================*/
|
|
|
|
/* out: offset to first block in
|
2007-04-12 12:32:16 +02:00
|
|
|
the list or ULINT_UNDEFINED in
|
2007-04-04 13:05:33 +02:00
|
|
|
case of error */
|
|
|
|
dict_index_t* index, /* in: index to be created */
|
|
|
|
os_file_t file, /* in: File handle */
|
|
|
|
int* error) /* out: 0 or error */
|
|
|
|
{
|
|
|
|
merge_block_t* block1;
|
|
|
|
merge_block_t* block2;
|
|
|
|
merge_block_t* backup1;
|
|
|
|
merge_block_t* backup2;
|
|
|
|
merge_file_t output;
|
|
|
|
ulint block_size;
|
2007-04-12 12:32:16 +02:00
|
|
|
ulint list_head = 0;
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
ut_ad(index);
|
|
|
|
|
|
|
|
/* Allocate memory for blocks */
|
2007-04-11 11:42:50 +02:00
|
|
|
backup1 = block1 = row_merge_block_create();
|
|
|
|
backup2 = block2 = row_merge_block_create();
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
output.file = file;
|
|
|
|
|
2007-04-12 14:58:09 +02:00
|
|
|
for (block_size = 1;; block_size *= 2) {
|
2007-04-17 11:44:29 +02:00
|
|
|
ibool sorted = TRUE;
|
2007-04-12 14:58:09 +02:00
|
|
|
ibool list_is_empty = TRUE;
|
2007-04-12 12:32:16 +02:00
|
|
|
|
2007-04-04 13:05:33 +02:00
|
|
|
block1 = backup1;
|
|
|
|
|
2007-04-13 14:11:43 +02:00
|
|
|
if (!row_merge_block_read(file, list_head, block1)) {
|
|
|
|
file_error:
|
|
|
|
*error = DB_CORRUPTION;
|
|
|
|
goto err_exit;
|
|
|
|
}
|
2007-04-04 13:05:33 +02:00
|
|
|
ut_ad(row_merge_block_validate(block1, index));
|
|
|
|
|
2007-04-11 11:42:50 +02:00
|
|
|
for (;;) {
|
2007-04-12 14:58:09 +02:00
|
|
|
ulint offset = block1->header.offset;
|
|
|
|
ulint list1_size = 0;
|
|
|
|
ulint list2_size = block_size;
|
|
|
|
ulint i;
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
/* Count how many list elements we have in the list. */
|
|
|
|
|
|
|
|
for (i = 0; i < block_size; i++) {
|
2007-04-12 14:58:09 +02:00
|
|
|
merge_block_header_t header;
|
|
|
|
|
2007-04-04 13:05:33 +02:00
|
|
|
list1_size++;
|
|
|
|
|
|
|
|
/* Here read only the header to iterate the
|
|
|
|
list in the disk. */
|
|
|
|
|
2007-04-13 14:11:43 +02:00
|
|
|
if (!row_merge_block_header_read(file, offset,
|
|
|
|
&header)) {
|
|
|
|
goto file_error;
|
|
|
|
}
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
offset = header.next;
|
|
|
|
|
|
|
|
/* If the offset is zero we have arrived to the
|
|
|
|
end of disk list */
|
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
if (!offset) {
|
2007-04-04 13:05:33 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If offset is zero we have reached end of the list in
|
|
|
|
the disk. */
|
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
if (!offset) {
|
2007-04-04 13:05:33 +02:00
|
|
|
block2 = NULL;
|
|
|
|
} else {
|
|
|
|
block2 = backup2;
|
2007-04-13 14:11:43 +02:00
|
|
|
if (!row_merge_block_read(
|
|
|
|
file, offset, block2)) {
|
|
|
|
goto file_error;
|
|
|
|
}
|
2007-04-04 13:05:33 +02:00
|
|
|
ut_ad(row_merge_block_validate(block2, index));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If list2 is not empty, we have two lists to merge.
|
2007-04-17 10:53:47 +02:00
|
|
|
Otherwise, we have a sorted list. */
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
while (list1_size > 0 || (list2_size > 0 && block2)) {
|
|
|
|
/* Merge sort two lists by deciding whether
|
|
|
|
next element of merge comes from list1 or
|
|
|
|
list2. */
|
2007-04-11 11:42:50 +02:00
|
|
|
merge_block_t* tmp;
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
if (list1_size == 0) {
|
|
|
|
/* First list is empty, next element
|
|
|
|
must come from the second list. */
|
|
|
|
|
|
|
|
tmp = block2;
|
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
if (!block2->header.next) {
|
2007-04-04 13:05:33 +02:00
|
|
|
block2 = NULL;
|
2007-04-12 14:58:09 +02:00
|
|
|
list2_size = 0;
|
|
|
|
} else {
|
|
|
|
list2_size--;
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
} else if (list2_size == 0 || !block2) {
|
|
|
|
/* Second list is empty, next record
|
|
|
|
must come from the first list. */
|
|
|
|
|
|
|
|
tmp = block1;
|
|
|
|
list1_size--;
|
|
|
|
} else {
|
|
|
|
/* Both lists contain a block and we
|
|
|
|
need to merge records on these block */
|
|
|
|
|
2007-04-11 11:42:50 +02:00
|
|
|
tmp = row_merge_block_merge(
|
|
|
|
block1, &block2, index);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
if (tmp == NULL) {
|
2007-04-13 14:11:43 +02:00
|
|
|
*error = DB_DUPLICATE_KEY;
|
|
|
|
goto err_exit;
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
2007-04-12 14:58:09 +02:00
|
|
|
block1 = backup1 = tmp;
|
|
|
|
backup2 = block2;
|
|
|
|
|
2007-04-04 13:05:33 +02:00
|
|
|
list1_size--;
|
|
|
|
}
|
|
|
|
|
2007-04-12 14:58:09 +02:00
|
|
|
/* Store the head offset of the disk
|
|
|
|
list. Note that only records in the
|
|
|
|
blocks are changed not the order of
|
|
|
|
the blocks in the disk. */
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
if (list_is_empty) {
|
|
|
|
list_is_empty = FALSE;
|
|
|
|
list_head = tmp->header.offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
ut_ad(row_merge_block_validate(tmp, index));
|
|
|
|
|
2007-04-13 14:11:43 +02:00
|
|
|
if (!row_merge_block_write(
|
|
|
|
file, tmp->header.offset, tmp)) {
|
|
|
|
goto file_error;
|
|
|
|
}
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
/* Now we can read the next record from the
|
|
|
|
selected list if it contains more records */
|
|
|
|
|
2007-04-13 14:11:43 +02:00
|
|
|
if (tmp->header.next
|
|
|
|
&& !row_merge_block_read(file,
|
2007-04-12 12:32:16 +02:00
|
|
|
tmp->header.next,
|
2007-04-13 14:11:43 +02:00
|
|
|
tmp)) {
|
|
|
|
goto file_error;
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-04-11 11:42:50 +02:00
|
|
|
/* Now we have processed block_size items from
|
|
|
|
the disk. Swap blocks using pointers. */
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-11 11:42:50 +02:00
|
|
|
if (!block2) {
|
2007-04-17 11:44:29 +02:00
|
|
|
if (sorted) {
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
break;
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
2007-04-11 11:42:50 +02:00
|
|
|
|
2007-04-17 11:44:29 +02:00
|
|
|
sorted = FALSE;
|
2007-04-11 11:42:50 +02:00
|
|
|
block2 = backup1;
|
|
|
|
block1 = backup2;
|
|
|
|
backup2 = block2;
|
|
|
|
backup1 = block1;
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-04-13 14:11:43 +02:00
|
|
|
err_exit:
|
2007-04-17 10:53:47 +02:00
|
|
|
list_head = ULINT_UNDEFINED;
|
|
|
|
func_exit:
|
2007-04-13 14:11:43 +02:00
|
|
|
mem_free(backup1);
|
|
|
|
mem_free(backup2);
|
2007-04-17 10:53:47 +02:00
|
|
|
return(list_head);
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
Merge sort linked list in the memory and store part of the linked
|
|
|
|
list into a block and write this block to the disk. */
|
|
|
|
static
|
2007-04-13 14:11:43 +02:00
|
|
|
ibool
|
2007-04-04 13:05:33 +02:00
|
|
|
row_merge_sort_and_store(
|
|
|
|
/*=====================*/
|
2007-04-13 14:11:43 +02:00
|
|
|
/* out: FALSE on error */
|
2007-04-04 13:05:33 +02:00
|
|
|
dict_index_t* index, /* in: Index */
|
|
|
|
merge_file_t* file, /* in: File where to write index
|
|
|
|
entries */
|
|
|
|
merge_block_t* block, /* in/out: Block where to store
|
|
|
|
the list */
|
|
|
|
merge_rec_list_t** list) /* in/out: Pointer to the list */
|
|
|
|
{
|
|
|
|
ut_ad(index && file && block && list);
|
|
|
|
|
|
|
|
/* Firstly, merge sort linked list in the memory */
|
|
|
|
if (!row_merge_sort_linked_list(index, *list)) {
|
2007-04-13 14:11:43 +02:00
|
|
|
return(FALSE);
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Secondly, write part of the linked list to the block */
|
|
|
|
*list = row_merge_write_list_to_block(*list, block, index);
|
|
|
|
|
|
|
|
ut_ad(row_merge_block_validate(block, index));
|
|
|
|
|
|
|
|
/* Next block will be written directly behind this one. This will
|
|
|
|
create a 'linked list' of blocks to the disk. */
|
|
|
|
|
|
|
|
block->header.offset = file->offset;
|
2007-04-12 12:32:16 +02:00
|
|
|
block->header.next = ++file->offset;
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
/* Thirdly, write block to the disk */
|
2007-04-13 14:11:43 +02:00
|
|
|
return(row_merge_block_write(file->file, block->header.offset, block));
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef UNIV_DEBUG_INDEX_CREATE
|
|
|
|
/************************************************************************
|
|
|
|
Pretty print data tuple */
|
|
|
|
static
|
|
|
|
void
|
|
|
|
row_merge_dtuple_print(
|
|
|
|
/*===================*/
|
|
|
|
FILE* f, /* in: output stream */
|
|
|
|
dtuple_t* dtuple) /* in: data tuple */
|
|
|
|
{
|
|
|
|
ulint n_fields;
|
|
|
|
ulint i;
|
|
|
|
|
|
|
|
ut_ad(f && dtuple);
|
|
|
|
|
|
|
|
n_fields = dtuple_get_n_fields(dtuple);
|
|
|
|
|
|
|
|
fprintf(f, "DATA TUPLE: %lu fields;\n", (ulong) n_fields);
|
|
|
|
|
|
|
|
for (i = 0; i < n_fields; i++) {
|
|
|
|
dfield_t* dfield;
|
|
|
|
|
|
|
|
dfield = dtuple_get_nth_field(dtuple, i);
|
|
|
|
|
|
|
|
fprintf(f, "%lu: ", (ulong) i);
|
|
|
|
|
|
|
|
if (dfield->len != UNIV_SQL_NULL) {
|
|
|
|
dfield_print_also_hex(dfield);
|
|
|
|
} else {
|
|
|
|
fputs(" SQL NULL", f);
|
|
|
|
}
|
|
|
|
|
|
|
|
putc(';', f);
|
|
|
|
}
|
|
|
|
|
|
|
|
putc('\n', f);
|
|
|
|
ut_ad(dtuple_validate(dtuple));
|
|
|
|
}
|
|
|
|
#endif /* UNIV_DEBUG_INDEX_CREATE */
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
Reads clustered index of the table and create temporary files
|
|
|
|
containing index entries for indexes to be built. */
|
|
|
|
|
|
|
|
ulint
|
|
|
|
row_merge_read_clustered_index(
|
|
|
|
/*===========================*/
|
|
|
|
/* out: DB_SUCCESS if successfull,
|
|
|
|
or ERROR code */
|
|
|
|
trx_t* trx, /* in: transaction */
|
|
|
|
dict_table_t* table, /* in: table where index is created */
|
|
|
|
dict_index_t** index, /* in: indexes to be created */
|
2007-04-11 10:16:14 +02:00
|
|
|
merge_file_t* files, /* in: Files where to write index
|
2007-04-04 13:05:33 +02:00
|
|
|
entries */
|
|
|
|
ulint num_of_idx) /* in: number of indexes to be
|
|
|
|
created */
|
|
|
|
{
|
2007-05-22 08:48:53 +02:00
|
|
|
dict_index_t* clust_index; /* Clustered index */
|
2007-04-04 13:05:33 +02:00
|
|
|
merge_rec_t* new_mrec; /* New merge record */
|
|
|
|
mem_heap_t* row_heap; /* Heap memory to create
|
|
|
|
clustered index records */
|
|
|
|
mem_heap_t* heap; /* Memory heap for
|
|
|
|
record lists and offsets */
|
|
|
|
merge_block_t* block; /* Merge block where records
|
|
|
|
are stored for memory sort and
|
|
|
|
then written to the disk */
|
|
|
|
merge_rec_list_t** merge_list; /* Temporary list for records*/
|
|
|
|
btr_pcur_t pcur; /* Persistent cursor on the
|
2007-05-22 08:48:53 +02:00
|
|
|
clustered index */
|
2007-04-04 13:05:33 +02:00
|
|
|
mtr_t mtr; /* Mini transaction */
|
|
|
|
ulint err = DB_SUCCESS; /* Return code */
|
|
|
|
ulint idx_num = 0; /* Index number */
|
|
|
|
ulint n_blocks = 0; /* Number of blocks written
|
|
|
|
to disk */
|
|
|
|
ulint sec_offsets_[REC_OFFS_NORMAL_SIZE];
|
|
|
|
ulint* sec_offs = sec_offsets_;
|
|
|
|
|
|
|
|
*sec_offsets_ = (sizeof sec_offsets_) / sizeof *sec_offsets_;
|
|
|
|
|
2007-05-22 08:48:53 +02:00
|
|
|
trx->op_info="reading clustered index";
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-05-22 08:48:53 +02:00
|
|
|
ut_ad(trx);
|
|
|
|
ut_ad(table);
|
|
|
|
ut_ad(index);
|
|
|
|
ut_ad(files);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
/* Create block where index entries are stored */
|
|
|
|
block = row_merge_block_create();
|
|
|
|
|
|
|
|
/* Create and initialize memory for record lists */
|
|
|
|
|
|
|
|
heap = mem_heap_create(256);
|
|
|
|
merge_list = mem_heap_alloc(heap, num_of_idx * sizeof *merge_list);
|
|
|
|
|
|
|
|
for (idx_num = 0; idx_num < num_of_idx; idx_num++) {
|
|
|
|
merge_list[idx_num] = row_merge_create_list();
|
|
|
|
}
|
|
|
|
|
|
|
|
mtr_start(&mtr);
|
|
|
|
|
|
|
|
/* Find the clustered index and create a persistent cursor
|
|
|
|
based on that. */
|
|
|
|
|
|
|
|
clust_index = dict_table_get_first_index(table);
|
|
|
|
|
|
|
|
btr_pcur_open_at_index_side(
|
|
|
|
TRUE, clust_index, BTR_SEARCH_LEAF, &pcur, TRUE, &mtr);
|
|
|
|
|
|
|
|
row_heap = mem_heap_create(512);
|
|
|
|
|
|
|
|
/* Iterate all records in the clustered index */
|
2007-05-22 08:48:53 +02:00
|
|
|
for (;;) {
|
|
|
|
const rec_t* rec;
|
2007-04-04 13:05:33 +02:00
|
|
|
dtuple_t* row;
|
|
|
|
row_ext_t* ext;
|
|
|
|
|
2007-05-22 08:48:53 +02:00
|
|
|
/* When switching pages, commit the mini-transaction
|
|
|
|
in order to release the latch on the old page. */
|
|
|
|
|
|
|
|
if (btr_pcur_is_after_last_on_page(&pcur, &mtr)) {
|
|
|
|
btr_pcur_store_position(&pcur, &mtr);
|
|
|
|
mtr_commit(&mtr);
|
|
|
|
mtr_start(&mtr);
|
|
|
|
btr_pcur_restore_position(BTR_SEARCH_LEAF,
|
|
|
|
&pcur, &mtr);
|
|
|
|
}
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-05-22 08:48:53 +02:00
|
|
|
if (!btr_pcur_move_to_next(&pcur, &mtr)) {
|
|
|
|
break;
|
|
|
|
}
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-05-22 08:48:53 +02:00
|
|
|
rec = btr_pcur_get_rec(&pcur);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
/* We don't count the delete marked records as "Inserted" */
|
2007-05-22 08:48:53 +02:00
|
|
|
if (!rec_get_deleted_flag(rec, dict_table_is_comp(table))) {
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
srv_n_rows_inserted++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Build row based on clustered index */
|
|
|
|
mem_heap_empty(row_heap);
|
|
|
|
|
|
|
|
row = row_build(ROW_COPY_POINTERS,
|
|
|
|
clust_index, rec, NULL, &ext, row_heap);
|
|
|
|
|
|
|
|
/* If the user has requested the creation of several indexes
|
|
|
|
for the same table. We build all index entries in a single
|
2007-05-22 08:48:53 +02:00
|
|
|
pass over the clustered index. */
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
for (idx_num = 0; idx_num < num_of_idx; idx_num++) {
|
|
|
|
|
|
|
|
dtuple_t* index_tuple;
|
|
|
|
|
|
|
|
index_tuple = row_build_index_entry(
|
|
|
|
row, ext,
|
|
|
|
index[idx_num], merge_list[idx_num]->heap);
|
|
|
|
|
|
|
|
#ifdef UNIV_DEBUG_INDEX_CREATE
|
|
|
|
row_merge_dtuple_print(stderr, index_tuple);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
new_mrec = row_merge_rec_create(
|
|
|
|
index_tuple,
|
|
|
|
ext ? ext->ext : NULL, ext ? ext->n_ext : 0,
|
|
|
|
index[idx_num], merge_list[idx_num]->heap);
|
|
|
|
|
|
|
|
sec_offs = rec_get_offsets(
|
|
|
|
new_mrec->rec, index[idx_num], sec_offs,
|
|
|
|
ULINT_UNDEFINED, &heap);
|
|
|
|
|
|
|
|
/* Add data tuple to linked list of data tuples */
|
|
|
|
|
|
|
|
row_merge_list_add(
|
|
|
|
new_mrec, rec_offs_size(sec_offs),
|
|
|
|
merge_list[idx_num]);
|
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
/* If we have enough data tuples to form a block
|
2007-04-04 13:05:33 +02:00
|
|
|
sort linked list and store it to the block and
|
|
|
|
write this block to the disk. Note that not all
|
|
|
|
data tuples in the list fit to the block.*/
|
|
|
|
|
|
|
|
if (merge_list[idx_num]->total_size >=
|
|
|
|
MERGE_BLOCK_SIZE) {
|
|
|
|
|
|
|
|
if (!row_merge_sort_and_store(
|
|
|
|
index[idx_num],
|
2007-04-11 10:16:14 +02:00
|
|
|
&files[idx_num],
|
2007-04-04 13:05:33 +02:00
|
|
|
block,
|
|
|
|
&(merge_list[idx_num]))) {
|
|
|
|
|
|
|
|
trx->error_key_num = idx_num;
|
|
|
|
err = DB_DUPLICATE_KEY;
|
|
|
|
goto error_handling;
|
|
|
|
}
|
|
|
|
|
|
|
|
n_blocks++;
|
2007-04-11 10:16:14 +02:00
|
|
|
files[idx_num].num_of_blocks++;
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now we have to write all remaining items in the list to
|
|
|
|
blocks and write these blocks to the disk */
|
|
|
|
|
|
|
|
for (idx_num = 0; idx_num < num_of_idx; idx_num++) {
|
|
|
|
|
|
|
|
/* While we have items in the list write them
|
|
|
|
to the block */
|
2007-04-16 09:11:00 +02:00
|
|
|
ut_ad(!merge_list[idx_num]->head
|
|
|
|
== !merge_list[idx_num]->tail);
|
|
|
|
ut_ad(!merge_list[idx_num]->n_records
|
|
|
|
== !merge_list[idx_num]->head);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-16 09:11:00 +02:00
|
|
|
if (merge_list[idx_num]->head) {
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
/* Next block will be written directly
|
|
|
|
behind this one. This will create a
|
|
|
|
'linked list' of blocks to the disk. */
|
|
|
|
|
2007-04-11 10:16:14 +02:00
|
|
|
block->header.offset = files[idx_num].offset;
|
2007-04-12 12:32:16 +02:00
|
|
|
block->header.next = files[idx_num].offset + 1;
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
if (!row_merge_sort_and_store(
|
|
|
|
index[idx_num],
|
2007-04-11 10:16:14 +02:00
|
|
|
&files[idx_num],
|
2007-04-04 13:05:33 +02:00
|
|
|
block,
|
|
|
|
&(merge_list[idx_num]))) {
|
|
|
|
|
|
|
|
trx->error_key_num = idx_num;
|
|
|
|
err = DB_DUPLICATE_KEY;
|
|
|
|
goto error_handling;
|
|
|
|
}
|
|
|
|
|
2007-04-11 10:16:14 +02:00
|
|
|
files[idx_num].num_of_blocks++;
|
2007-04-04 13:05:33 +02:00
|
|
|
n_blocks++;
|
|
|
|
}
|
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
/* Write the last block. */
|
|
|
|
block->header.next = 0; /* end-of-list marker */
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-13 14:11:43 +02:00
|
|
|
if (!row_merge_block_header_write(
|
|
|
|
files[idx_num].file, &block->header)) {
|
|
|
|
err = DB_CORRUPTION;
|
|
|
|
goto error_handling;
|
|
|
|
}
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef UNIV_DEBUG_INDEX_CREATE
|
|
|
|
fprintf(stderr, "Stored %lu blocks\n", n_blocks);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
error_handling:
|
|
|
|
|
|
|
|
/* Cleanup resources */
|
|
|
|
|
|
|
|
btr_pcur_close(&pcur);
|
|
|
|
mtr_commit(&mtr);
|
|
|
|
mem_heap_free(row_heap);
|
|
|
|
mem_free(block);
|
|
|
|
|
|
|
|
for (idx_num = 0; idx_num < num_of_idx; idx_num++) {
|
|
|
|
mem_heap_free(merge_list[idx_num]->heap);
|
|
|
|
}
|
|
|
|
|
|
|
|
mem_heap_free(heap);
|
|
|
|
|
|
|
|
trx->op_info="";
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
Read sorted file containing index data tuples and insert these data
|
|
|
|
tuples to the index */
|
|
|
|
|
|
|
|
ulint
|
|
|
|
row_merge_insert_index_tuples(
|
|
|
|
/*==========================*/
|
|
|
|
/* out: 0 or error number */
|
|
|
|
trx_t* trx, /* in: transaction */
|
|
|
|
dict_index_t* index, /* in: index */
|
|
|
|
dict_table_t* table, /* in: table */
|
|
|
|
os_file_t file, /* in: file handle */
|
2007-04-12 12:32:16 +02:00
|
|
|
ulint offset) /* in: offset where to start
|
2007-04-04 13:05:33 +02:00
|
|
|
reading */
|
|
|
|
{
|
|
|
|
merge_block_t* block;
|
|
|
|
que_thr_t* thr;
|
|
|
|
ins_node_t* node;
|
2007-04-13 14:11:43 +02:00
|
|
|
mem_heap_t* heap;
|
2007-04-04 13:05:33 +02:00
|
|
|
mem_heap_t* graph_heap;
|
|
|
|
ulint error = DB_SUCCESS;
|
|
|
|
|
|
|
|
ut_ad(trx && index && table);
|
|
|
|
|
|
|
|
/* We use the insert query graph as the dummy graph
|
|
|
|
needed in the row module call */
|
|
|
|
|
|
|
|
trx->op_info = "inserting index entries";
|
|
|
|
|
2007-04-13 14:11:43 +02:00
|
|
|
graph_heap = mem_heap_create(500);
|
2007-04-04 13:05:33 +02:00
|
|
|
node = ins_node_create(INS_DIRECT, table, graph_heap);
|
|
|
|
|
|
|
|
thr = pars_complete_graph_for_exec(node, trx, graph_heap);
|
|
|
|
|
|
|
|
que_thr_move_to_run_state_for_mysql(thr, trx);
|
|
|
|
|
|
|
|
block = row_merge_block_create();
|
2007-04-13 14:11:43 +02:00
|
|
|
heap = mem_heap_create(1000);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
do {
|
2007-04-04 13:05:33 +02:00
|
|
|
ulint n_rec;
|
2007-04-13 14:11:43 +02:00
|
|
|
ulint tuple_offset = 0;
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-13 14:11:43 +02:00
|
|
|
if (!row_merge_block_read(file, offset, block)) {
|
|
|
|
error = DB_CORRUPTION;
|
|
|
|
break;
|
|
|
|
}
|
2007-04-12 12:32:16 +02:00
|
|
|
|
2007-04-04 13:05:33 +02:00
|
|
|
ut_ad(row_merge_block_validate(block, index));
|
|
|
|
|
|
|
|
for (n_rec = 0; n_rec < block->header.n_records; n_rec++) {
|
2007-04-13 14:11:43 +02:00
|
|
|
merge_rec_t* mrec = row_merge_read_rec_from_block(
|
|
|
|
block, &tuple_offset, heap, index);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
if (!rec_get_deleted_flag(mrec->rec, 0)) {
|
|
|
|
|
2007-04-13 14:11:43 +02:00
|
|
|
dtuple_t* dtuple = row_rec_to_index_entry(
|
2007-04-04 13:05:33 +02:00
|
|
|
ROW_COPY_POINTERS,
|
2007-04-13 14:11:43 +02:00
|
|
|
index, mrec->rec, heap);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
node->row = dtuple;
|
|
|
|
node->table = table;
|
|
|
|
node->trx_id = trx->id;
|
|
|
|
|
|
|
|
ut_ad(dtuple_validate(dtuple));
|
|
|
|
|
|
|
|
#ifdef UNIV_DEBUG_INDEX_CREATE
|
|
|
|
row_merge_dtuple_print(stderr, dtuple);
|
|
|
|
#endif
|
|
|
|
|
2007-04-13 14:11:43 +02:00
|
|
|
do {
|
|
|
|
thr->run_node = thr;
|
|
|
|
thr->prev_node = thr->common.parent;
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-13 14:11:43 +02:00
|
|
|
error = row_ins_index_entry(
|
|
|
|
index, dtuple, NULL, 0, thr);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-13 14:11:43 +02:00
|
|
|
if (error == DB_SUCCESS) {
|
|
|
|
goto next_rec;
|
|
|
|
}
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-13 14:11:43 +02:00
|
|
|
thr->lock_state = QUE_THR_LOCK_ROW;
|
|
|
|
trx->error_state = error;
|
|
|
|
que_thr_stop_for_mysql(thr);
|
|
|
|
thr->lock_state = QUE_THR_LOCK_NOLOCK;
|
|
|
|
} while (row_mysql_handle_errors(&error, trx,
|
|
|
|
thr, NULL));
|
|
|
|
|
|
|
|
goto err_exit;
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
2007-04-13 14:11:43 +02:00
|
|
|
|
|
|
|
next_rec:
|
|
|
|
mem_heap_empty(heap);
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
offset = block->header.next;
|
|
|
|
|
|
|
|
/* If we have reached the end of the disk list we have
|
|
|
|
inserted all of the index entries to the index. */
|
2007-04-12 12:32:16 +02:00
|
|
|
} while (offset);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
que_thr_stop_for_mysql_no_error(thr, trx);
|
2007-04-13 14:11:43 +02:00
|
|
|
err_exit:
|
2007-04-04 13:05:33 +02:00
|
|
|
que_graph_free(thr->graph);
|
|
|
|
|
|
|
|
trx->op_info = "";
|
|
|
|
|
2007-04-13 14:11:43 +02:00
|
|
|
mem_free(block);
|
|
|
|
mem_heap_free(heap);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
return(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
2007-06-04 09:49:31 +02:00
|
|
|
Drop an index from the InnoDB system tables. */
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-06-04 09:49:31 +02:00
|
|
|
void
|
|
|
|
row_merge_drop_index(
|
|
|
|
/*=================*/
|
2007-04-04 13:05:33 +02:00
|
|
|
/* out: error code or DB_SUCCESS */
|
|
|
|
dict_index_t* index, /* in: index to be removed */
|
|
|
|
dict_table_t* table, /* in: table */
|
|
|
|
trx_t* trx) /* in: transaction handle */
|
|
|
|
{
|
|
|
|
ulint err;
|
|
|
|
ibool dict_lock = FALSE;
|
2007-04-11 14:42:54 +02:00
|
|
|
pars_info_t* info = pars_info_create();
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
/* We use the private SQL parser of Innobase to generate the
|
|
|
|
query graphs needed in deleting the dictionary data from system
|
|
|
|
tables in Innobase. Deleting a row from SYS_INDEXES table also
|
|
|
|
frees the file segments of the B-tree associated with the index. */
|
|
|
|
|
|
|
|
static const char str1[] =
|
|
|
|
"PROCEDURE DROP_INDEX_PROC () IS\n"
|
|
|
|
"BEGIN\n"
|
2007-04-11 14:42:54 +02:00
|
|
|
"DELETE FROM SYS_FIELDS WHERE INDEX_ID = :indexid;\n"
|
|
|
|
"DELETE FROM SYS_INDEXES WHERE ID = :indexid\n"
|
|
|
|
" AND TABLE_ID = :tableid;\n"
|
2007-04-04 13:05:33 +02:00
|
|
|
"END;\n";
|
|
|
|
|
|
|
|
ut_ad(index && table && trx);
|
|
|
|
|
2007-04-11 14:42:54 +02:00
|
|
|
pars_info_add_dulint_literal(info, "indexid", index->id);
|
|
|
|
pars_info_add_dulint_literal(info, "tableid", table->id);
|
|
|
|
|
2007-04-04 13:05:33 +02:00
|
|
|
trx_start_if_not_started(trx);
|
|
|
|
trx->op_info = "dropping index";
|
|
|
|
|
|
|
|
if (trx->dict_operation_lock_mode == 0) {
|
|
|
|
row_mysql_lock_data_dictionary(trx);
|
|
|
|
dict_lock = TRUE;
|
|
|
|
}
|
|
|
|
|
2007-04-11 14:42:54 +02:00
|
|
|
err = que_eval_sql(info, str1, FALSE, trx);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-11 14:42:54 +02:00
|
|
|
ut_a(err == DB_SUCCESS);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-11 14:42:54 +02:00
|
|
|
/* Replace this index with another equivalent index for all
|
|
|
|
foreign key constraints on this table where this index is used */
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-11 14:42:54 +02:00
|
|
|
dict_table_replace_index_in_foreign_list(table, index);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-11 14:42:54 +02:00
|
|
|
if (trx->dict_redo_list) {
|
|
|
|
dict_redo_remove_index(trx, index);
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
2007-04-11 14:42:54 +02:00
|
|
|
dict_index_remove_from_cache(table, index);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
if (dict_lock) {
|
|
|
|
row_mysql_unlock_data_dictionary(trx);
|
|
|
|
}
|
|
|
|
|
|
|
|
trx->op_info = "";
|
2007-06-04 09:49:31 +02:00
|
|
|
}
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-06-04 09:49:31 +02:00
|
|
|
/*************************************************************************
|
|
|
|
Drop those indexes which were created before an error occurred
|
|
|
|
when building an index. */
|
|
|
|
|
|
|
|
void
|
|
|
|
row_merge_drop_indexes(
|
|
|
|
/*===================*/
|
|
|
|
trx_t* trx, /* in: transaction */
|
|
|
|
dict_table_t* table, /* in: table containing the indexes */
|
|
|
|
dict_index_t** index, /* in: indexes to drop */
|
|
|
|
ulint num_created) /* in: number of elements in index[] */
|
|
|
|
{
|
|
|
|
ulint key_num;
|
|
|
|
|
|
|
|
for (key_num = 0; key_num < num_created; key_num++) {
|
|
|
|
row_merge_drop_index(index[key_num], table, trx);
|
|
|
|
}
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
Allocate and initialize memory for a merge file structure */
|
|
|
|
|
2007-04-11 10:16:14 +02:00
|
|
|
void
|
|
|
|
row_merge_file_create(
|
|
|
|
/*==================*/
|
|
|
|
merge_file_t* merge_file) /* out: merge file structure */
|
2007-04-04 13:05:33 +02:00
|
|
|
{
|
|
|
|
merge_file->file = innobase_mysql_tmpfile();
|
|
|
|
|
2007-04-12 12:32:16 +02:00
|
|
|
merge_file->offset = 0;
|
2007-04-04 13:05:33 +02:00
|
|
|
merge_file->num_of_blocks = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
Create a temporary table using a definition of the old table. You must
|
|
|
|
lock data dictionary before calling this function. */
|
|
|
|
|
|
|
|
dict_table_t*
|
|
|
|
row_merge_create_temporary_table(
|
|
|
|
/*=============================*/
|
|
|
|
/* out: new temporary table
|
|
|
|
definition */
|
|
|
|
const char* table_name, /* in: new table name */
|
|
|
|
dict_table_t* table, /* in: old table definition */
|
|
|
|
trx_t* trx, /* in: trx */
|
|
|
|
ulint* error) /* in:out/ error code or DB_SUCCESS */
|
|
|
|
{
|
|
|
|
ulint i;
|
|
|
|
dict_table_t* new_table = NULL;
|
|
|
|
ulint n_cols = dict_table_get_n_user_cols(table);
|
|
|
|
|
|
|
|
ut_ad(table_name && table && error);
|
|
|
|
|
|
|
|
#ifdef UNIV_SYNC_DEBUG
|
|
|
|
ut_ad(mutex_own(&dict_sys->mutex));
|
|
|
|
#endif /* UNIV_SYNC_DEBUG */
|
|
|
|
|
|
|
|
*error = row_undo_report_create_table_dict_operation(trx, table_name);
|
|
|
|
|
|
|
|
if (*error == DB_SUCCESS) {
|
|
|
|
|
|
|
|
mem_heap_t* heap = mem_heap_create(1000);
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
|
|
|
|
new_table = dict_mem_table_create(
|
|
|
|
table_name, 0, n_cols, table->flags);
|
|
|
|
|
|
|
|
for (i = 0; i < n_cols; i++) {
|
|
|
|
const dict_col_t* col;
|
|
|
|
|
|
|
|
col = dict_table_get_nth_col(table, i);
|
|
|
|
|
|
|
|
dict_mem_table_add_col(
|
|
|
|
new_table, heap,
|
|
|
|
dict_table_get_col_name(table, i),
|
|
|
|
col->mtype, col->prtype, col->len);
|
|
|
|
}
|
|
|
|
|
|
|
|
*error = row_create_table_for_mysql(new_table, trx);
|
2007-04-04 16:26:06 +02:00
|
|
|
mem_heap_free(heap);
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return(new_table);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
Rename the indexes in the dicitionary. */
|
|
|
|
|
|
|
|
ulint
|
|
|
|
row_merge_rename_index(
|
|
|
|
/*===================*/
|
|
|
|
trx_t* trx, /* in: Transaction */
|
|
|
|
dict_table_t* table, /* in: Table for index */
|
|
|
|
dict_index_t* index) /* in: Index to rename */
|
|
|
|
{
|
|
|
|
ibool dict_lock = FALSE;
|
|
|
|
ulint err = DB_SUCCESS;
|
2007-04-11 14:42:54 +02:00
|
|
|
pars_info_t* info = pars_info_create();
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
/* Only rename from temp names */
|
|
|
|
ut_a(*index->name == TEMP_TABLE_PREFIX);
|
|
|
|
|
|
|
|
/* We use the private SQL parser of Innobase to generate the
|
|
|
|
query graphs needed in renaming index. */
|
|
|
|
|
|
|
|
static const char str1[] =
|
|
|
|
"PROCEDURE RENAME_INDEX_PROC () IS\n"
|
|
|
|
"BEGIN\n"
|
2007-04-11 14:42:54 +02:00
|
|
|
"UPDATE SYS_INDEXES SET NAME = :name\n"
|
|
|
|
" WHERE ID = :indexid AND TABLE_ID = :tableid;\n"
|
2007-04-04 13:05:33 +02:00
|
|
|
"END;\n";
|
|
|
|
|
|
|
|
table = index->table;
|
|
|
|
|
|
|
|
ut_ad(index && table && trx);
|
|
|
|
|
|
|
|
trx_start_if_not_started(trx);
|
|
|
|
trx->op_info = "renaming index";
|
|
|
|
|
2007-04-11 14:42:54 +02:00
|
|
|
pars_info_add_str_literal(info, "name", index->name);
|
|
|
|
pars_info_add_dulint_literal(info, "indexid", index->id);
|
|
|
|
pars_info_add_dulint_literal(info, "tableid", table->id);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
if (trx->dict_operation_lock_mode == 0) {
|
|
|
|
row_mysql_lock_data_dictionary(trx);
|
|
|
|
dict_lock = TRUE;
|
|
|
|
}
|
|
|
|
|
2007-04-11 14:42:54 +02:00
|
|
|
err = que_eval_sql(info, str1, FALSE, trx);
|
2007-04-04 13:05:33 +02:00
|
|
|
|
|
|
|
if (err == DB_SUCCESS) {
|
2007-04-11 14:42:54 +02:00
|
|
|
index->name++;
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (dict_lock) {
|
|
|
|
row_mysql_unlock_data_dictionary(trx);
|
|
|
|
}
|
|
|
|
|
|
|
|
trx->op_info = "";
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
Create the index and load in to the dicitionary. */
|
|
|
|
|
|
|
|
ulint
|
|
|
|
row_merge_create_index(
|
|
|
|
/*===================*/
|
|
|
|
trx_t* trx, /* in: transaction */
|
|
|
|
dict_index_t** index, /* out: the instance of the index */
|
|
|
|
dict_table_t* table, /* in: the index is on this table */
|
|
|
|
const merge_index_def_t* /* in: the index definition */
|
|
|
|
index_def)
|
|
|
|
{
|
|
|
|
ulint err = DB_SUCCESS;
|
|
|
|
ulint n_fields = index_def->n_fields;
|
|
|
|
|
|
|
|
/* Create the index prototype, using the passed in def, this is not
|
|
|
|
a persistent operation. We pass 0 as the space id, and determine at
|
|
|
|
a lower level the space id where to store the table.*/
|
|
|
|
|
|
|
|
*index = dict_mem_index_create(
|
|
|
|
table->name, index_def->name, 0, index_def->ind_type, n_fields);
|
|
|
|
|
|
|
|
ut_a(*index);
|
|
|
|
|
|
|
|
/* Create the index id, as it will be required when we build
|
|
|
|
the index. We assign the id here because we want to write an
|
|
|
|
UNDO record before we insert the entry into SYS_INDEXES.*/
|
|
|
|
ut_a(ut_dulint_is_zero((*index)->id));
|
|
|
|
|
|
|
|
(*index)->id = dict_hdr_get_new_id(DICT_HDR_INDEX_ID);
|
|
|
|
(*index)->table = table;
|
|
|
|
|
|
|
|
/* Write the UNDO record for the create index */
|
|
|
|
err = row_undo_report_create_index_dict_operation(trx, *index);
|
|
|
|
|
|
|
|
if (err == DB_SUCCESS) {
|
|
|
|
ulint i;
|
|
|
|
|
|
|
|
/* Make sure the UNDO record gets to disk */
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
|
|
|
|
for (i = 0; i < n_fields; i++) {
|
|
|
|
merge_index_field_t* ifield;
|
|
|
|
|
2007-04-11 14:17:55 +02:00
|
|
|
ifield = &index_def->fields[i];
|
2007-04-04 13:05:33 +02:00
|
|
|
|
2007-04-11 14:17:55 +02:00
|
|
|
dict_mem_index_add_field(*index,
|
|
|
|
ifield->field_name,
|
|
|
|
ifield->prefix_len);
|
2007-04-04 13:05:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Add the index to SYS_INDEXES, this will use the prototype
|
|
|
|
to create an entry in SYS_INDEXES.*/
|
|
|
|
err = row_create_index_graph_for_mysql(trx, table, *index);
|
|
|
|
|
|
|
|
if (err == DB_SUCCESS) {
|
|
|
|
|
|
|
|
*index = row_merge_dict_table_get_index(
|
|
|
|
table, index_def);
|
|
|
|
|
|
|
|
ut_a(*index);
|
|
|
|
|
|
|
|
/* Note the id of the transaction that created this
|
|
|
|
index, we use it to restrict readers from accessing
|
|
|
|
this index, to ensure read consistency.*/
|
|
|
|
(*index)->trx_id = trx->id;
|
|
|
|
|
|
|
|
/* Create element and append to list in trx. So that
|
|
|
|
we can rename from temp name to real name.*/
|
|
|
|
if (trx->dict_redo_list) {
|
|
|
|
dict_redo_t* dict_redo;
|
|
|
|
|
|
|
|
dict_redo = dict_redo_create_element(trx);
|
|
|
|
dict_redo->index = *index;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
Check if a transaction can use an index.*/
|
|
|
|
|
|
|
|
ibool
|
|
|
|
row_merge_is_index_usable(
|
|
|
|
/*======================*/
|
2007-04-11 10:16:14 +02:00
|
|
|
const trx_t* trx, /* in: transaction */
|
|
|
|
const dict_index_t* index) /* in: index to check */
|
2007-04-04 13:05:33 +02:00
|
|
|
{
|
|
|
|
if (!trx->read_view) {
|
|
|
|
return(TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(ut_dulint_cmp(index->trx_id, trx->read_view->low_limit_id) < 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
Drop the old table.*/
|
|
|
|
|
|
|
|
ulint
|
|
|
|
row_merge_drop_table(
|
|
|
|
/*=================*/
|
|
|
|
/* out: DB_SUCCESS if all OK else
|
|
|
|
error code.*/
|
|
|
|
trx_t* trx, /* in: transaction */
|
|
|
|
dict_table_t* table) /* in: table to drop */
|
|
|
|
{
|
|
|
|
ulint err = DB_SUCCESS;
|
|
|
|
ibool dict_locked = FALSE;
|
|
|
|
|
|
|
|
if (trx->dict_operation_lock_mode == 0) {
|
|
|
|
row_mysql_lock_data_dictionary(trx);
|
|
|
|
dict_locked = TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
ut_a(table->to_be_dropped);
|
|
|
|
ut_a(*table->name == TEMP_TABLE_PREFIX);
|
|
|
|
|
|
|
|
/* Drop the table immediately iff it is not references by MySQL */
|
|
|
|
if (table->n_mysql_handles_opened == 0) {
|
|
|
|
/* Set the commit flag to FALSE.*/
|
|
|
|
err = row_drop_table_for_mysql(table->name, trx, FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dict_locked) {
|
|
|
|
row_mysql_unlock_data_dictionary(trx);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|