mariadb/storage/maria/ma_write.c
Monty b9f5793176 MDEV-9101 Limit size of created disk temporary files and tables
Two new variables added:
- max_tmp_space_usage : Limits the the temporary space allowance per user
- max_total_tmp_space_usage: Limits the temporary space allowance for
  all users.

New status variables: tmp_space_used & max_tmp_space_used
New field in information_schema.process_list: TMP_SPACE_USED

The temporary space is counted for:
- All SQL level temporary files. This includes files for filesort,
  transaction temporary space, analyze, binlog_stmt_cache etc.
  It does not include engine internal temporary files used for repair,
  alter table, index pre sorting etc.
- All internal on disk temporary tables created as part of resolving a
  SELECT, multi-source update etc.

Special cases:
- When doing a commit, the last flush of the binlog_stmt_cache
  will not cause an error even if the temporary space limit is exceeded.
  This is to avoid giving errors on commit. This means that a user
  can temporary go over the limit with up to binlog_stmt_cache_size.

Noteworthy issue:
- One has to be careful when using small values for max_tmp_space_limit
  together with binary logging and with non transactional tables.
  If a the binary log entry for the query is bigger than
  binlog_stmt_cache_size and one hits the limit of max_tmp_space_limit
  when flushing the entry to disk, the query will abort and the
  binary log will not contain the last changes to the table.
  This will also stop the slave!
  This is also true for all Aria tables as Aria cannot do rollback
  (except in case of crashes)!
  One way to avoid it is to use @@binlog_format=statement for
  queries that updates a lot of rows.

Implementation:
- All writes to temporary files or internal temporary tables, that
  increases the file size, are routed through temp_file_size_cb_func()
  which updates and checks the temp space usage.
- Most of the temporary file monitoring is done inside IO_CACHE.
  Temporary file monitoring is done inside the Aria engine.
- MY_TRACK and MY_TRACK_WITH_LIMIT are new flags for ini_io_cache().
  MY_TRACK means that we track the file usage. TRACK_WITH_LIMIT means
  that we track the file usage and we give an error if the limit is
  breached. This is used to not give an error on commit when
  binlog_stmp_cache is flushed.
- global_tmp_space_used contains the total tmp space used so far.
  This is needed quickly check against max_total_tmp_space_usage.
- Temporary space errors are using EE_LOCAL_TMP_SPACE_FULL and
  handler errors are using HA_ERR_LOCAL_TMP_SPACE_FULL.
  This is needed until we move general errors to it's own error space
  so that they cannot conflict with system error numbers.
- Return value of my_chsize() and mysql_file_chsize() has changed
  so that -1 is returned in the case my_chsize() could not decrease
  the file size (very unlikely and will not happen on modern systems).
  All calls to _chsize() are updated to check for > 0 as the error
  condition.
- At the destruction of THD we check that THD::tmp_file_space == 0
- At server end we check that global_tmp_space_used == 0
- As a precaution against errors in the tmp_space_used code, one can set
  max_tmp_space_usage and max_total_tmp_space_usage to 0 to disable
  the tmp space quota errors.
- truncate_io_cache() function added.
- Aria tables using static or dynamic row length are registered in 8K
  increments to avoid some calls to update_tmp_file_size().

Other things:
- Ensure that all handler errors are registered.  Before, some engine
  errors could be printed as "Unknown error".
- Fixed bug in filesort() that causes a assert if there was an error
  when writing to the temporay file.
- Fixed that compute_window_func() now takes into account write errors.
- In case of parallel replication, rpl_group_info::cleanup_context()
  could call trans_rollback() with thd->error set, which would cause
  an assert. Fixed by resetting the error before calling trans_rollback().
- Fixed bug in subselect3.inc which caused following test to use
  heap tables with low value for max_heap_table_size
- Fixed bug in sql_expression_cache where it did not overflow
  heap table to Aria table.
- Added Max_tmp_disk_space_used to slow query log.
- Fixed some bugs in log_slow_innodb.test
2024-05-27 12:39:04 +02:00

2510 lines
83 KiB
C

/* Copyright (C) 2004-2008 MySQL AB & MySQL Finland AB & TCX DataKonsult AB
Copyright (C) 2008-2009 Sun Microsystems, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
/* Write a row to a MARIA table */
#include "ma_fulltext.h"
#include "ma_rt_index.h"
#include "trnman.h"
#include "ma_key_recover.h"
#include "ma_blockrec.h"
#include "mysys_err.h"
/* Functions declared in this file */
static int w_search(MARIA_HA *info, uint32 comp_flag,
MARIA_KEY *key, my_off_t page,
MARIA_PAGE *father_page, uchar *father_keypos,
my_bool insert_last);
static int _ma_balance_page(MARIA_HA *info, MARIA_KEYDEF *keyinfo,
MARIA_KEY *key, MARIA_PAGE *curr_page,
MARIA_PAGE *father_page,
uchar *father_key_pos, MARIA_KEY_PARAM *s_temp);
static uchar *_ma_find_last_pos(MARIA_KEY *int_key,
MARIA_PAGE *page, uchar **after_key);
static my_bool _ma_ck_write_tree(register MARIA_HA *info, MARIA_KEY *key);
static my_bool _ma_ck_write_btree(register MARIA_HA *info, MARIA_KEY *key);
static my_bool _ma_ck_write_btree_with_log(MARIA_HA *, MARIA_KEY *, my_off_t *,
uint32);
static my_bool _ma_log_split(MARIA_PAGE *page, uint org_length,
uint new_length,
const uchar *key_pos,
uint key_length, int move_length,
enum en_key_op prefix_or_suffix,
const uchar *data, uint data_length,
uint changed_length);
static my_bool _ma_log_del_prefix(MARIA_PAGE *page,
uint org_length, uint new_length,
const uchar *key_pos, uint key_length,
int move_length);
static my_bool _ma_log_key_middle(MARIA_PAGE *page,
uint new_length,
uint data_added_first,
uint data_changed_first,
uint data_deleted_last,
const uchar *key_pos,
uint key_length, int move_length);
/*
@brief Default handler for returing position to new row
@note
This is only called for non transactional tables and not for block format
which is why we use info->state here.
*/
MARIA_RECORD_POS _ma_write_init_default(MARIA_HA *info,
const uchar *record
__attribute__((unused)))
{
return ((info->s->state.dellink != HA_OFFSET_ERROR &&
!info->append_insert_at_end) ?
info->s->state.dellink :
info->state->data_file_length);
}
my_bool _ma_write_abort_default(MARIA_HA *info __attribute__((unused)))
{
return 0;
}
/* Write new record to a table */
int maria_write(MARIA_HA *info, const uchar *record)
{
MARIA_SHARE *share= info->s;
uint i;
int save_errno;
MARIA_RECORD_POS filepos, oldpos= info->cur_row.lastpos;
uchar *buff;
my_bool lock_tree= share->lock_key_trees;
my_bool fatal_error;
MARIA_KEYDEF *keyinfo;
DBUG_ENTER("maria_write");
DBUG_PRINT("enter",("index_file: %d data_file: %d",
share->kfile.file, info->dfile.file));
DBUG_EXECUTE_IF("maria_pretend_crashed_table_on_usage",
_ma_print_error(info, HA_ERR_CRASHED, 0);
DBUG_RETURN(my_errno= HA_ERR_CRASHED););
if (share->options & HA_OPTION_READ_ONLY_DATA)
{
DBUG_RETURN(my_errno=EACCES);
}
if (_ma_readinfo(info,F_WRLCK,1))
DBUG_RETURN(my_errno);
if ((share->state.changed & STATE_DATA_FILE_FULL) ||
(share->base.reloc == (ha_rows) 1 &&
share->base.records == (ha_rows) 1 &&
share->state.state.records == (ha_rows) 1))
{ /* System file */
my_errno=HA_ERR_RECORD_FILE_FULL;
goto err2;
}
if (share->state.state.key_file_length >= share->base.margin_key_file_length)
{
my_errno=HA_ERR_INDEX_FILE_FULL;
goto err2;
}
if (_ma_mark_file_changed(share))
goto err2;
/* Calculate and check all unique constraints */
if (share->state.header.uniques)
{
for (i=0 ; i < share->state.header.uniques ; i++)
{
MARIA_UNIQUEDEF *def= share->uniqueinfo + i;
ha_checksum unique_hash= _ma_unique_hash(share->uniqueinfo+i,record);
if (maria_is_key_active(share->state.key_map, def->key))
{
if (_ma_check_unique(info, def, record,
unique_hash, HA_OFFSET_ERROR))
goto err2;
}
else
maria_unique_store(record+ share->keyinfo[def->key].seg->start,
unique_hash);
}
}
/* Ensure we don't try to restore auto_increment if it doesn't change */
info->last_auto_increment= ~(ulonglong) 0;
if ((info->opt_flag & OPT_NO_ROWS))
filepos= HA_OFFSET_ERROR;
else
{
/*
This may either calculate a record or, or write the record and return
the record id
*/
if ((filepos= (*share->write_record_init)(info, record)) ==
HA_OFFSET_ERROR)
goto err2;
}
/* Write all keys to indextree */
buff= info->lastkey_buff2;
for (i=0, keyinfo= share->keyinfo ; i < share->base.keys ; i++, keyinfo++)
{
MARIA_KEY int_key;
if (maria_is_key_active(share->state.key_map, i))
{
my_bool local_lock_tree= (lock_tree &&
!(info->bulk_insert &&
is_tree_inited(&info->bulk_insert[i])));
if (local_lock_tree)
{
mysql_rwlock_wrlock(&keyinfo->root_lock);
keyinfo->version++;
}
if (keyinfo->flag & HA_FULLTEXT )
{
if (_ma_ft_add(info,i, buff,record,filepos))
{
if (local_lock_tree)
mysql_rwlock_unlock(&keyinfo->root_lock);
DBUG_PRINT("error",("Got error: %d on write",my_errno));
goto err;
}
}
else
{
while (keyinfo->ck_insert(info,
(*keyinfo->make_key)(info, &int_key, i,
buff, record, filepos,
info->trn->trid)))
{
TRN *blocker;
DBUG_PRINT("error",("Got error: %d on write",my_errno));
/*
explicit check to filter out temp tables, they aren't
transactional and don't have a proper TRN so the code
below doesn't work for them.
Also, filter out non-thread maria use, and table modified in
the same transaction.
At last, filter out non-dup-unique errors.
*/
if (!local_lock_tree)
goto err;
if (info->dup_key_trid == info->trn->trid ||
my_errno != HA_ERR_FOUND_DUPP_KEY)
{
mysql_rwlock_unlock(&keyinfo->root_lock);
goto err;
}
/* Different TrIDs: table must be transactional */
DBUG_ASSERT(share->base.born_transactional);
/*
If transactions are disabled, and dup_key_trid is different from
our TrID, it must be ALTER TABLE with dup_key_trid==0 (no
transaction). ALTER TABLE does have MARIA_HA::TRN not dummy but
puts TrID=0 in rows/keys.
*/
DBUG_ASSERT(share->now_transactional ||
(info->dup_key_trid == 0));
blocker= trnman_trid_to_trn(info->trn, info->dup_key_trid);
/*
if blocker TRN was not found, it means that the conflicting
transaction was committed long time ago. It could not be
aborted, as it would have to wait on the key tree lock
to remove the conflicting key it has inserted.
*/
if (!blocker || blocker->commit_trid != ~(TrID)0)
{ /* committed */
if (blocker)
mysql_mutex_unlock(& blocker->state_lock);
mysql_rwlock_unlock(&keyinfo->root_lock);
goto err;
}
mysql_rwlock_unlock(&keyinfo->root_lock);
{
/* running. now we wait */
WT_RESOURCE_ID rc;
int res;
PSI_stage_info old_stage_info;
rc.type= &ma_rc_dup_unique;
/* TODO savepoint id when we'll have them */
rc.value= (intptr)blocker;
res= wt_thd_will_wait_for(info->trn->wt, blocker->wt, & rc);
if (res != WT_OK)
{
mysql_mutex_unlock(& blocker->state_lock);
my_errno= HA_ERR_LOCK_DEADLOCK;
goto err;
}
proc_info_hook(0, &stage_waiting_for_a_resource, &old_stage_info,
__func__, __FILE__, __LINE__);
res= wt_thd_cond_timedwait(info->trn->wt, & blocker->state_lock);
proc_info_hook(0, &old_stage_info, 0, __func__, __FILE__, __LINE__);
mysql_mutex_unlock(& blocker->state_lock);
if (res != WT_OK)
{
my_errno= res == WT_TIMEOUT ? HA_ERR_LOCK_WAIT_TIMEOUT
: HA_ERR_LOCK_DEADLOCK;
goto err;
}
}
mysql_rwlock_wrlock(&keyinfo->root_lock);
#ifndef MARIA_CANNOT_ROLLBACK
keyinfo->version++;
#endif
}
}
/* The above changed info->lastkey2. Inform maria_rnext_same(). */
info->update&= ~HA_STATE_RNEXT_SAME;
if (local_lock_tree)
mysql_rwlock_unlock(&keyinfo->root_lock);
}
}
if (share->calc_write_checksum)
info->cur_row.checksum= (*share->calc_write_checksum)(info,record);
if (filepos != HA_OFFSET_ERROR)
{
if ((*share->write_record)(info,record))
goto err;
info->state->checksum+= info->cur_row.checksum;
}
if (!share->now_transactional)
{
if (share->base.auto_key != 0)
{
const HA_KEYSEG *keyseg= share->keyinfo[share->base.auto_key-1].seg;
const uchar *key= record + keyseg->start;
set_if_bigger(share->state.auto_increment,
ma_retrieve_auto_increment(key, keyseg->type));
}
}
info->state->records++;
info->update= (HA_STATE_CHANGED | HA_STATE_AKTIV | HA_STATE_WRITTEN |
HA_STATE_ROW_CHANGED);
info->row_changes++;
share->state.changed|= STATE_NOT_MOVABLE | STATE_NOT_ZEROFILLED;
info->state->changed= 1;
info->cur_row.lastpos= oldpos;
_ma_writeinfo(info, WRITEINFO_UPDATE_KEYFILE);
if (info->invalidator != 0)
{
DBUG_PRINT("info", ("invalidator... '%s' (update)",
share->open_file_name.str));
(*info->invalidator)(share->open_file_name.str);
info->invalidator=0;
}
/*
Update status of the table. We need to do so after each row write
for the log tables, as we want the new row to become visible to
other threads as soon as possible. We don't lock mutex here
(as it is required by pthread memory visibility rules) as (1) it's
not critical to use outdated share->is_log_table value (2) locking
mutex here for every write is too expensive.
*/
if (share->is_log_table)
_ma_update_status((void*) info);
DBUG_RETURN(0);
err:
save_errno= my_errno;
fatal_error= 0;
if (my_errno == HA_ERR_FOUND_DUPP_KEY ||
my_errno == HA_ERR_RECORD_FILE_FULL ||
my_errno == HA_ERR_LOCK_DEADLOCK ||
my_errno == HA_ERR_LOCK_WAIT_TIMEOUT ||
my_errno == HA_ERR_NULL_IN_SPATIAL ||
my_errno == HA_ERR_OUT_OF_MEM)
{
info->errkey= i < share->base.keys ? (int) i : -1;
/*
We delete keys in the reverse order of insertion. This is the order that
a rollback would do and is important for CLR_ENDs generated by
_ma_ft|ck_delete() and write_record_abort() to work (with any other
order they would cause wrong jumps in the chain).
*/
while ( i-- > 0)
{
if (maria_is_key_active(share->state.key_map, i))
{
my_bool local_lock_tree= (lock_tree &&
!(info->bulk_insert &&
is_tree_inited(&info->bulk_insert[i])));
keyinfo= share->keyinfo + i;
if (local_lock_tree)
mysql_rwlock_wrlock(&keyinfo->root_lock);
/**
@todo RECOVERY BUG
The key deletes below should generate CLR_ENDs
*/
if (keyinfo->flag & HA_FULLTEXT)
{
if (_ma_ft_del(info,i,buff,record,filepos))
{
fatal_error= 1;
if (local_lock_tree)
mysql_rwlock_unlock(&keyinfo->root_lock);
break;
}
}
else
{
MARIA_KEY key;
if (keyinfo->ck_delete(info,
(*keyinfo->make_key)(info, &key, i, buff,
record,
filepos,
info->trn->trid)))
{
fatal_error= 1;
if (local_lock_tree)
mysql_rwlock_unlock(&keyinfo->root_lock);
break;
}
}
if (local_lock_tree)
mysql_rwlock_unlock(&keyinfo->root_lock);
}
}
}
else if (my_errno == HA_ERR_LOCAL_TMP_SPACE_FULL ||
my_errno == HA_ERR_GLOBAL_TMP_SPACE_FULL)
{
filepos= HA_OFFSET_ERROR; /* Avoid write_record_abort() */
}
else
fatal_error= 1;
if (filepos != HA_OFFSET_ERROR)
{
if ((*share->write_record_abort)(info))
fatal_error= 1;
}
if (info->bulk_insert)
{
uint j;
for (j=0 ; j < share->base.keys ; j++)
maria_flush_bulk_insert(info, j);
}
if (fatal_error)
_ma_set_fatal_error(info, HA_ERR_CRASHED);
info->update= (HA_STATE_CHANGED | HA_STATE_WRITTEN | HA_STATE_ROW_CHANGED);
my_errno=save_errno;
err2:
save_errno=my_errno;
DBUG_ASSERT(save_errno);
if (!save_errno)
save_errno= HA_ERR_INTERNAL_ERROR; /* Should never happen */
DBUG_PRINT("error", ("got error: %d", save_errno));
_ma_writeinfo(info,WRITEINFO_UPDATE_KEYFILE);
DBUG_RETURN(my_errno=save_errno);
} /* maria_write */
/*
Write one key to btree
TODO
Remove this function and have bulk insert change keyinfo->ck_insert
to point to the right function
*/
my_bool _ma_ck_write(MARIA_HA *info, MARIA_KEY *key)
{
my_bool tmp;
DBUG_ENTER("_ma_ck_write");
if (info->bulk_insert &&
is_tree_inited(&info->bulk_insert[key->keyinfo->key_nr]))
tmp= _ma_ck_write_tree(info, key);
else
tmp= _ma_ck_write_btree(info, key);
DBUG_RETURN(tmp);
} /* _ma_ck_write */
/**********************************************************************
Insert key into btree (normal case)
**********************************************************************/
static my_bool _ma_ck_write_btree(MARIA_HA *info, MARIA_KEY *key)
{
my_bool error;
MARIA_KEYDEF *keyinfo= key->keyinfo;
my_off_t *root= &info->s->state.key_root[keyinfo->key_nr];
DBUG_ENTER("_ma_ck_write_btree");
error= _ma_ck_write_btree_with_log(info, key, root,
keyinfo->write_comp_flag | key->flag);
if (info->ft1_to_ft2)
{
if (!error)
error= _ma_ft_convert_to_ft2(info, key);
delete_dynamic(info->ft1_to_ft2);
my_free(info->ft1_to_ft2);
info->ft1_to_ft2=0;
}
DBUG_RETURN(error);
} /* _ma_ck_write_btree */
/**
@brief Write a key to the b-tree
@retval 1 error
@retval 0 ok
*/
static my_bool _ma_ck_write_btree_with_log(MARIA_HA *info, MARIA_KEY *key,
my_off_t *root, uint32 comp_flag)
{
MARIA_SHARE *share= info->s;
LSN lsn= LSN_IMPOSSIBLE;
int error;
my_off_t new_root= *root;
uchar key_buff[MARIA_MAX_KEY_BUFF];
MARIA_KEY org_key; /* Set/used when now_transactional=TRUE */
my_bool transactional= share->now_transactional;
DBUG_ENTER("_ma_ck_write_btree_with_log");
LINT_INIT_STRUCT(org_key);
if (transactional)
{
/* Save original value as the key may change */
org_key= *key;
memcpy(key_buff, key->data, key->data_length + key->ref_length);
}
error= _ma_ck_real_write_btree(info, key, &new_root, comp_flag);
if (!error && transactional)
{
/* Log the original value */
*key= org_key;
key->data= key_buff;
error= _ma_write_undo_key_insert(info, key, root, new_root, &lsn);
}
else
{
*root= new_root;
_ma_fast_unlock_key_del(info);
}
_ma_unpin_all_pages_and_finalize_row(info, lsn);
DBUG_RETURN(error != 0);
} /* _ma_ck_write_btree_with_log */
/**
@brief Write a key to the b-tree
@retval 1 error
@retval 0 ok
*/
my_bool _ma_ck_real_write_btree(MARIA_HA *info, MARIA_KEY *key, my_off_t *root,
uint32 comp_flag)
{
int error;
DBUG_ENTER("_ma_ck_real_write_btree");
/* key_length parameter is used only if comp_flag is SEARCH_FIND */
if (*root == HA_OFFSET_ERROR ||
(error= w_search(info, comp_flag, key, *root, (MARIA_PAGE *) 0,
(uchar*) 0, 1)) > 0)
error= _ma_enlarge_root(info, key, root);
DBUG_RETURN(error != 0);
} /* _ma_ck_real_write_btree */
/**
@brief Make a new root with key as only pointer
@retval 1 error
@retval 0 ok
*/
my_bool _ma_enlarge_root(MARIA_HA *info, MARIA_KEY *key, my_off_t *root)
{
uint t_length, nod_flag;
MARIA_KEY_PARAM s_temp;
MARIA_SHARE *share= info->s;
MARIA_PINNED_PAGE tmp_page_link, *page_link= &tmp_page_link;
MARIA_KEYDEF *keyinfo= key->keyinfo;
MARIA_PAGE page;
my_bool res= 0;
DBUG_ENTER("_ma_enlarge_root");
page.info= info;
page.keyinfo= keyinfo;
page.buff= info->buff;
page.flag= 0;
nod_flag= (*root != HA_OFFSET_ERROR) ? share->base.key_reflength : 0;
/* Store pointer to prev page if nod */
_ma_kpointer(info, page.buff + share->keypage_header, *root);
t_length= (*keyinfo->pack_key)(key, nod_flag, (uchar*) 0,
(uchar*) 0, (uchar*) 0, &s_temp);
page.size= share->keypage_header + t_length + nod_flag;
bzero(page.buff, share->keypage_header);
_ma_store_keynr(share, page.buff, keyinfo->key_nr);
if (nod_flag)
page.flag|= KEYPAGE_FLAG_ISNOD;
if (key->flag & (SEARCH_USER_KEY_HAS_TRANSID | SEARCH_PAGE_KEY_HAS_TRANSID))
page.flag|= KEYPAGE_FLAG_HAS_TRANSID;
(*keyinfo->store_key)(keyinfo, page.buff + share->keypage_header +
nod_flag, &s_temp);
/* Mark that info->buff was used */
info->keyread_buff_used= info->page_changed= 1;
if ((page.pos= _ma_new(info, PAGECACHE_PRIORITY_HIGH, &page_link)) ==
HA_OFFSET_ERROR)
DBUG_RETURN(1);
*root= page.pos;
page_store_info(share, &page);
/*
Clear unitialized part of page to avoid valgrind/purify warnings
and to get a clean page that is easier to compress and compare with
pages generated with redo
*/
bzero(page.buff + page.size, share->block_size - page.size);
if (share->now_transactional && _ma_log_new(&page, 1))
res= 1;
if (_ma_write_keypage(&page, page_link->write_lock,
PAGECACHE_PRIORITY_HIGH))
res= 1;
DBUG_RETURN(res);
} /* _ma_enlarge_root */
/*
Search after a position for a key and store it there
TODO:
Change this to use pagecache directly instead of creating a copy
of the page. To do this, we must however change write-key-on-page
algorithm to not overwrite the buffer but instead store any overflow
key in a separate buffer.
@return
@retval -1 error
@retval 0 ok
@retval > 0 Key should be stored in higher tree
*/
static int w_search(register MARIA_HA *info, uint32 comp_flag, MARIA_KEY *key,
my_off_t page_pos,
MARIA_PAGE *father_page, uchar *father_keypos,
my_bool insert_last)
{
int error,flag;
uchar *temp_buff,*keypos,*keybuff;
my_bool was_last_key, buff_alloced;
my_off_t next_page, dup_key_pos;
MARIA_SHARE *share= info->s;
MARIA_KEYDEF *keyinfo= key->keyinfo;
MARIA_PAGE page;
DBUG_ENTER("w_search");
DBUG_PRINT("enter", ("page: %lu", (ulong) (page_pos/keyinfo->block_length)));
alloc_on_stack(*info->stack_end_ptr, temp_buff, buff_alloced,
(keyinfo->block_length + keyinfo->max_store_length*3));
if (!temp_buff)
DBUG_RETURN(1);
keybuff= temp_buff + (keyinfo->block_length + keyinfo->max_store_length*2);
if (_ma_fetch_keypage(&page, info, keyinfo, page_pos, PAGECACHE_LOCK_WRITE,
DFLT_INIT_HITS, temp_buff, 0))
goto err;
flag= (*keyinfo->bin_search)(key, &page, comp_flag, &keypos,
keybuff, &was_last_key);
if (flag == 0)
{
MARIA_KEY tmp_key;
/* get position to record with duplicated key */
tmp_key.keyinfo= keyinfo;
tmp_key.data= keybuff;
if ((*keyinfo->get_key)(&tmp_key, page.flag, page.node, &keypos))
dup_key_pos= _ma_row_pos_from_key(&tmp_key);
else
dup_key_pos= HA_OFFSET_ERROR;
if (keyinfo->flag & HA_FULLTEXT)
{
uint off;
int subkeys;
get_key_full_length_rdonly(off, keybuff);
subkeys=ft_sintXkorr(keybuff+off);
comp_flag=SEARCH_SAME;
if (subkeys >= 0)
{
/* normal word, one-level tree structure */
flag=(*keyinfo->bin_search)(key, &page, comp_flag,
&keypos, keybuff, &was_last_key);
}
else
{
/* popular word. two-level tree. going down */
my_off_t root= dup_key_pos;
MARIA_KEY subkey;
get_key_full_length_rdonly(off, key->data);
subkey.keyinfo= keyinfo= &share->ft2_keyinfo;
subkey.data= key->data + off;
subkey.data_length= key->data_length - off;
subkey.ref_length= key->ref_length;
subkey.flag= key->flag;
/* we'll modify key entry 'in vivo' */
keypos-= keyinfo->keylength + page.node;
error= _ma_ck_real_write_btree(info, &subkey, &root, comp_flag);
_ma_dpointer(share, keypos+HA_FT_WLEN, root);
subkeys--; /* should there be underflow protection ? */
DBUG_ASSERT(subkeys < 0);
ft_intXstore(keypos, subkeys);
if (!error)
{
page_mark_changed(info, &page);
if (_ma_write_keypage(&page, PAGECACHE_LOCK_LEFT_WRITELOCKED,
DFLT_INIT_HITS))
goto err;
}
stack_alloc_free(temp_buff, buff_alloced);
DBUG_RETURN(error);
}
}
else /* not HA_FULLTEXT, normal HA_NOSAME key */
{
/*
TODO
When the index will support true versioning - with multiple
identical values in the UNIQUE index, invisible to each other -
the following should be changed to "continue inserting keys, at the
end (of the row or statement) wait". We need to wait on *all*
unique conflicts at once, not one-at-a-time, because we need to
know all blockers in advance, otherwise we'll have incomplete wait-for
graph.
*/
/*
transaction that has inserted the conflicting key may be in progress.
the caller will wait for it to be committed or aborted.
*/
info->dup_key_trid= _ma_trid_from_key(&tmp_key);
info->dup_key_pos= dup_key_pos;
my_errno= HA_ERR_FOUND_DUPP_KEY;
DBUG_PRINT("warning",
("Duplicate key. dup_key_trid: %lu pos %lu visible: %d",
(ulong) info->dup_key_trid,
(ulong) info->dup_key_pos,
info->trn ? trnman_can_read_from(info->trn,
info->dup_key_trid) : 2));
goto err;
}
}
if (flag == MARIA_FOUND_WRONG_KEY)
{
my_errno= HA_ERR_CRASHED;
goto err;
}
if (!was_last_key)
insert_last=0;
next_page= _ma_kpos(page.node, keypos);
if (next_page == HA_OFFSET_ERROR ||
(error= w_search(info, comp_flag, key, next_page,
&page, keypos, insert_last)) > 0)
{
error= _ma_insert(info, key, &page, keypos, keybuff,
father_page, father_keypos, insert_last);
if (error < 0)
goto err;
page_mark_changed(info, &page);
if (_ma_write_keypage(&page, PAGECACHE_LOCK_LEFT_WRITELOCKED,
DFLT_INIT_HITS))
goto err;
}
stack_alloc_free(temp_buff, buff_alloced);
DBUG_RETURN(error);
err:
stack_alloc_free(temp_buff, buff_alloced);
DBUG_PRINT("exit",("Error: %d",my_errno));
DBUG_RETURN(-1);
} /* w_search */
/*
Insert new key.
SYNOPSIS
_ma_insert()
info Open table information.
keyinfo Key definition information.
key New key
anc_page Key page (beginning)
key_pos Position in key page where to insert.
key_buff Copy of previous key if keys where packed.
father_page position of parent key page in file.
father_key_pos position in parent key page for balancing.
insert_last If to append at end of page.
DESCRIPTION
Insert new key at right of key_pos.
Note that caller must save anc_buff
This function writes log records for all changed pages
(Including anc_buff and father page)
RETURN
< 0 Error.
0 OK
1 If key contains key to upper level (from balance page)
2 If key contains key to upper level (from split space)
*/
int _ma_insert(register MARIA_HA *info, MARIA_KEY *key,
MARIA_PAGE *anc_page, uchar *key_pos, uchar *key_buff,
MARIA_PAGE *father_page, uchar *father_key_pos,
my_bool insert_last)
{
uint a_length, nod_flag, org_anc_length;
int t_length;
uchar *endpos, *prev_key, *anc_buff;
MARIA_KEY_PARAM s_temp;
MARIA_SHARE *share= info->s;
MARIA_KEYDEF *keyinfo= key->keyinfo;
DBUG_ENTER("_ma_insert");
DBUG_PRINT("enter",("key_pos:%p", key_pos));
DBUG_EXECUTE("key", _ma_print_key(DBUG_FILE, key););
/*
Note that anc_page->size can be bigger then block_size in case of
delete key that caused increase of page length
*/
org_anc_length= a_length= anc_page->size;
nod_flag= anc_page->node;
anc_buff= anc_page->buff;
endpos= anc_buff+ a_length;
prev_key= (key_pos == anc_buff + share->keypage_header + nod_flag ?
(uchar*) 0 : key_buff);
t_length= (*keyinfo->pack_key)(key, nod_flag,
(key_pos == endpos ? (uchar*) 0 : key_pos),
prev_key, prev_key, &s_temp);
#ifndef DBUG_OFF
if (prev_key && (keyinfo->flag & (HA_BINARY_PACK_KEY | HA_PACK_KEY)))
{
DBUG_DUMP("prev_key", prev_key, _ma_keylength(keyinfo,prev_key));
}
if (keyinfo->flag & HA_PACK_KEY)
{
DBUG_PRINT("test",("t_length: %d ref_len: %d",
t_length,s_temp.ref_length));
DBUG_PRINT("test",("n_ref_len: %d n_length: %d key_pos: %p",
s_temp.n_ref_length, s_temp.n_length, s_temp.key));
}
#endif
if (t_length > 0)
{
if (t_length >= keyinfo->maxlength*2+MARIA_INDEX_OVERHEAD_SIZE)
{
_ma_set_fatal_error(info, HA_ERR_CRASHED);
DBUG_RETURN(-1);
}
bmove_upp(endpos+t_length, endpos, (uint) (endpos-key_pos));
}
else
{
if (-t_length >= keyinfo->maxlength*2+MARIA_INDEX_OVERHEAD_SIZE)
{
_ma_set_fatal_error(info, HA_ERR_CRASHED);
DBUG_RETURN(-1);
}
bmove(key_pos,key_pos-t_length,(uint) (endpos-key_pos)+t_length);
}
(*keyinfo->store_key)(keyinfo,key_pos,&s_temp);
a_length+=t_length;
if (key->flag & (SEARCH_USER_KEY_HAS_TRANSID | SEARCH_PAGE_KEY_HAS_TRANSID))
_ma_mark_page_with_transid(share, anc_page);
anc_page->size= a_length;
page_store_size(share, anc_page);
/*
Check if the new key fits totally into the the page
(anc_buff is big enough to contain a full page + one key)
*/
if (a_length <= share->max_index_block_size)
{
if (share->max_index_block_size - a_length < 32 &&
(keyinfo->flag & HA_FULLTEXT) && key_pos == endpos &&
share->base.key_reflength <= share->rec_reflength &&
share->options & (HA_OPTION_PACK_RECORD | HA_OPTION_COMPRESS_RECORD))
{
/*
Normal word. One-level tree. Page is almost full.
Let's consider converting.
We'll compare 'key' and the first key at anc_buff
*/
const uchar *a= key->data;
const uchar *b= anc_buff + share->keypage_header + nod_flag;
uint alen, blen, ft2len= share->ft2_keyinfo.keylength;
/* the very first key on the page is always unpacked */
DBUG_ASSERT((*b & 128) == 0);
#if HA_FT_MAXLEN >= 127
blen= mi_uint2korr(b); b+=2;
When you enable this code, as part of the MyISAM->Maria merge of
ChangeSet@1.2562, 2008-04-09 07:41:40+02:00, serg@janus.mylan +9 -0
restore ft2 functionality, fix bugs.
Then this will enable two-level fulltext index, which is not totally
recoverable yet.
So remove this text and inform Guilhem so that he fixes the issue.
#else
blen= *b++;
#endif
get_key_length(alen,a);
DBUG_ASSERT(info->ft1_to_ft2==0);
if (alen == blen &&
ha_compare_char_varying(keyinfo->seg->charset,
a, alen,
b, blen,
FALSE/*b_is_prefix*/) == 0)
{
/* Yup. converting */
info->ft1_to_ft2=(DYNAMIC_ARRAY *)
my_malloc(PSI_INSTRUMENT_ME, sizeof(DYNAMIC_ARRAY), MYF(MY_WME));
my_init_dynamic_array(PSI_INSTRUMENT_ME, info->ft1_to_ft2, ft2len, 300,
50, MYF(0));
/*
Now, adding all keys from the page to dynarray
if the page is a leaf (if not keys will be deleted later)
*/
if (!nod_flag)
{
/*
Let's leave the first key on the page, though, because
we cannot easily dispatch an empty page here
*/
b+=blen+ft2len+2;
for (a=anc_buff+a_length ; b < a ; b+=ft2len+2)
insert_dynamic(info->ft1_to_ft2, b);
/* fixing the page's length - it contains only one key now */
anc_page->size= share->keypage_header + blen + ft2len + 2;
page_store_size(share, anc_page);
}
/* the rest will be done when we're back from recursion */
}
}
else
{
if (share->now_transactional &&
_ma_log_add(anc_page, org_anc_length,
key_pos, s_temp.changed_length, t_length, 1,
KEY_OP_DEBUG_LOG_ADD_1))
DBUG_RETURN(-1);
}
DBUG_RETURN(0); /* There is room on page */
}
/* Page is full */
if (nod_flag)
insert_last=0;
/*
TODO:
Remove 'born_transactional' here.
The only reason for having it here is that the current
_ma_balance_page_ can't handle variable length keys.
*/
if (!(keyinfo->flag & (HA_VAR_LENGTH_KEY | HA_BINARY_PACK_KEY)) &&
father_page && !insert_last && !info->quick_mode &&
!info->s->base.born_transactional)
{
s_temp.key_pos= key_pos;
page_mark_changed(info, father_page);
DBUG_RETURN(_ma_balance_page(info, keyinfo, key, anc_page,
father_page, father_key_pos,
&s_temp));
}
DBUG_RETURN(_ma_split_page(info, key, anc_page,
MY_MIN(org_anc_length,
info->s->max_index_block_size),
key_pos, s_temp.changed_length, t_length,
key_buff, insert_last));
} /* _ma_insert */
/**
@brief split a full page in two and assign emerging item to key
@fn _ma_split_page()
info Maria handler
keyinfo Key handler
key Buffer for middle key
split_page Page that should be split
org_split_length Original length of split_page before key was inserted
inserted_key_pos Address in buffer where key was inserted
changed_length Number of bytes changed at 'inserted_key_pos'
move_length Number of bytes buffer was moved when key was inserted
key_buff Key buffer to use for temporary storage of key
insert_last_key If we are insert key on rightmost key page
@note
split_buff is not stored on disk (caller has to do this)
@return
@retval 2 ok (Middle key up from _ma_insert())
@retval -1 error
*/
int _ma_split_page(MARIA_HA *info, MARIA_KEY *key, MARIA_PAGE *split_page,
uint org_split_length,
uchar *inserted_key_pos, uint changed_length,
int move_length,
uchar *key_buff, my_bool insert_last_key)
{
uint keynr;
uint length,a_length,key_ref_length,t_length,nod_flag,key_length;
uint page_length, split_length, page_flag;
uchar *key_pos, *pos, *UNINIT_VAR(after_key);
MARIA_KEY_PARAM s_temp;
MARIA_PINNED_PAGE tmp_page_link, *page_link= &tmp_page_link;
MARIA_SHARE *share= info->s;
MARIA_KEYDEF *keyinfo= key->keyinfo;
MARIA_KEY tmp_key;
MARIA_PAGE new_page;
int res;
DBUG_ENTER("_ma_split_page");
DBUG_DUMP("buff", split_page->buff, split_page->size);
info->page_changed=1; /* Info->buff is used */
info->keyread_buff_used=1;
page_flag= split_page->flag;
nod_flag= split_page->node;
key_ref_length= share->keypage_header + nod_flag;
new_page.info= info;
new_page.buff= info->buff;
new_page.keyinfo= keyinfo;
tmp_key.data= key_buff;
tmp_key.keyinfo= keyinfo;
if (insert_last_key)
key_pos= _ma_find_last_pos(&tmp_key, split_page, &after_key);
else
key_pos= _ma_find_half_pos(&tmp_key, split_page, &after_key);
if (!key_pos)
DBUG_RETURN(-1);
key_length= tmp_key.data_length + tmp_key.ref_length;
split_length= (uint) (key_pos - split_page->buff);
a_length= split_page->size;
split_page->size= split_length;
page_store_size(share, split_page);
key_pos=after_key;
if (nod_flag)
{
DBUG_PRINT("test",("Splitting nod"));
pos=key_pos-nod_flag;
memcpy(new_page.buff + share->keypage_header, pos, (size_t) nod_flag);
}
/* Move middle item to key and pointer to new page */
if ((new_page.pos= _ma_new(info, PAGECACHE_PRIORITY_HIGH, &page_link)) ==
HA_OFFSET_ERROR)
DBUG_RETURN(-1);
_ma_copy_key(key, &tmp_key);
_ma_kpointer(info, key->data + key_length, new_page.pos);
/* Store new page */
if (!(*keyinfo->get_key)(&tmp_key, page_flag, nod_flag, &key_pos))
DBUG_RETURN(-1);
t_length=(*keyinfo->pack_key)(&tmp_key, nod_flag, (uchar *) 0,
(uchar*) 0, (uchar*) 0, &s_temp);
length=(uint) ((split_page->buff + a_length) - key_pos);
memcpy(new_page.buff + key_ref_length + t_length, key_pos,
(size_t) length);
(*keyinfo->store_key)(keyinfo,new_page.buff+key_ref_length,&s_temp);
page_length= length + t_length + key_ref_length;
bzero(new_page.buff, share->keypage_header);
/* Copy KEYFLAG_FLAG_ISNODE and KEYPAGE_FLAG_HAS_TRANSID from parent page */
new_page.flag= page_flag;
new_page.size= page_length;
page_store_info(share, &new_page);
/* Copy key number */
keynr= _ma_get_keynr(share, split_page->buff);
_ma_store_keynr(share, new_page.buff, keynr);
res= 2; /* Middle key up */
if (share->now_transactional && _ma_log_new(&new_page, 0))
res= -1;
/*
Clear unitialized part of page to avoid valgrind/purify warnings
and to get a clean page that is easier to compress and compare with
pages generated with redo
*/
bzero(new_page.buff + page_length, share->block_size - page_length);
if (_ma_write_keypage(&new_page, page_link->write_lock,
DFLT_INIT_HITS))
res= -1;
/* Save changes to split pages */
if (share->now_transactional &&
_ma_log_split(split_page, org_split_length, split_length,
inserted_key_pos, changed_length, move_length,
KEY_OP_NONE, (uchar*) 0, 0, 0))
res= -1;
DBUG_DUMP_KEY("middle_key", key);
DBUG_RETURN(res);
} /* _ma_split_page */
/*
Calculate how to much to move to split a page in two
Returns pointer to start of key.
key will contain the key.
after_key will contain the position to where the next key starts
*/
uchar *_ma_find_half_pos(MARIA_KEY *key, MARIA_PAGE *ma_page,
uchar **after_key)
{
uint keys, length, key_ref_length, page_flag, nod_flag;
uchar *page, *end, *lastpos;
MARIA_HA *info= ma_page->info;
MARIA_SHARE *share= info->s;
MARIA_KEYDEF *keyinfo= key->keyinfo;
DBUG_ENTER("_ma_find_half_pos");
nod_flag= ma_page->node;
key_ref_length= share->keypage_header + nod_flag;
page_flag= ma_page->flag;
length= ma_page->size - key_ref_length;
page= ma_page->buff+ key_ref_length; /* Point to first key */
if (!(keyinfo->flag &
(HA_PACK_KEY | HA_SPACE_PACK_USED | HA_VAR_LENGTH_KEY |
HA_BINARY_PACK_KEY)) && !(page_flag & KEYPAGE_FLAG_HAS_TRANSID))
{
key_ref_length= keyinfo->keylength+nod_flag;
key->data_length= keyinfo->keylength - info->s->rec_reflength;
key->ref_length= info->s->rec_reflength;
key->flag= 0;
keys=length/(key_ref_length*2);
end=page+keys*key_ref_length;
*after_key=end+key_ref_length;
memcpy(key->data, end, key_ref_length);
DBUG_RETURN(end);
}
end=page+length/2-key_ref_length; /* This is aprox. half */
key->data[0]= 0; /* Safety */
do
{
lastpos=page;
if (!(length= (*keyinfo->get_key)(key, page_flag, nod_flag, &page)))
DBUG_RETURN(0);
} while (page < end);
*after_key= page;
DBUG_PRINT("exit",("returns: %p page: %p half: %p",
lastpos, page, end));
DBUG_RETURN(lastpos);
} /* _ma_find_half_pos */
/**
Find second to last key on leaf page
@notes
Used to split buffer at last key. In this case the next to last
key will be moved to parent page and last key will be on it's own page.
@TODO
Add one argument for 'last key value' to get_key so that one can
do the loop without having to copy the found key the whole time
@return
@retval Pointer to the start of the key before the last key
@retval int_key will contain the last key
*/
static uchar *_ma_find_last_pos(MARIA_KEY *int_key, MARIA_PAGE *ma_page,
uchar **after_key)
{
uint keys, length, key_ref_length, page_flag;
uchar *page, *end, *lastpos, *prevpos;
uchar key_buff[MARIA_MAX_KEY_BUFF];
MARIA_HA *info= ma_page->info;
MARIA_SHARE *share= info->s;
MARIA_KEYDEF *keyinfo= int_key->keyinfo;
MARIA_KEY tmp_key;
DBUG_ENTER("_ma_find_last_pos");
key_ref_length= share->keypage_header;
page_flag= ma_page->flag;
length= ma_page->size - key_ref_length;
page= ma_page->buff + key_ref_length;
if (!(keyinfo->flag &
(HA_PACK_KEY | HA_SPACE_PACK_USED | HA_VAR_LENGTH_KEY |
HA_BINARY_PACK_KEY)) && !(page_flag & KEYPAGE_FLAG_HAS_TRANSID))
{
keys= length / keyinfo->keylength - 2;
length= keyinfo->keylength;
int_key->data_length= length - info->s->rec_reflength;
int_key->ref_length= info->s->rec_reflength;
int_key->flag= 0;
end=page+keys*length;
*after_key=end+length;
memcpy(int_key->data, end, length);
DBUG_RETURN(end);
}
end=page+length-key_ref_length;
lastpos=page;
tmp_key.data= key_buff;
tmp_key.keyinfo= int_key->keyinfo;
key_buff[0]= 0; /* Safety */
/* We know that there are at least 2 keys on the page */
if (!(length=(*keyinfo->get_key)(&tmp_key, page_flag, 0, &page)))
{
_ma_set_fatal_error(info, HA_ERR_CRASHED);
DBUG_RETURN(0);
}
do
{
prevpos=lastpos; lastpos=page;
int_key->data_length= tmp_key.data_length;
int_key->ref_length= tmp_key.ref_length;
int_key->flag= tmp_key.flag;
memcpy(int_key->data, key_buff, length); /* previous key */
if (!(length=(*keyinfo->get_key)(&tmp_key, page_flag, 0, &page)))
{
_ma_set_fatal_error(info, HA_ERR_CRASHED);
DBUG_RETURN(0);
}
} while (page < end);
*after_key=lastpos;
DBUG_PRINT("exit",("returns: %p page: %p end: %p",
prevpos,page,end));
DBUG_RETURN(prevpos);
} /* _ma_find_last_pos */
/**
@brief Balance page with static size keys with page on right/left
@param key Middle key will be stored here
@notes
Father_buff will always be changed
Caller must handle saving of curr_buff
@return
@retval 0 Balance was done (father buff is saved)
@retval 1 Middle key up (father buff is not saved)
@retval -1 Error
*/
static int _ma_balance_page(MARIA_HA *info, MARIA_KEYDEF *keyinfo,
MARIA_KEY *key, MARIA_PAGE *curr_page,
MARIA_PAGE *father_page,
uchar *father_key_pos, MARIA_KEY_PARAM *s_temp)
{
MARIA_PINNED_PAGE tmp_page_link, *new_page_link= &tmp_page_link;
MARIA_SHARE *share= info->s;
my_bool right, buff_alloced;
uint k_length,father_length,father_keylength,nod_flag,curr_keylength;
uint right_length,left_length,new_right_length,new_left_length,extra_length;
uint keys, tmp_length, extra_buff_length;
uchar *pos, *extra_buff, *parting_key;
uchar *tmp_part_key;
MARIA_PAGE next_page, extra_page, *left_page, *right_page;
DBUG_ENTER("_ma_balance_page");
alloc_on_stack(*info->stack_end_ptr, tmp_part_key, buff_alloced,
keyinfo->max_store_length);
if (!tmp_part_key)
DBUG_RETURN(-1);
k_length= keyinfo->keylength;
father_length= father_page->size;
father_keylength= k_length + share->base.key_reflength;
nod_flag= curr_page->node;
curr_keylength= k_length+nod_flag;
info->page_changed=1;
if ((father_key_pos != father_page->buff+father_length &&
(info->state->records & 1)) ||
father_key_pos == father_page->buff+ share->keypage_header +
share->base.key_reflength)
{
right=1;
next_page.pos= _ma_kpos(share->base.key_reflength,
father_key_pos+father_keylength);
left_page= curr_page;
right_page= &next_page;
DBUG_PRINT("info", ("use right page: %lu",
(ulong) (next_page.pos / keyinfo->block_length)));
}
else
{
right=0;
father_key_pos-=father_keylength;
next_page.pos= _ma_kpos(share->base.key_reflength,father_key_pos);
left_page= &next_page;
right_page= curr_page;
DBUG_PRINT("info", ("use left page: %lu",
(ulong) (next_page.pos / keyinfo->block_length)));
} /* father_key_pos ptr to parting key */
if (_ma_fetch_keypage(&next_page, info, keyinfo, next_page.pos,
PAGECACHE_LOCK_WRITE,
DFLT_INIT_HITS, info->buff, 0))
goto err;
page_mark_changed(info, &next_page);
DBUG_DUMP("next", next_page.buff, next_page.size);
/* Test if there is room to share keys */
left_length= left_page->size;
right_length= right_page->size;
keys= ((left_length+right_length-share->keypage_header*2-nod_flag*2)/
curr_keylength);
if ((right ? right_length : left_length) + curr_keylength <=
share->max_index_block_size)
{
/* Enough space to hold all keys in the two buffers ; Balance bufferts */
new_left_length= share->keypage_header+nod_flag+(keys/2)*curr_keylength;
new_right_length=share->keypage_header+nod_flag+(((keys+1)/2)*
curr_keylength);
left_page->size= new_left_length;
page_store_size(share, left_page);
right_page->size= new_right_length;
page_store_size(share, right_page);
DBUG_PRINT("info", ("left_length: %u -> %u right_length: %u -> %u",
left_length, new_left_length,
right_length, new_right_length));
if (left_length < new_left_length)
{
uint length;
DBUG_PRINT("info", ("move keys to end of buff"));
/* Move keys right_page -> left_page */
pos= left_page->buff+left_length;
memcpy(pos,father_key_pos, (size_t) k_length);
memcpy(pos+k_length, right_page->buff + share->keypage_header,
(size_t) (length=new_left_length - left_length - k_length));
pos= right_page->buff + share->keypage_header + length;
memcpy(father_key_pos, pos, (size_t) k_length);
bmove(right_page->buff + share->keypage_header,
pos + k_length, new_right_length - share->keypage_header);
if (share->now_transactional)
{
if (right)
{
/*
Log changes to page on left
The original page is on the left and stored in left_page->buff
We have on the page the newly inserted key and data
from buff added last on the page
*/
if (_ma_log_split(curr_page,
left_length - s_temp->move_length,
new_left_length,
s_temp->key_pos, s_temp->changed_length,
s_temp->move_length,
KEY_OP_ADD_SUFFIX,
curr_page->buff + left_length,
new_left_length - left_length,
new_left_length - left_length+ k_length))
goto err;
/*
Log changes to page on right
This contains the original data with some keys deleted from
start of page
*/
if (_ma_log_prefix(&next_page, 0,
((int) new_right_length - (int) right_length),
KEY_OP_DEBUG_LOG_PREFIX_3))
goto err;
}
else
{
/*
Log changes to page on right (the original page) which is in buff
Data is removed from start of page
The inserted key may be in buff or moved to curr_buff
*/
if (_ma_log_del_prefix(curr_page,
right_length - s_temp->changed_length,
new_right_length,
s_temp->key_pos, s_temp->changed_length,
s_temp->move_length))
goto err;
/*
Log changes to page on left, which has new data added last
*/
if (_ma_log_suffix(&next_page, left_length, new_left_length))
goto err;
}
}
}
else
{
uint length;
DBUG_PRINT("info", ("move keys to start of right_page"));
bmove_upp(right_page->buff + new_right_length,
right_page->buff + right_length,
right_length - share->keypage_header);
length= new_right_length -right_length - k_length;
memcpy(right_page->buff + share->keypage_header + length, father_key_pos,
(size_t) k_length);
pos= left_page->buff + new_left_length;
memcpy(father_key_pos, pos, (size_t) k_length);
memcpy(right_page->buff + share->keypage_header, pos+k_length,
(size_t) length);
if (share->now_transactional)
{
if (right)
{
/*
Log changes to page on left
The original page is on the left and stored in curr_buff
The page is shortened from end and the key may be on the page
*/
if (_ma_log_split(curr_page,
left_length - s_temp->move_length,
new_left_length,
s_temp->key_pos, s_temp->changed_length,
s_temp->move_length,
KEY_OP_NONE, (uchar*) 0, 0, 0))
goto err;
/*
Log changes to page on right
This contains the original data, with some data from cur_buff
added first
*/
if (_ma_log_prefix(&next_page,
(uint) (new_right_length - right_length),
(int) (new_right_length - right_length),
KEY_OP_DEBUG_LOG_PREFIX_4))
goto err;
}
else
{
/*
Log changes to page on right (the original page) which is in buff
We have on the page the newly inserted key and data
from buff added first on the page
*/
uint diff_length= new_right_length - right_length;
if (_ma_log_split(curr_page,
left_length - s_temp->move_length,
new_right_length,
s_temp->key_pos + diff_length,
s_temp->changed_length,
s_temp->move_length,
KEY_OP_ADD_PREFIX,
curr_page->buff + share->keypage_header,
diff_length, diff_length + k_length))
goto err;
/*
Log changes to page on left, which is shortened from end
*/
if (_ma_log_suffix(&next_page, left_length, new_left_length))
goto err;
}
}
}
/* Log changes to father (one level up) page */
if (share->now_transactional &&
_ma_log_change(father_page, father_key_pos, k_length,
KEY_OP_DEBUG_FATHER_CHANGED_1))
goto err;
/*
next_page_link->changed is marked as true above and fathers
page_link->changed is marked as true in caller
*/
if (_ma_write_keypage(&next_page, PAGECACHE_LOCK_LEFT_WRITELOCKED,
DFLT_INIT_HITS) ||
_ma_write_keypage(father_page,
PAGECACHE_LOCK_LEFT_WRITELOCKED, DFLT_INIT_HITS))
goto err;
stack_alloc_free(tmp_part_key, buff_alloced);
DBUG_RETURN(0);
}
/* left_page and right_page are full, lets split and make new nod */
extra_buff= info->buff+share->base.max_key_block_length;
new_left_length= new_right_length= (share->keypage_header + nod_flag +
(keys+1) / 3 * curr_keylength);
extra_page.info= info;
extra_page.keyinfo= keyinfo;
extra_page.buff= extra_buff;
/*
5 is the minum number of keys we can have here. This comes from
the fact that each full page can store at least 2 keys and in this case
we have a 'split' key, ie 2+2+1 = 5
*/
if (keys == 5) /* Too few keys to balance */
new_left_length-=curr_keylength;
extra_length= (nod_flag + left_length + right_length -
new_left_length - new_right_length - curr_keylength);
extra_buff_length= extra_length + share->keypage_header;
DBUG_PRINT("info",("left_length: %d right_length: %d new_left_length: %d new_right_length: %d extra_length: %d",
left_length, right_length,
new_left_length, new_right_length,
extra_length));
left_page->size= new_left_length;
page_store_size(share, left_page);
right_page->size= new_right_length;
page_store_size(share, right_page);
bzero(extra_buff, share->keypage_header);
extra_page.flag= nod_flag ? KEYPAGE_FLAG_ISNOD : 0;
extra_page.size= extra_buff_length;
page_store_info(share, &extra_page);
/* Copy key number */
_ma_store_keynr(share, extra_buff, keyinfo->key_nr);
/* move first largest keys to new page */
pos= right_page->buff + right_length-extra_length;
memcpy(extra_buff + share->keypage_header, pos, extra_length);
/* Zero old data from buffer */
bzero(extra_buff + extra_buff_length,
share->block_size - extra_buff_length);
/* Save new parting key between buff and extra_buff */
memcpy(tmp_part_key, pos-k_length,k_length);
/* Make place for new keys */
bmove_upp(right_page->buff + new_right_length, pos - k_length,
right_length - extra_length - k_length - share->keypage_header);
/* Copy keys from left page */
pos= left_page->buff + new_left_length;
memcpy(right_page->buff + share->keypage_header, pos + k_length,
(size_t) (tmp_length= left_length - new_left_length - k_length));
/* Copy old parting key */
parting_key= right_page->buff + share->keypage_header + tmp_length;
memcpy(parting_key, father_key_pos, (size_t) k_length);
/* Move new parting keys up to caller */
memcpy((right ? key->data : father_key_pos),pos,(size_t) k_length);
memcpy((right ? father_key_pos : key->data),tmp_part_key, k_length);
if ((extra_page.pos= _ma_new(info, DFLT_INIT_HITS, &new_page_link))
== HA_OFFSET_ERROR)
goto err;
_ma_kpointer(info,key->data+k_length, extra_page.pos);
/* This is safe as long we are using not keys with transid */
key->data_length= k_length - info->s->rec_reflength;
key->ref_length= info->s->rec_reflength;
if (right)
{
/*
Page order according to key values:
orignal_page (curr_page = left_page), next_page (buff), extra_buff
Move page positions so that we store data in extra_page where
next_page was and next_page will be stored at the new position
*/
swap_variables(my_off_t, extra_page.pos, next_page.pos);
}
if (share->now_transactional)
{
if (right)
{
/*
left_page is shortened,
right_page is getting new keys at start and shortened from end.
extra_page is new page
Note that extra_page (largest key parts) will be stored at the
place of the original 'right' page (next_page) and right page
will be stored at the new page position
This makes the log entries smaller as right_page contains all
data to generate the data extra_buff
*/
/*
Log changes to page on left (page shortened page at end)
*/
if (_ma_log_split(curr_page,
left_length - s_temp->move_length, new_left_length,
s_temp->key_pos, s_temp->changed_length,
s_temp->move_length,
KEY_OP_NONE, (uchar*) 0, 0, 0))
goto err;
/*
Log changes to right page (stored at next page)
This contains the last 'extra_buff' from 'buff'
*/
if (_ma_log_prefix(&extra_page,
0, (int) (extra_buff_length - right_length),
KEY_OP_DEBUG_LOG_PREFIX_5))
goto err;
/*
Log changes to middle page, which is stored at the new page
position
*/
if (_ma_log_new(&next_page, 0))
goto err;
}
else
{
/*
Log changes to page on right (the original page) which is in buff
This contains the original data, with some data from curr_buff
added first and shortened at end
*/
int data_added_first= left_length - new_left_length;
if (_ma_log_key_middle(right_page,
new_right_length,
data_added_first,
data_added_first,
extra_length,
s_temp->key_pos,
s_temp->changed_length,
s_temp->move_length))
goto err;
/* Log changes to page on left, which is shortened from end */
if (_ma_log_suffix(left_page, left_length, new_left_length))
goto err;
/* Log change to rightmost (new) page */
if (_ma_log_new(&extra_page, 0))
goto err;
}
/* Log changes to father (one level up) page */
if (share->now_transactional &&
_ma_log_change(father_page, father_key_pos, k_length,
KEY_OP_DEBUG_FATHER_CHANGED_2))
goto err;
}
if (_ma_write_keypage(&next_page,
(right ? new_page_link->write_lock :
PAGECACHE_LOCK_LEFT_WRITELOCKED),
DFLT_INIT_HITS) ||
_ma_write_keypage(&extra_page,
(!right ? new_page_link->write_lock :
PAGECACHE_LOCK_LEFT_WRITELOCKED),
DFLT_INIT_HITS))
goto err;
stack_alloc_free(tmp_part_key, buff_alloced);
DBUG_RETURN(1); /* Middle key up */
err:
stack_alloc_free(tmp_part_key, buff_alloced);
DBUG_RETURN(-1);
} /* _ma_balance_page */
/**********************************************************************
* Bulk insert code *
**********************************************************************/
typedef struct {
MARIA_HA *info;
uint keynr;
} bulk_insert_param;
static my_bool _ma_ck_write_tree(register MARIA_HA *info, MARIA_KEY *key)
{
my_bool error;
uint keynr= key->keyinfo->key_nr;
DBUG_ENTER("_ma_ck_write_tree");
/* Store ref_length as this is always constant */
info->bulk_insert_ref_length= key->ref_length;
error= tree_insert(&info->bulk_insert[keynr], key->data,
key->data_length + key->ref_length,
info->bulk_insert[keynr].custom_arg) == 0;
DBUG_RETURN(error);
} /* _ma_ck_write_tree */
/* typeof(_ma_keys_compare)=qsort_cmp2 */
static int keys_compare(bulk_insert_param *param, uchar *key1, uchar *key2)
{
uint not_used[2];
return ha_key_cmp(param->info->s->keyinfo[param->keynr].seg,
key1, key2, USE_WHOLE_KEY, SEARCH_SAME,
not_used);
}
static int keys_free(void* key_arg, TREE_FREE mode, void *param_arg)
{
/*
Probably I can use info->lastkey here, but I'm not sure,
and to be safe I'd better use local lastkey.
*/
bulk_insert_param *param= (bulk_insert_param*)param_arg;
MARIA_SHARE *share= param->info->s;
uchar lastkey[MARIA_MAX_KEY_BUFF], *key= (uchar*)key_arg;
uint keylen;
MARIA_KEYDEF *keyinfo= share->keyinfo + param->keynr;
MARIA_KEY tmp_key;
switch (mode) {
case free_init:
if (share->lock_key_trees)
{
mysql_rwlock_wrlock(&keyinfo->root_lock);
keyinfo->version++;
}
return 0;
case free_free:
/* Note: keylen doesn't contain transid lengths */
keylen= _ma_keylength(keyinfo, key);
tmp_key.data= lastkey;
tmp_key.keyinfo= keyinfo;
tmp_key.data_length= keylen - share->rec_reflength;
tmp_key.ref_length= param->info->bulk_insert_ref_length;
tmp_key.flag= (param->info->bulk_insert_ref_length ==
share->rec_reflength ? 0 : SEARCH_USER_KEY_HAS_TRANSID);
/*
We have to copy key as ma_ck_write_btree may need the buffer for
copying middle key up if tree is growing
*/
memcpy(lastkey, key, tmp_key.data_length + tmp_key.ref_length);
_ma_ck_write_btree(param->info, &tmp_key);
return 0;
case free_end:
if (share->lock_key_trees)
mysql_rwlock_unlock(&keyinfo->root_lock);
return 0;
}
return 0;
}
int maria_init_bulk_insert(MARIA_HA *info, size_t cache_size, ha_rows rows)
{
MARIA_SHARE *share= info->s;
MARIA_KEYDEF *key=share->keyinfo;
bulk_insert_param *params;
uint i, num_keys, total_keylength;
ulonglong key_map;
DBUG_ENTER("_ma_init_bulk_insert");
DBUG_PRINT("enter",("cache_size: %lu", (ulong) cache_size));
DBUG_ASSERT(!info->bulk_insert &&
(!rows || rows >= MARIA_MIN_ROWS_TO_USE_BULK_INSERT));
maria_clear_all_keys_active(key_map);
for (i=total_keylength=num_keys=0 ; i < share->base.keys ; i++)
{
if (! (key[i].flag & HA_NOSAME) && (share->base.auto_key != i + 1) &&
maria_is_key_active(share->state.key_map, i))
{
num_keys++;
maria_set_key_active(key_map, i);
total_keylength+=key[i].maxlength+TREE_ELEMENT_EXTRA_SIZE;
}
}
if (num_keys==0 ||
num_keys * (size_t) MARIA_MIN_SIZE_BULK_INSERT_TREE > cache_size)
DBUG_RETURN(0);
if (rows && rows*total_keylength < cache_size)
cache_size= (size_t)rows;
else
cache_size/=total_keylength*16;
info->bulk_insert=(TREE *)
my_malloc(PSI_INSTRUMENT_ME, (sizeof(TREE)*share->base.keys+
sizeof(bulk_insert_param)*num_keys),MYF(0));
if (!info->bulk_insert)
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
params=(bulk_insert_param *)(info->bulk_insert+share->base.keys);
for (i=0 ; i < share->base.keys ; i++)
{
if (maria_is_key_active(key_map, i))
{
params->info=info;
params->keynr=i;
/* Only allocate a 16'th of the buffer at a time */
init_tree(&info->bulk_insert[i],
cache_size * key[i].maxlength,
cache_size * key[i].maxlength, 0,
(qsort_cmp2) keys_compare, keys_free, (void *)params++, MYF(0));
}
else
info->bulk_insert[i].root=0;
}
DBUG_RETURN(0);
}
void maria_flush_bulk_insert(MARIA_HA *info, uint inx)
{
if (info->bulk_insert)
{
if (is_tree_inited(&info->bulk_insert[inx]))
reset_tree(&info->bulk_insert[inx]);
}
}
int maria_end_bulk_insert(MARIA_HA *info, my_bool abort)
{
int first_error= 0;
DBUG_ENTER("maria_end_bulk_insert");
if (info->bulk_insert)
{
uint i;
for (i=0 ; i < info->s->base.keys ; i++)
{
if (is_tree_inited(&info->bulk_insert[i]))
{
int error;
if (info->s->deleting)
reset_free_element(&info->bulk_insert[i]);
if ((error= delete_tree(&info->bulk_insert[i], abort)))
{
first_error= first_error ? first_error : error;
abort= 1;
}
}
}
my_free(info->bulk_insert);
info->bulk_insert= 0;
}
DBUG_RETURN(first_error);
}
/****************************************************************************
Dedicated functions that generate log entries
****************************************************************************/
int _ma_write_undo_key_insert(MARIA_HA *info, const MARIA_KEY *key,
my_off_t *root, my_off_t new_root, LSN *res_lsn)
{
MARIA_SHARE *share= info->s;
MARIA_KEYDEF *keyinfo= key->keyinfo;
uchar log_data[LSN_STORE_SIZE + FILEID_STORE_SIZE +
KEY_NR_STORE_SIZE];
const uchar *key_value;
LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2];
struct st_msg_to_write_hook_for_undo_key msg;
uint key_length;
/* Save if we need to write a clr record */
lsn_store(log_data, info->trn->undo_lsn);
key_nr_store(log_data + LSN_STORE_SIZE + FILEID_STORE_SIZE,
keyinfo->key_nr);
key_length= key->data_length + key->ref_length;
log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data;
log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data);
log_array[TRANSLOG_INTERNAL_PARTS + 1].str= key->data;
log_array[TRANSLOG_INTERNAL_PARTS + 1].length= key_length;
msg.root= root;
msg.value= new_root;
msg.auto_increment= 0;
key_value= key->data;
if (share->base.auto_key == ((uint) keyinfo->key_nr + 1))
{
const HA_KEYSEG *keyseg= keyinfo->seg;
uchar reversed[MARIA_MAX_KEY_BUFF];
if (keyseg->flag & HA_SWAP_KEY)
{
/* We put key from log record to "data record" packing format... */
const uchar *key_ptr= key->data, *key_end= key->data + keyseg->length;
uchar *to= reversed + keyseg->length;
do
{
*--to= *key_ptr++;
} while (key_ptr != key_end);
key_value= to;
}
/* ... so that we can read it with: */
msg.auto_increment=
ma_retrieve_auto_increment(key_value, keyseg->type);
/* and write_hook_for_undo_key_insert() will pick this. */
}
return translog_write_record(res_lsn, LOGREC_UNDO_KEY_INSERT,
info->trn, info,
(translog_size_t)
log_array[TRANSLOG_INTERNAL_PARTS + 0].length +
key_length,
TRANSLOG_INTERNAL_PARTS + 2, log_array,
log_data + LSN_STORE_SIZE, &msg) ? -1 : 0;
}
/**
@brief Log creation of new page
@note
We don't have to store the page_length into the log entry as we can
calculate this from the length of the log entry
@retval 1 error
@retval 0 ok
*/
my_bool _ma_log_new(MARIA_PAGE *ma_page, my_bool root_page)
{
LSN lsn;
uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2 + KEY_NR_STORE_SIZE
+1];
uint page_length;
LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2];
MARIA_HA *info= ma_page->info;
MARIA_SHARE *share= info->s;
my_off_t page= ma_page->pos / share->block_size;
DBUG_ENTER("_ma_log_new");
DBUG_PRINT("enter", ("page: %lu", (ulong) page));
DBUG_ASSERT(share->now_transactional);
/* Store address of new root page */
page_store(log_data + FILEID_STORE_SIZE, page);
/* Store link to next unused page */
if (info->key_del_used == 2)
page= 0; /* key_del not changed */
else
page= ((share->key_del_current == HA_OFFSET_ERROR) ? IMPOSSIBLE_PAGE_NO :
share->key_del_current / share->block_size);
page_store(log_data + FILEID_STORE_SIZE + PAGE_STORE_SIZE, page);
key_nr_store(log_data + FILEID_STORE_SIZE + PAGE_STORE_SIZE*2,
ma_page->keyinfo->key_nr);
log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE*2 + KEY_NR_STORE_SIZE]=
(uchar) root_page;
log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data;
log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data);
page_length= ma_page->size - LSN_STORE_SIZE;
log_array[TRANSLOG_INTERNAL_PARTS + 1].str= ma_page->buff + LSN_STORE_SIZE;
log_array[TRANSLOG_INTERNAL_PARTS + 1].length= page_length;
/* Remember new page length for future log entires for same page */
ma_page->org_size= ma_page->size;
if (translog_write_record(&lsn, LOGREC_REDO_INDEX_NEW_PAGE,
info->trn, info,
(translog_size_t)
(sizeof(log_data) + page_length),
TRANSLOG_INTERNAL_PARTS + 2, log_array,
log_data, NULL))
DBUG_RETURN(1);
DBUG_RETURN(0);
}
/**
@brief
Log when some part of the key page changes
*/
my_bool _ma_log_change(MARIA_PAGE *ma_page, const uchar *key_pos, uint length,
enum en_key_debug debug_marker __attribute__((unused)))
{
LSN lsn;
uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 2 + 6 + 7], *log_pos;
LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 4];
uint offset= (uint) (key_pos - ma_page->buff), translog_parts;
MARIA_HA *info= ma_page->info;
my_off_t page= ma_page->pos / info->s->block_size;
DBUG_ENTER("_ma_log_change");
DBUG_PRINT("enter", ("page: %lu length: %u", (ulong) page, length));
DBUG_ASSERT(info->s->now_transactional);
DBUG_ASSERT(offset + length <= ma_page->size);
DBUG_ASSERT(ma_page->org_size == ma_page->size);
/* Store address of new root page */
page= ma_page->pos / info->s->block_size;
page_store(log_data + FILEID_STORE_SIZE, page);
log_pos= log_data+ FILEID_STORE_SIZE + PAGE_STORE_SIZE;
#ifdef EXTRA_DEBUG_KEY_CHANGES
(*log_pos++)= KEY_OP_DEBUG;
(*log_pos++)= debug_marker;
#endif
log_pos[0]= KEY_OP_OFFSET;
int2store(log_pos+1, offset);
log_pos[3]= KEY_OP_CHANGE;
int2store(log_pos+4, length);
log_pos+= 6;
log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data;
log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (log_pos - log_data);
log_array[TRANSLOG_INTERNAL_PARTS + 1].str= key_pos;
log_array[TRANSLOG_INTERNAL_PARTS + 1].length= length;
translog_parts= 2;
_ma_log_key_changes(ma_page,
log_array + TRANSLOG_INTERNAL_PARTS + translog_parts,
log_pos, &length, &translog_parts);
if (translog_write_record(&lsn, LOGREC_REDO_INDEX,
info->trn, info,
(translog_size_t) (log_pos - log_data) + length,
TRANSLOG_INTERNAL_PARTS + translog_parts,
log_array, log_data, NULL))
DBUG_RETURN(1);
DBUG_RETURN(0);
}
/**
@brief Write log entry for page splitting
@fn _ma_log_split()
@param
ma_page Page that is changed
org_length Original length of page. Can be bigger than block_size
for block that overflowed
new_length New length of page
key_pos Where key is inserted on page (may be 0 if no key)
key_length Number of bytes changed at key_pos
move_length Number of bytes moved at key_pos to make room for key
prefix_or_suffix KEY_OP_NONE Ignored
KEY_OP_ADD_PREFIX Add data to start of page
KEY_OP_ADD_SUFFIX Add data to end of page
data What data was added
data_length Number of bytes added first or last
changed_length Number of bytes changed first or last.
@note
Write log entry for page that has got a key added to the page under
one and only one of the following senarios:
- Page is shortened from end
- Data is added to end of page
- Data added at front of page
*/
static my_bool _ma_log_split(MARIA_PAGE *ma_page,
uint org_length, uint new_length,
const uchar *key_pos, uint key_length,
int move_length, enum en_key_op prefix_or_suffix,
const uchar *data, uint data_length,
uint changed_length)
{
LSN lsn;
uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 2 + 2 + 3+3+3+3+3+2 +7];
uchar *log_pos;
LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 6];
uint offset= (uint) (key_pos - ma_page->buff);
uint translog_parts, extra_length;
MARIA_HA *info= ma_page->info;
my_off_t page= ma_page->pos / info->s->block_size;
DBUG_ENTER("_ma_log_split");
DBUG_PRINT("enter", ("page: %lu org_length: %u new_length: %u",
(ulong) page, org_length, new_length));
DBUG_ASSERT(changed_length >= data_length);
DBUG_ASSERT(org_length <= info->s->max_index_block_size);
DBUG_ASSERT(new_length == ma_page->size);
DBUG_ASSERT(org_length == ma_page->org_size);
log_pos= log_data + FILEID_STORE_SIZE;
page_store(log_pos, page);
log_pos+= PAGE_STORE_SIZE;
#ifdef EXTRA_DEBUG_KEY_CHANGES
(*log_pos++)= KEY_OP_DEBUG;
(*log_pos++)= KEY_OP_DEBUG_LOG_SPLIT;
#endif
/* Store keypage_flag */
*log_pos++= KEY_OP_SET_PAGEFLAG;
*log_pos++= _ma_get_keypage_flag(info->s, ma_page->buff);
if (new_length <= offset || !key_pos)
{
/*
Page was split before inserted key. Write redo entry where
we just cut current page at page_length
*/
uint length_offset= org_length - new_length;
log_pos[0]= KEY_OP_DEL_SUFFIX;
int2store(log_pos+1, length_offset);
log_pos+= 3;
translog_parts= 1;
extra_length= 0;
DBUG_ASSERT(data_length == 0);
}
else
{
/* Key was added to page which was split after the inserted key */
uint max_key_length;
/*
Handle case when split happened directly after the newly inserted key.
*/
max_key_length= new_length - offset;
extra_length= MY_MIN(key_length, max_key_length);
if (offset + move_length > new_length)
{
/* This is true when move_length includes changes for next packed key */
move_length= new_length - offset;
}
if ((int) new_length < (int) (org_length + move_length + data_length))
{
/* Shorten page */
uint diff= org_length + move_length + data_length - new_length;
log_pos[0]= KEY_OP_DEL_SUFFIX;
int2store(log_pos + 1, diff);
log_pos+= 3;
DBUG_ASSERT(data_length == 0); /* Page is shortened */
DBUG_ASSERT(offset <= org_length - diff);
}
else
{
DBUG_ASSERT(new_length == org_length + move_length + data_length);
DBUG_ASSERT(offset <= org_length);
}
log_pos[0]= KEY_OP_OFFSET;
int2store(log_pos+1, offset);
log_pos+= 3;
if (move_length)
{
log_pos[0]= KEY_OP_SHIFT;
int2store(log_pos+1, move_length);
log_pos+= 3;
}
log_pos[0]= KEY_OP_CHANGE;
int2store(log_pos+1, extra_length);
log_pos+= 3;
/* Point to original inserted key data */
if (prefix_or_suffix == KEY_OP_ADD_PREFIX)
key_pos+= data_length;
translog_parts= 2;
log_array[TRANSLOG_INTERNAL_PARTS + 1].str= key_pos;
log_array[TRANSLOG_INTERNAL_PARTS + 1].length= extra_length;
}
if (data_length)
{
/* Add prefix or suffix */
log_pos[0]= prefix_or_suffix;
int2store(log_pos+1, data_length);
log_pos+= 3;
if (prefix_or_suffix == KEY_OP_ADD_PREFIX)
{
int2store(log_pos+1, changed_length);
log_pos+= 2;
data_length= changed_length;
}
log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].str= data;
log_array[TRANSLOG_INTERNAL_PARTS + translog_parts].length= data_length;
translog_parts++;
extra_length+= data_length;
}
log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data;
log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (uint) (log_pos -
log_data);
_ma_log_key_changes(ma_page,
log_array + TRANSLOG_INTERNAL_PARTS + translog_parts,
log_pos, &extra_length, &translog_parts);
/* Remember new page length for future log entires for same page */
ma_page->org_size= ma_page->size;
DBUG_RETURN(translog_write_record(&lsn, LOGREC_REDO_INDEX,
info->trn, info,
(translog_size_t)
log_array[TRANSLOG_INTERNAL_PARTS +
0].length + extra_length,
TRANSLOG_INTERNAL_PARTS + translog_parts,
log_array, log_data, NULL));
}
/**
@brief
Write log entry for page that has got a key added to the page
and page is shortened from start of page
@fn _ma_log_del_prefix()
@param info Maria handler
@param page Page number
@param buff Page buffer
@param org_length Length of buffer when read
@param new_length Final length
@param key_pos Where on page buffer key was added. This is position
before prefix was removed
@param key_length How many bytes was changed at 'key_pos'
@param move_length How many bytes was moved up when key was added
@return
@retval 0 ok
@retval 1 error
*/
static my_bool _ma_log_del_prefix(MARIA_PAGE *ma_page,
uint org_length, uint new_length,
const uchar *key_pos, uint key_length,
int move_length)
{
LSN lsn;
uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 2 + 2 + 12 + 7];
uchar *log_pos;
LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 4];
uint offset= (uint) (key_pos - ma_page->buff);
uint diff_length= org_length + move_length - new_length;
uint translog_parts, extra_length;
MARIA_HA *info= ma_page->info;
my_off_t page= ma_page->pos / info->s->block_size;
DBUG_ENTER("_ma_log_del_prefix");
DBUG_PRINT("enter", ("page: %lu org_length: %u new_length: %u",
(ulong) page, org_length, new_length));
DBUG_ASSERT((int) diff_length > 0);
DBUG_ASSERT(ma_page->org_size == org_length);
DBUG_ASSERT(ma_page->size == new_length);
log_pos= log_data + FILEID_STORE_SIZE;
page_store(log_pos, page);
log_pos+= PAGE_STORE_SIZE;
translog_parts= 1;
extra_length= 0;
#ifdef EXTRA_DEBUG_KEY_CHANGES
*log_pos++= KEY_OP_DEBUG;
*log_pos++= KEY_OP_DEBUG_LOG_DEL_PREFIX;
#endif
/* Store keypage_flag */
*log_pos++= KEY_OP_SET_PAGEFLAG;
*log_pos++= _ma_get_keypage_flag(info->s, ma_page->buff);
if (offset < diff_length + info->s->keypage_header)
{
/*
Key is not anymore on page. Move data down, but take into account that
the original page had grown with 'move_length bytes'
*/
DBUG_ASSERT(offset + key_length <= diff_length + info->s->keypage_header);
log_pos[0]= KEY_OP_DEL_PREFIX;
int2store(log_pos+1, diff_length - move_length);
log_pos+= 3;
}
else
{
/*
Correct position to key, as data before key has been delete and key
has thus been moved down
*/
offset-= diff_length;
key_pos-= diff_length;
/* Move data down */
log_pos[0]= KEY_OP_DEL_PREFIX;
int2store(log_pos+1, diff_length);
log_pos+= 3;
log_pos[0]= KEY_OP_OFFSET;
int2store(log_pos+1, offset);
log_pos+= 3;
if (move_length)
{
log_pos[0]= KEY_OP_SHIFT;
int2store(log_pos+1, move_length);
log_pos+= 3;
}
log_pos[0]= KEY_OP_CHANGE;
int2store(log_pos+1, key_length);
log_pos+= 3;
log_array[TRANSLOG_INTERNAL_PARTS + 1].str= key_pos;
log_array[TRANSLOG_INTERNAL_PARTS + 1].length= key_length;
translog_parts= 2;
extra_length= key_length;
}
log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data;
log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (uint) (log_pos -
log_data);
_ma_log_key_changes(ma_page,
log_array + TRANSLOG_INTERNAL_PARTS + translog_parts,
log_pos, &extra_length, &translog_parts);
/* Remember new page length for future log entires for same page */
ma_page->org_size= ma_page->size;
DBUG_RETURN(translog_write_record(&lsn, LOGREC_REDO_INDEX,
info->trn, info,
(translog_size_t)
log_array[TRANSLOG_INTERNAL_PARTS +
0].length + extra_length,
TRANSLOG_INTERNAL_PARTS + translog_parts,
log_array, log_data, NULL));
}
/**
@brief
Write log entry for page that has got data added first and
data deleted last. Old changed key may be part of page
*/
static my_bool _ma_log_key_middle(MARIA_PAGE *ma_page,
uint new_length,
uint data_added_first,
uint data_changed_first,
uint data_deleted_last,
const uchar *key_pos,
uint key_length, int move_length)
{
LSN lsn;
uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 2 + 2 + 3+5+3+3+3 + 7];
uchar *log_pos;
LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 6];
uint key_offset;
uint translog_parts, extra_length;
MARIA_HA *info= ma_page->info;
my_off_t page= ma_page->pos / info->s->block_size;
DBUG_ENTER("_ma_log_key_middle");
DBUG_PRINT("enter", ("page: %lu", (ulong) page));
DBUG_ASSERT(ma_page->size == new_length);
/* new place of key after changes */
key_pos+= data_added_first;
key_offset= (uint) (key_pos - ma_page->buff);
if (key_offset < new_length)
{
/* key is on page; Calculate how much of the key is there */
uint max_key_length= new_length - key_offset;
if (max_key_length < key_length)
{
/* Key is last on page */
key_length= max_key_length;
move_length= 0;
}
/*
Take into account that new data was added as part of original key
that also needs to be removed from page
*/
data_deleted_last+= move_length;
}
/* First log changes to page */
log_pos= log_data + FILEID_STORE_SIZE;
page_store(log_pos, page);
log_pos+= PAGE_STORE_SIZE;
#ifdef EXTRA_DEBUG_KEY_CHANGES
*log_pos++= KEY_OP_DEBUG;
*log_pos++= KEY_OP_DEBUG_LOG_MIDDLE;
#endif
/* Store keypage_flag */
*log_pos++= KEY_OP_SET_PAGEFLAG;
*log_pos++= _ma_get_keypage_flag(info->s, ma_page->buff);
log_pos[0]= KEY_OP_DEL_SUFFIX;
int2store(log_pos+1, data_deleted_last);
log_pos+= 3;
log_pos[0]= KEY_OP_ADD_PREFIX;
int2store(log_pos+1, data_added_first);
int2store(log_pos+3, data_changed_first);
log_pos+= 5;
log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data;
log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (uint) (log_pos -
log_data);
log_array[TRANSLOG_INTERNAL_PARTS + 1].str= (ma_page->buff +
info->s->keypage_header);
log_array[TRANSLOG_INTERNAL_PARTS + 1].length= data_changed_first;
translog_parts= 2;
extra_length= data_changed_first;
/* If changed key is on page, log those changes too */
if (key_offset < new_length)
{
uchar *start_log_pos= log_pos;
log_pos[0]= KEY_OP_OFFSET;
int2store(log_pos+1, key_offset);
log_pos+= 3;
if (move_length)
{
log_pos[0]= KEY_OP_SHIFT;
int2store(log_pos+1, move_length);
log_pos+= 3;
}
log_pos[0]= KEY_OP_CHANGE;
int2store(log_pos+1, key_length);
log_pos+= 3;
log_array[TRANSLOG_INTERNAL_PARTS + 2].str= start_log_pos;
log_array[TRANSLOG_INTERNAL_PARTS + 2].length= (uint) (log_pos -
start_log_pos);
log_array[TRANSLOG_INTERNAL_PARTS + 3].str= key_pos;
log_array[TRANSLOG_INTERNAL_PARTS + 3].length= key_length;
translog_parts+=2;
extra_length+= (uint) (log_array[TRANSLOG_INTERNAL_PARTS + 2].length +
key_length);
}
_ma_log_key_changes(ma_page,
log_array + TRANSLOG_INTERNAL_PARTS + translog_parts,
log_pos, &extra_length, &translog_parts);
/* Remember new page length for future log entires for same page */
ma_page->org_size= ma_page->size;
DBUG_RETURN(translog_write_record(&lsn, LOGREC_REDO_INDEX,
info->trn, info,
(translog_size_t)
(log_array[TRANSLOG_INTERNAL_PARTS +
0].length + extra_length),
TRANSLOG_INTERNAL_PARTS + translog_parts,
log_array, log_data, NULL));
}
#ifdef NOT_NEEDED
/**
@brief
Write log entry for page that has got data added first and
data deleted last
*/
static my_bool _ma_log_middle(MARIA_PAGE *ma_page,
uint data_added_first, uint data_changed_first,
uint data_deleted_last)
{
LSN lsn;
LEX_STRING log_array[TRANSLOG_INTERNAL_PARTS + 4];
uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE + 3 + 5 + 7], *log_pos;
MARIA_HA *info= ma_page->info;
my_off_t page= ma_page->page / info->s->block_size;
uint translog_parts, extra_length;
DBUG_ENTER("_ma_log_middle");
DBUG_PRINT("enter", ("page: %lu", (ulong) page));
DBUG_ASSERT(ma_page->org_size + data_added_first - data_deleted_last ==
ma_page->size);
log_pos= log_data + FILEID_STORE_SIZE;
page_store(log_pos, page);
log_pos+= PAGE_STORE_SIZE;
log_pos[0]= KEY_OP_DEL_PREFIX;
int2store(log_pos+1, data_deleted_last);
log_pos+= 3;
log_pos[0]= KEY_OP_ADD_PREFIX;
int2store(log_pos+1, data_added_first);
int2store(log_pos+3, data_changed_first);
log_pos+= 5;
log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data;
log_array[TRANSLOG_INTERNAL_PARTS + 0].length= (uint) (log_pos -
log_data);
log_array[TRANSLOG_INTERNAL_PARTS + 1].str= ((char*) buff +
info->s->keypage_header);
log_array[TRANSLOG_INTERNAL_PARTS + 1].length= data_changed_first;
translog_parts= 2;
extra_length= data_changed_first;
_ma_log_key_changes(ma_page,
log_array + TRANSLOG_INTERNAL_PARTS + translog_parts,
log_pos, &extra_length, &translog_parts);
/* Remember new page length for future log entires for same page */
ma_page->org_size= ma_page->size;
DBUG_RETURN(translog_write_record(&lsn, LOGREC_REDO_INDEX,
info->trn, info,
(translog_size_t)
log_array[TRANSLOG_INTERNAL_PARTS +
0].length + extra_length,
TRANSLOG_INTERNAL_PARTS + translog_parts,
log_array, log_data, NULL));
}
#endif