/* Copyright (C) 2007 Michael Widenius
Copyright ( c ) 2010 , 2013 , Monty Program Ab .
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 */
/*
Bitmap handling ( for records in block )
The data file starts with a bitmap page , followed by as many data
pages as the bitmap can cover . After this there is a new bitmap page
and more data pages etc .
The bitmap code assumes there is always an active bitmap page and thus
that there is at least one bitmap page in the file
Structure of bitmap page :
Fixed size records ( to be implemented later ) :
2 bits are used to indicate :
0 Empty
1 0 - 75 % full ( at least room for 2 records )
2 75 - 100 % full ( at least room for one record )
3 100 % full ( no more room for records )
Assuming 8 K pages , this will allow us to map :
8192 ( bytes per page ) * 4 ( pages mapped per byte ) * 8192 ( page size ) = 256 M
( For Maria this will be 7 * 4 * 8192 = 224 K smaller because of LSN )
Note that for fixed size rows , we can ' t add more columns without doing
a full reorganization of the table . The user can always force a dynamic
size row format by specifying ROW_FORMAT = dynamic .
Dynamic size records :
3 bits are used to indicate Bytes free in 8 K page
0 Empty page 8176 ( head or tail )
1 0 - 30 % full ( at least room for 3 records ) 5724
2 30 - 60 % full ( at least room for 2 records ) 3271
3 60 - 90 % full ( at least room for one record ) 818
4 100 % full ( no more room for records ) 0
5 Tail page , 0 - 40 % full 4906
6 Tail page , 40 - 80 % full 1636
7 Full tail page or full blob page 0
Assuming 8 K pages , this will allow us to map :
8192 ( bytes per page ) * 8 bits / byte / 3 bits / page * 8192 ( page size ) = 170 . 7 M
Note that values 1 - 3 may be adjust for each individual table based on
' min record length ' . Tail pages are for overflow data which can be of
any size and thus doesn ' t have to be adjusted for different tables .
If we add more columns to the table , some of the originally calculated
' cut off ' points may not be optimal , but they shouldn ' t be ' drastically
wrong ' .
When allocating data from the bitmap , we are trying to do it in a
' best fit ' manner . Blobs and varchar blocks are given out in large
continuous extents to allow fast access to these . Before allowing a
row to ' flow over ' to other blocks , we will compact the page and use
all space on it . If there is many rows in the page , we will ensure
there is * LEFT_TO_GROW_ON_SPLIT * bytes left on the page to allow other
rows to grow .
The bitmap format allows us to extend the row file in big chunks , if needed .
When calculating the size for a packed row , we will calculate the following
things separately :
- Row header + null_bits + empty_bits fixed size segments etc .
- Size of all char / varchar fields
- Size of each blob field
The bitmap handler will get all the above information and return
either one page or a set of pages to put the different parts .
Bitmaps are read on demand in response to insert / delete / update operations .
The following bitmap pointers will be cached and stored on disk on close :
- Current insert_bitmap ; When inserting new data we will first try to
fill this one .
- First bitmap which is not completely full . This is updated when we
free data with an update or delete .
While flushing out bitmaps , we will cache the status of the bitmap in memory
to avoid having to read a bitmap for insert of new data that will not
be of any use
- Total empty space
- Largest number of continuous pages
Bitmap ONLY goes to disk in the following scenarios
- The file is closed ( and we flush all changes to disk )
- On checkpoint
( Ie : When we do a checkpoint , we have to ensure that all bitmaps are
put on disk even if they are not in the page cache ) .
- When explicitly requested ( for example on backup or after recovery ,
to simplify things )
The flow of writing a row is that :
- Mark the bitmap not flushable ( _ ma_bitmap_flushable ( X , 1 ) )
- Lock the bitmap
- Decide which data pages we will write to
- Mark them full in the bitmap page so that other threads do not try to
use the same data pages as us
- We unlock the bitmap
- Write the data pages
- Lock the bitmap
- Correct the bitmap page with the true final occupation of the data
pages ( that is , we marked pages full but when we are done we realize
we didn ' t fill them )
- Unlock the bitmap .
- Mark the bitmap flushable ( _ ma_bitmap_flushable ( X , - 1 ) )
*/
#include "maria_def.h"
#include "ma_blockrec.h"
#define FULL_HEAD_PAGE 4
#define FULL_TAIL_PAGE 7
const char *bits_to_txt[]=
{
"empty" , "00-30% full" , "30-60% full" , "60-90% full" , "full" ,
"tail 00-40 % full" , "tail 40-80 % full" , "tail/blob full"
};
#define WRONG_BITMAP_FLUSH 0 /*define to 1 only for provoking bugs*/
static my_bool _ma_read_bitmap_page(MARIA_HA *info,
MARIA_FILE_BITMAP *bitmap,
pgcache_page_no_t page);
static my_bool _ma_bitmap_create_missing(MARIA_HA *info,
MARIA_FILE_BITMAP *bitmap,
pgcache_page_no_t page);
static void _ma_bitmap_unpin_all(MARIA_SHARE *share);
#ifndef DBUG_OFF
static void _ma_check_bitmap(MARIA_FILE_BITMAP *bitmap);
#else
#define _ma_check_bitmap(A) do { } while (0 )
#endif
/* Write bitmap page to key cache */
static inline my_bool write_changed_bitmap(MARIA_SHARE *share,
MARIA_FILE_BITMAP *bitmap)
{
my_bool res;
DBUG_ENTER("write_changed_bitmap" );
DBUG_ASSERT(share->pagecache->block_size == bitmap->block_size);
DBUG_ASSERT(bitmap->file.pre_write_hook != 0 );
DBUG_PRINT("info" , ("bitmap->non_flushable: %u" , bitmap->non_flushable));
/*
Mark that a bitmap page has been written to page cache and we have
to flush it during checkpoint .
*/
bitmap->changed_not_flushed= 1 ;
if ((bitmap->non_flushable == 0 ) || WRONG_BITMAP_FLUSH)
{
res= pagecache_write(share->pagecache,
&bitmap->file, bitmap->page, 0 ,
bitmap->map, PAGECACHE_PLAIN_PAGE,
PAGECACHE_LOCK_LEFT_UNLOCKED,
PAGECACHE_PIN_LEFT_UNPINNED,
PAGECACHE_WRITE_DELAY, 0 , LSN_IMPOSSIBLE);
DBUG_ASSERT(!res);
DBUG_RETURN(res);
}
else
{
/*
bitmap - > non_flushable means that someone has changed the bitmap ,
but it ' s not yet complete so it can ' t yet be written to disk .
In this case we write the changed bitmap to the disk cache ,
but keep it pinned until the change is completed . The page will
be unpinned later by _ ma_bitmap_unpin_all ( ) as soon as non_flushable
is set back to 0 .
*/
MARIA_PINNED_PAGE page_link;
DBUG_PRINT("info" , ("Writing pinned bitmap page" ));
res= pagecache_write(share->pagecache,
&bitmap->file, bitmap->page, 0 ,
bitmap->map, PAGECACHE_PLAIN_PAGE,
PAGECACHE_LOCK_LEFT_UNLOCKED, PAGECACHE_PIN,
PAGECACHE_WRITE_DELAY, &page_link.link,
LSN_IMPOSSIBLE);
page_link.unlock= PAGECACHE_LOCK_LEFT_UNLOCKED;
page_link.changed= 1 ;
push_dynamic(&bitmap->pinned_pages, (const uchar*) (void *) &page_link);
DBUG_ASSERT(!res);
DBUG_RETURN(res);
}
}
/*
Initialize bitmap variables in share
SYNOPSIS
_ ma_bitmap_init ( )
share Share handler
file Data file handler
last_page Pointer to last page ( max_file_size ) that needs to be
mapped by the bitmap . This is adjusted to bitmap
alignment .
NOTES
This is called the first time a file is opened .
RETURN
0 ok
1 error
*/
my_bool _ma_bitmap_init(MARIA_SHARE *share, File file,
pgcache_page_no_t *last_page)
{
uint aligned_bit_blocks;
uint max_page_size;
MARIA_FILE_BITMAP *bitmap= &share->bitmap;
uint size= share->block_size;
myf flag= MY_WME | share->malloc_flag;
pgcache_page_no_t first_bitmap_with_space;
#ifndef DBUG_OFF
/* We want to have a copy of the bitmap to be able to print differences */
size*= 2 ;
#endif
if (!((bitmap->map= (uchar*) my_malloc(PSI_INSTRUMENT_ME, size, flag))) ||
my_init_dynamic_array(PSI_INSTRUMENT_ME, &bitmap->pinned_pages,
sizeof (MARIA_PINNED_PAGE), 1 , 1 , flag))
return 1 ;
bitmap->share= share;
bitmap->block_size= share->block_size;
bitmap->file.file= file;
bitmap->file.pagecache= share->kfile.pagecache;
_ma_bitmap_set_pagecache_callbacks(&bitmap->file, share);
/* Size needs to be aligned on 6 */
aligned_bit_blocks= (share->block_size - PAGE_SUFFIX_SIZE) / 6 ;
bitmap->max_total_size= bitmap->total_size= aligned_bit_blocks * 6 ;
/*
In each 6 bytes , we have 6 * 8 / 3 = 16 pages covered
The + 1 is to add the bitmap page , as this doesn ' t have to be covered
*/
bitmap->pages_covered= aligned_bit_blocks * 16 + 1 ;
bitmap->flush_all_requested= bitmap->waiting_for_flush_all_requested=
bitmap->waiting_for_non_flushable= 0 ;
bitmap->non_flushable= 0 ;
/* Update size for bits */
/* TODO; Make this dependent of the row size */
max_page_size= share->block_size - PAGE_OVERHEAD_SIZE(share) + DIR_ENTRY_SIZE;
bitmap->sizes[0 ]= max_page_size; /* Empty page */
bitmap->sizes[1 ]= max_page_size - max_page_size * 30 / 100 ;
bitmap->sizes[2 ]= max_page_size - max_page_size * 60 / 100 ;
bitmap->sizes[3 ]= max_page_size - max_page_size * 90 / 100 ;
bitmap->sizes[4 ]= 0 ; /* Full page */
bitmap->sizes[5 ]= max_page_size - max_page_size * 40 / 100 ;
bitmap->sizes[6 ]= max_page_size - max_page_size * 80 / 100 ;
bitmap->sizes[7 ]= 0 ;
/*
If a record size will fit into the smallest empty page , return first
found page in find_head ( )
*/
if (bitmap->sizes[3 ] >= share->base.max_pack_length)
bitmap->return_first_match= 1 ;
mysql_mutex_init(key_SHARE_BITMAP_lock,
&share->bitmap.bitmap_lock, MY_MUTEX_INIT_SLOW);
mysql_cond_init(key_SHARE_BITMAP_cond,
&share->bitmap.bitmap_cond, 0 );
first_bitmap_with_space= share->state.first_bitmap_with_space;
_ma_bitmap_reset_cache(share);
/*
The bitmap used to map the file are aligned on 6 bytes . We now
calculate the max file size that can be used by the bitmap . This
is needed to get ma_info ( ) give a true file size so that the user can
estimate if there is still space free for records in the file .
*/
{
pgcache_page_no_t last_bitmap_page;
ulong blocks, bytes;
last_bitmap_page= *last_page - *last_page % bitmap->pages_covered;
blocks= (ulong) (*last_page - last_bitmap_page);
bytes= (blocks * 3 ) / 8 ; /* 3 bit per page / 8 bits per byte */
/* Size needs to be aligned on 6 */
bytes/= 6 ;
bytes*= 6 ;
bitmap->last_bitmap_page= last_bitmap_page;
bitmap->last_total_size= (uint)bytes;
*last_page= ((last_bitmap_page + bytes*8 /3 ));
}
/* Restore first_bitmap_with_space if it's resonable */
if (first_bitmap_with_space <= (share->state.state.data_file_length /
share->block_size))
share->state.first_bitmap_with_space= first_bitmap_with_space;
return 0 ;
}
/*
Free data allocated by _ ma_bitmap_init
SYNOPSIS
_ ma_bitmap_end ( )
share Share handler
*/
my_bool _ma_bitmap_end(MARIA_SHARE *share)
{
my_bool res;
#ifndef DBUG_OFF
if (! share->internal_table)
mysql_mutex_assert_owner(&share->close_lock);
#endif
DBUG_ASSERT(share->bitmap.non_flushable == 0 );
DBUG_ASSERT(share->bitmap.flush_all_requested == 0 );
DBUG_ASSERT(share->bitmap.waiting_for_non_flushable == 0 &&
share->bitmap.waiting_for_flush_all_requested == 0 );
DBUG_ASSERT(share->bitmap.pinned_pages.elements == 0 );
res= _ma_bitmap_flush(share);
mysql_mutex_destroy(&share->bitmap.bitmap_lock);
mysql_cond_destroy(&share->bitmap.bitmap_cond);
delete_dynamic(&share->bitmap.pinned_pages);
my_free(share->bitmap.map);
share->bitmap.map= 0 ;
/*
This is to not get an assert in checkpoint . The bitmap will be flushed
at once by _ ma_once_end_block_record ( ) as part of the normal flush
of the kfile .
*/
share->bitmap.changed_not_flushed= 0 ;
return res;
}
/*
Ensure that we have incremented open count before we try to read / write
a page while we have the bitmap lock .
This is needed to ensure that we don ' t call _ ma_mark_file_changed ( ) as
part of flushing a page to disk , as this locks share - > internal_lock
and then mutex lock would happen in the wrong order .
*/
static inline void _ma_bitmap_mark_file_changed(MARIA_SHARE *share,
my_bool flush_translog)
{
/*
It ' s extremely unlikely that the following test is true as it
only happens once if the table has changed .
*/
if (unlikely(!share->global_changed &&
(share->state.changed & STATE_CHANGED)))
{
/* purecov: begin inspected */
/* unlock mutex as it can't be hold during _ma_mark_file_changed() */
mysql_mutex_unlock(&share->bitmap.bitmap_lock);
/*
We have to flush the translog to ensure we have registered that the
table is open .
*/
if (flush_translog && share->now_transactional)
(void ) translog_flush(share->state.logrec_file_id);
_ma_mark_file_changed_now(share);
mysql_mutex_lock(&share->bitmap.bitmap_lock);
/* purecov: end */
}
}
/*
Send updated bitmap to the page cache
SYNOPSIS
_ ma_bitmap_flush ( )
share Share handler
NOTES
In the future , _ ma_bitmap_flush ( ) will be called to flush changes don ' t
by this thread ( ie , checking the changed flag is ok ) . The reason we
check it again in the mutex is that if someone else did a flush at the
same time , we don ' t have to do the write .
This is also ok for _ ma_scan_init_block_record ( ) which does not want to
miss rows : it cares only for committed rows , that is , rows for which there
was a commit before our transaction started ; as commit and transaction ' s
start are protected by the same LOCK_trn_list mutex , we see memory at
least as new as at other transaction ' s commit time , so if the committed
rows caused bitmap - > changed to be true , we see it ; if we see 0 it really
means a flush happened since then . So , it ' s ok to read without bitmap ' s
mutex .
RETURN
0 ok
1 error
*/
my_bool _ma_bitmap_flush(MARIA_SHARE *share)
{
my_bool res= 0 ;
DBUG_ENTER("_ma_bitmap_flush" );
if (share->bitmap.changed)
{
mysql_mutex_lock(&share->bitmap.bitmap_lock);
if (share->bitmap.changed)
{
/*
We have to mark the file changed here , as otherwise the following
write to pagecache may force a page out from this file , which would
cause _ ma_mark_file_changed ( ) to be called with bitmaplock hold !
*/
_ma_bitmap_mark_file_changed(share, 1 );
res= write_changed_bitmap(share, &share->bitmap);
share->bitmap.changed= 0 ;
}
mysql_mutex_unlock(&share->bitmap.bitmap_lock);
}
DBUG_RETURN(res);
}
/**
Dirty - page filtering criteria for bitmap pages
@ param type Page ' s type
@ param pageno Page ' s number
@ param rec_lsn Page ' s rec_lsn
@ param arg pages_covered of bitmap
*/
static enum pagecache_flush_filter_result
filter_flush_bitmap_pages(enum pagecache_page_type type
__attribute__ ((unused)),
pgcache_page_no_t pageno,
LSN rec_lsn __attribute__ ((unused)),
void *arg)
{
return ((pageno % (*(ulong*)arg)) == 0 );
}
/**
Flushes current bitmap page to the pagecache , and then all bitmap pages
from pagecache to the file . Used by Checkpoint .
@ param share Table ' s share
*/
my_bool _ma_bitmap_flush_all(MARIA_SHARE *share)
{
my_bool res= 0 ;
uint send_signal= 0 ;
MARIA_FILE_BITMAP *bitmap= &share->bitmap;
DBUG_ENTER("_ma_bitmap_flush_all" );
#ifdef EXTRA_DEBUG_BITMAP
{
char buff[160 ];
uint len= my_sprintf(buff,
(buff, "bitmap_flush: fd: %d id: %u "
"changed: %d changed_not_flushed: %d "
"flush_all_requested: %d" ,
share->bitmap.file.file,
share->id,
bitmap->changed,
bitmap->changed_not_flushed,
bitmap->flush_all_requested));
(void ) translog_log_debug_info(0 , LOGREC_DEBUG_INFO_QUERY,
(uchar*) buff, len);
}
#endif
mysql_mutex_lock(&bitmap->bitmap_lock);
if (!bitmap->changed && !bitmap->changed_not_flushed)
{
mysql_mutex_unlock(&bitmap->bitmap_lock);
DBUG_RETURN(0 );
}
_ma_bitmap_mark_file_changed(share, 0 );
/*
The following should be true as it was tested above . We have to test
this again as _ ma_bitmap_mark_file_changed ( ) did temporarly release
the bitmap mutex .
*/
if (bitmap->changed || bitmap->changed_not_flushed)
{
bitmap->flush_all_requested++;
bitmap->waiting_for_non_flushable++;
#if !WRONG_BITMAP_FLUSH
while (bitmap->non_flushable > 0 )
{
DBUG_PRINT("info" , ("waiting for bitmap to be flushable" ));
mysql_cond_wait(&bitmap->bitmap_cond, &bitmap->bitmap_lock);
}
#endif
bitmap->waiting_for_non_flushable--;
#ifdef EXTRA_DEBUG_BITMAP
{
char tmp[MAX_BITMAP_INFO_LENGTH];
size_t len;
len= _ma_get_bitmap_description(bitmap, bitmap->map, bitmap->page, tmp,
sizeof (tmp));
(void ) translog_log_debug_info(0 , LOGREC_DEBUG_INFO_QUERY,
(uchar*) tmp, len);
}
#endif
DBUG_ASSERT(bitmap->flush_all_requested == 1 );
/*
Bitmap is in a flushable state : its contents in memory are reflected by
log records ( complete REDO - UNDO groups ) and all bitmap pages are
unpinned . We keep the mutex to preserve this situation , and flush to the
file .
*/
if (bitmap->changed)
{
bitmap->changed= FALSE ;
res= write_changed_bitmap(share, bitmap);
}
/*
We do NOT use FLUSH_KEEP_LAZY because we must be sure that bitmap
pages have been flushed . That ' s a condition of correctness of
Recovery : data pages may have been all flushed , if we write the
checkpoint record Recovery will start from after their REDOs . If
bitmap page was not flushed , as the REDOs about it will be skipped , it
will wrongly not be recovered . If bitmap pages had a rec_lsn it would
be different .
There should be no pinned pages as bitmap - > non_flushable = = 0 .
*/
if (flush_pagecache_blocks_with_filter(bitmap->file.pagecache,
&bitmap->file, FLUSH_KEEP,
filter_flush_bitmap_pages,
&bitmap->pages_covered) &
PCFLUSH_PINNED_AND_ERROR)
res= TRUE ;
bitmap->changed_not_flushed= FALSE ;
bitmap->flush_all_requested--;
/*
Some well - behaved threads may be waiting for flush_all_requested to
become false , wake them up .
*/
DBUG_PRINT("info" , ("bitmap flusher waking up others" ));
send_signal= (bitmap->waiting_for_flush_all_requested |
bitmap->waiting_for_non_flushable);
}
mysql_mutex_unlock(&bitmap->bitmap_lock);
if (send_signal)
mysql_cond_broadcast(&bitmap->bitmap_cond);
DBUG_RETURN(res);
}
/**
@ brief Lock bitmap from being used by another thread
@ fn _ ma_bitmap_lock ( )
@ param share Table ' s share
@ notes
This is a temporary solution for allowing someone to delete an inserted
duplicate - key row while someone else is doing concurrent inserts .
This is ok for now as duplicate key errors are not that common .
In the future we will add locks for row - pages to ensure two threads doesn ' t
work at the same time on the same page .
*/
void _ma_bitmap_lock(MARIA_SHARE *share)
{
MARIA_FILE_BITMAP *bitmap= &share->bitmap;
DBUG_ENTER("_ma_bitmap_lock" );
if (!share->now_transactional)
DBUG_VOID_RETURN;
mysql_mutex_lock(&bitmap->bitmap_lock);
bitmap->flush_all_requested++;
bitmap->waiting_for_non_flushable++;
while (bitmap->non_flushable)
{
DBUG_PRINT("info" , ("waiting for bitmap to be flushable" ));
mysql_cond_wait(&bitmap->bitmap_cond, &bitmap->bitmap_lock);
}
bitmap->waiting_for_non_flushable--;
/*
Ensure that _ ma_bitmap_flush_all ( ) and _ ma_bitmap_lock ( ) are blocked .
ma_bitmap_flushable ( ) is blocked thanks to ' flush_all_requested ' .
*/
bitmap->non_flushable= 1 ;
mysql_mutex_unlock(&bitmap->bitmap_lock);
DBUG_VOID_RETURN;
}
/**
@ brief Unlock bitmap after _ ma_bitmap_lock ( )
@ fn _ ma_bitmap_unlock ( )
@ param share Table ' s share
*/
void _ma_bitmap_unlock(MARIA_SHARE *share)
{
MARIA_FILE_BITMAP *bitmap= &share->bitmap;
uint send_signal;
DBUG_ENTER("_ma_bitmap_unlock" );
if (!share->now_transactional)
DBUG_VOID_RETURN;
DBUG_ASSERT(bitmap->flush_all_requested > 0 && bitmap->non_flushable == 1 );
mysql_mutex_lock(&bitmap->bitmap_lock);
bitmap->non_flushable= 0 ;
_ma_bitmap_unpin_all(share);
send_signal= bitmap->waiting_for_non_flushable;
if (!--bitmap->flush_all_requested)
send_signal|= bitmap->waiting_for_flush_all_requested;
mysql_mutex_unlock(&bitmap->bitmap_lock);
if (send_signal)
mysql_cond_broadcast(&bitmap->bitmap_cond);
DBUG_VOID_RETURN;
}
/**
@ brief Unpin all pinned bitmap pages
@ param share Table ' s share
@ return Operation status
@ retval 0 ok
@ note This unpins pages pinned by other threads .
*/
static void _ma_bitmap_unpin_all(MARIA_SHARE *share)
{
MARIA_FILE_BITMAP *bitmap= &share->bitmap;
MARIA_PINNED_PAGE *page_link= ((MARIA_PINNED_PAGE*)
dynamic_array_ptr(&bitmap->pinned_pages, 0 ));
MARIA_PINNED_PAGE *pinned_page= page_link + bitmap->pinned_pages.elements;
DBUG_ENTER("_ma_bitmap_unpin_all" );
DBUG_PRINT("info" , ("pinned: %zu" , bitmap->pinned_pages.elements));
while (pinned_page-- != page_link)
pagecache_unlock_by_link(share->pagecache, pinned_page->link,
pinned_page->unlock, PAGECACHE_UNPIN,
LSN_IMPOSSIBLE, LSN_IMPOSSIBLE, FALSE , TRUE );
bitmap->pinned_pages.elements= 0 ;
DBUG_VOID_RETURN;
}
/*
Intialize bitmap in memory to a zero bitmap
SYNOPSIS
_ ma_bitmap_delete_all ( )
share Share handler
NOTES
This is called on maria_delete_all_rows ( truncate data file ) .
*/
void _ma_bitmap_delete_all(MARIA_SHARE *share)
{
MARIA_FILE_BITMAP *bitmap= &share->bitmap;
DBUG_ENTER("_ma_bitmap_delete_all" );
if (bitmap->map) /* Not in create */
{
bzero(bitmap->map, bitmap->block_size);
bitmap->changed= 1 ;
bitmap->page= 0 ;
bitmap->used_size= bitmap->full_tail_size= bitmap->full_head_size= 0 ;
bitmap->total_size= bitmap->max_total_size;
}
DBUG_VOID_RETURN;
}
/**
@ brief Reset bitmap caches
@ fn _ ma_bitmap_reset_cache ( )
@ param share Maria share
@ notes
This is called after we have swapped file descriptors and we want
bitmap to forget all cached information .
It ' s also called directly after we have opened a file .
*/
void _ma_bitmap_reset_cache(MARIA_SHARE *share)
{
MARIA_FILE_BITMAP *bitmap= &share->bitmap;
if (bitmap->map) /* If using bitmap */
{
/* Forget changes in current bitmap page */
bitmap->changed= 0 ;
/*
We can ' t read a page yet , as in some case we don ' t have an active
page cache yet .
Pretend we have a dummy , full and not changed bitmap page in memory .
We set bitmap - > page to a value so that if we use it in
move_to_next_bitmap ( ) it will point to page 0 .
( This can only happen if writing to a bitmap page fails )
*/
bitmap->page= ((pgcache_page_no_t) 0 ) - bitmap->pages_covered;
bitmap->used_size= bitmap->total_size= bitmap->max_total_size;
bitmap->full_head_size= bitmap->full_tail_size= bitmap->max_total_size;
bfill(bitmap->map, share->block_size, 255 );
#ifndef DBUG_OFF
memcpy(bitmap->map + bitmap->block_size, bitmap->map, bitmap->block_size);
#endif
/* Start scanning for free space from start of file */
share->state.first_bitmap_with_space = 0 ;
}
}
/*
Return bitmap pattern for the smallest head block that can hold ' size '
SYNOPSIS
size_to_head_pattern ( )
bitmap Bitmap
size Requested size
RETURN
0 - 3 For a description of the bitmap sizes , see the header
*/
static uint size_to_head_pattern(MARIA_FILE_BITMAP *bitmap, uint size)
{
if (size <= bitmap->sizes[3 ])
return 3 ;
if (size <= bitmap->sizes[2 ])
return 2 ;
if (size <= bitmap->sizes[1 ])
return 1 ;
DBUG_ASSERT(size <= bitmap->sizes[0 ]);
return 0 ;
}
/*
Return bitmap pattern for head block where there is size bytes free
SYNOPSIS
_ ma_free_size_to_head_pattern ( )
bitmap Bitmap
size Requested size
RETURN
0 - 4 ( Possible bitmap patterns for head block )
*/
uint _ma_free_size_to_head_pattern(MARIA_FILE_BITMAP *bitmap, uint size)
{
if (size < bitmap->sizes[3 ])
return 4 ;
if (size < bitmap->sizes[2 ])
return 3 ;
if (size < bitmap->sizes[1 ])
return 2 ;
return (size < bitmap->sizes[0 ]) ? 1 : 0 ;
}
/*
Return bitmap pattern for the smallest tail block that can hold ' size '
SYNOPSIS
size_to_tail_pattern ( )
bitmap Bitmap
size Requested size
RETURN
0 , 5 or 6 For a description of the bitmap sizes , see the header
*/
static uint size_to_tail_pattern(MARIA_FILE_BITMAP *bitmap, uint size)
{
if (size <= bitmap->sizes[6 ])
return 6 ;
if (size <= bitmap->sizes[5 ])
return 5 ;
DBUG_ASSERT(size <= bitmap->sizes[0 ]);
return 0 ;
}
/*
Return bitmap pattern for tail block where there is size bytes free
SYNOPSIS
free_size_to_tail_pattern ( )
bitmap Bitmap
size Requested size
RETURN
0 , 5 , 6 , 7 For a description of the bitmap sizes , see the header
*/
static uint free_size_to_tail_pattern(MARIA_FILE_BITMAP *bitmap, uint size)
{
if (size >= bitmap->sizes[0 ])
return 0 ; /* Revert to empty page */
if (size < bitmap->sizes[6 ])
return 7 ;
if (size < bitmap->sizes[5 ])
return 6 ;
return 5 ;
}
/*
Return size guranteed to be available on a page
SYNOPSIS
pattern_to_head_size ( )
bitmap Bitmap
pattern Pattern ( 0 - 7 )
RETURN
0 - block_size
*/
static inline uint pattern_to_size(MARIA_FILE_BITMAP *bitmap, uint pattern)
{
DBUG_ASSERT(pattern <= 7 );
return bitmap->sizes[pattern];
}
/*
Print bitmap for debugging
SYNOPSIS
_ ma_print_bitmap_changes ( )
bitmap Bitmap to print
IMPLEMENTATION
Prints all changed bits since last call to _ ma_print_bitmap ( ) .
This is done by having a copy of the last bitmap in
bitmap - > map + bitmap - > block_size .
*/
#ifndef DBUG_OFF
static void _ma_print_bitmap_changes(MARIA_FILE_BITMAP *bitmap)
{
uchar *pos, *end, *org_pos;
ulong page;
DBUG_ENTER("_ma_print_bitmap_changes" );
end= bitmap->map + bitmap->used_size;
DBUG_LOCK_FILE;
fprintf(DBUG_FILE,"\nBitmap page changes at page: %lu bitmap: %p\n" ,
(ulong) bitmap->page, bitmap->map);
page= (ulong) bitmap->page+1 ;
for (pos= bitmap->map, org_pos= bitmap->map + bitmap->block_size ;
pos < end ;
pos+= 6 , org_pos+= 6 )
{
ulonglong bits= uint6korr(pos); /* 6 bytes = 6*8/3= 16 patterns */
ulonglong org_bits= uint6korr(org_pos);
uint i;
/*
Test if there is any changes in the next 16 bitmaps ( to not have to
loop through all bits if we know they are the same )
*/
if (bits != org_bits)
{
for (i= 0 ; i < 16 ; i++, bits>>= 3 , org_bits>>= 3 )
{
if ((bits & 7 ) != (org_bits & 7 ))
fprintf(DBUG_FILE, "Page: %8lu %s -> %s\n" , page+i,
bits_to_txt[org_bits & 7 ], bits_to_txt[bits & 7 ]);
}
}
page+= 16 ;
}
fputc('\n' , DBUG_FILE);
DBUG_UNLOCK_FILE;
memcpy(bitmap->map + bitmap->block_size, bitmap->map, bitmap->block_size);
DBUG_VOID_RETURN;
}
/* Print content of bitmap for debugging */
void _ma_print_bitmap(MARIA_FILE_BITMAP *bitmap, uchar *data,
pgcache_page_no_t page)
{
uchar *pos, *end;
char llbuff[22 ];
DBUG_LOCK_FILE;
fprintf(DBUG_FILE,"\nDump of bitmap page at %s\n" , llstr(page, llbuff));
page++; /* Skip bitmap page */
for (pos= data, end= pos + bitmap->max_total_size;
pos < end ;
pos+= 6 )
{
ulonglong bits= uint6korr(pos); /* 6 bytes = 6*8/3= 16 patterns */
/*
Test if there is any changes in the next 16 bitmaps ( to not have to
loop through all bits if we know they are the same )
*/
if (bits)
{
uint i;
for (i= 0 ; i < 16 ; i++, bits>>= 3 )
{
if (bits & 7 )
fprintf(DBUG_FILE, "Page: %8s %s\n" , llstr(page+i, llbuff),
bits_to_txt[bits & 7 ]);
}
}
page+= 16 ;
}
fputc('\n' , DBUG_FILE);
DBUG_UNLOCK_FILE;
}
#endif /* DBUG_OFF */
/*
Return content of bitmap as a printable string
*/
size_t _ma_get_bitmap_description(MARIA_FILE_BITMAP *bitmap,
uchar *bitmap_data,
pgcache_page_no_t page,
char *out, size_t out_size)
{
uchar *pos, *end;
char *out_end= out + out_size;
size_t count=0 , dot_printed= 0 , len;
char buff[80 ], last[80 ];
page++;
last[0 ]=0 ;
for (pos= bitmap_data, end= pos+ bitmap->used_size ; pos < end ; pos+= 6 )
{
ulonglong bits= uint6korr(pos); /* 6 bytes = 6*8/3= 16 patterns */
uint i;
for (i= 0 ; i < 16 ; i++, bits>>= 3 )
{
if (count > 60 )
{
if (memcmp(buff, last, count))
{
memcpy(last, buff, count);
len= snprintf(out, out_end - out, "%8lu: " , (ulong) (page - count));
memcpy(out+len, buff, count);
out+= len + count + 1 ;
out[-1 ]= '\n' ;
dot_printed= 0 ;
}
else if (!(dot_printed++))
{
out= strmov(out, "...\n" );
}
count= 0 ;
}
buff[count++]= '0' + (uint) (bits & 7 );
page++;
}
}
len= snprintf(out, out_end - out, "%8lu: " , (ulong) (page - count));
memcpy(out+len, buff, count);
out[len + count]= '\n' ;
out[len + count + 1 ]= 0 ;
return len + count + 1 ;
}
/*
Adjust bitmap - > total_size to not go over max_data_file_size
*/
static void adjust_total_size(MARIA_HA *info, pgcache_page_no_t page)
{
MARIA_FILE_BITMAP *bitmap= &info->s->bitmap;
if (page < bitmap->last_bitmap_page)
bitmap->total_size= bitmap->max_total_size; /* Use all bits in bitmap */
else
bitmap->total_size= bitmap->last_total_size;
}
/***************************************************************************
Reading & writing bitmap pages
***************************************************************************/
/*
Read a given bitmap page
SYNOPSIS
_ ma_read_bitmap_page ( )
info Maria handler
bitmap Bitmap handler
page Page to read
NOTE
We don ' t always have share - > bitmap . bitmap_lock here
( when called from_ma_check_bitmap_data ( ) for example ) .
RETURN
0 ok
1 error ( Error writing old bitmap or reading bitmap page )
*/
static my_bool _ma_read_bitmap_page(MARIA_HA *info,
MARIA_FILE_BITMAP *bitmap,
pgcache_page_no_t page)
{
MARIA_SHARE *share= info->s;
my_bool res;
DBUG_ENTER("_ma_read_bitmap_page" );
DBUG_PRINT("enter" , ("page: %lld data_file_length: %lld" ,
(longlong) page,
(longlong) share->state.state.data_file_length));
DBUG_ASSERT(page % bitmap->pages_covered == 0 );
DBUG_ASSERT(!bitmap->changed);
bitmap->page= page;
if ((page + 1 ) * bitmap->block_size > share->state.state.data_file_length)
{
/* Inexistent or half-created page */
res= _ma_bitmap_create_missing(info, bitmap, page);
if (!res)
adjust_total_size(info, page);
DBUG_RETURN(res);
}
adjust_total_size(info, page);
bitmap->full_head_size= bitmap->full_tail_size= 0 ;
DBUG_ASSERT(share->pagecache->block_size == bitmap->block_size);
if (share->internal_table &&
page == 0 && share->state.state.data_file_length == bitmap->block_size)
{
/* Avoid read of bitmap for internal temporary tables */
bzero(bitmap->map, bitmap->block_size);
bitmap->used_size= 0 ;
res= 0 ;
}
else
{
res= pagecache_read(bitmap->file.pagecache,
&bitmap->file, page, 0 ,
bitmap->map, PAGECACHE_PLAIN_PAGE,
PAGECACHE_LOCK_LEFT_UNLOCKED, 0 ) == NULL;
if (!res)
{
/* Calculate used_size */
const uchar *data, *end= bitmap->map;
for (data= bitmap->map + bitmap->total_size; --data >= end && *data == 0 ; )
{}
bitmap->used_size= (uint) ((data + 1 ) - end);
DBUG_ASSERT(bitmap->used_size <= bitmap->total_size);
}
}
/*
We can ' t check maria_bitmap_marker here as if the bitmap page
previously had a true checksum and the user switched mode to not checksum
this may have any value , except maria_normal_page_marker .
Using maria_normal_page_marker gives us a protection against bugs
when running without any checksums .
*/
#ifndef DBUG_OFF
if (!res)
{
memcpy(bitmap->map + bitmap->block_size, bitmap->map, bitmap->block_size);
_ma_check_bitmap(bitmap);
}
#endif
DBUG_RETURN(res);
}
/*
Change to another bitmap page
SYNOPSIS
_ ma_change_bitmap_page ( )
info Maria handler
bitmap Bitmap handler
page Bitmap page to read
NOTES
If old bitmap was changed , write it out before reading new one
We return empty bitmap if page is outside of file size
RETURN
0 ok
1 error ( Error writing old bitmap or reading bitmap page )
*/
static my_bool _ma_change_bitmap_page(MARIA_HA *info,
MARIA_FILE_BITMAP *bitmap,
pgcache_page_no_t page)
{
DBUG_ENTER("_ma_change_bitmap_page" );
_ma_check_bitmap(bitmap);
/*
We have to mark the file changed here , as otherwise the following
read / write to pagecache may force a page out from this file , which would
cause _ ma_mark_file_changed ( ) to be called with bitmaplock hold !
*/
_ma_bitmap_mark_file_changed(info->s, 1 );
if (bitmap->changed)
{
if (write_changed_bitmap(info->s, bitmap))
DBUG_RETURN(1 );
bitmap->changed= 0 ;
}
DBUG_RETURN(_ma_read_bitmap_page(info, bitmap, page));
}
/*
Read next suitable bitmap
SYNOPSIS
move_to_next_bitmap ( )
bitmap Bitmap handle
NOTES
The found bitmap may be full , so calling function may need to call this
repeatedly until it finds enough space .
TODO
Add cache of bitmaps to not read something that is not usable
RETURN
0 ok
1 error ( either couldn ' t save old bitmap or read new one )
*/
static my_bool move_to_next_bitmap(MARIA_HA *info, MARIA_FILE_BITMAP *bitmap)
{
pgcache_page_no_t page= bitmap->page;
MARIA_STATE_INFO *state= &info->s->state;
my_bool res;
DBUG_ENTER("move_to_next_bitmap" );
if (state->first_bitmap_with_space != ~(pgcache_page_no_t) 0 &&
state->first_bitmap_with_space != page)
{
page= state->first_bitmap_with_space;
state->first_bitmap_with_space= ~(pgcache_page_no_t) 0 ;
DBUG_ASSERT(page % bitmap->pages_covered == 0 );
}
else
{
page+= bitmap->pages_covered;
DBUG_ASSERT(page % bitmap->pages_covered == 0 );
}
res= _ma_change_bitmap_page(info, bitmap, page);
DBUG_RETURN(res);
}
/****************************************************************************
Allocate data in bitmaps
****************************************************************************/
/*
Store data in ' block ' and mark the place used in the bitmap
SYNOPSIS
fill_block ( )
bitmap Bitmap handle
block Store data about what we found
best_data Pointer to best 6 uchar aligned area in bitmap - > map
best_pos Which bit in * best_data the area starts
0 = first bit pattern , 1 second bit pattern etc
best_bits The original value of the bits at best_pos
fill_pattern Bitmap pattern to store in best_data [ best_pos ]
NOTES
We mark all pages to be ' TAIL ' s , which means that
block - > page_count is really a row position inside the page .
*/
static void fill_block(MARIA_FILE_BITMAP *bitmap,
MARIA_BITMAP_BLOCK *block,
uchar *best_data, uint best_pos, uint best_bits,
uint fill_pattern)
{
uint page, offset, tmp;
uchar *data;
DBUG_ENTER("fill_block" );
/* For each 6 bytes we have 6*8/3= 16 patterns */
page= ((uint) (best_data - bitmap->map)) / 6 * 16 + best_pos;
DBUG_ASSERT(page + 1 < bitmap->pages_covered);
block->page= bitmap->page + 1 + page;
block->page_count= TAIL_PAGE_COUNT_MARKER;
block->empty_space= pattern_to_size(bitmap, best_bits);
block->sub_blocks= 0 ;
block->org_bitmap_value= best_bits;
block->used= BLOCKUSED_TAIL; /* See _ma_bitmap_release_unused() */
/*
Mark place used by reading / writing 2 bytes at a time to handle
bitmaps in overlapping bytes
*/
best_pos*= 3 ;
data= best_data+ best_pos / 8 ;
offset= best_pos & 7 ;
tmp= uint2korr(data);
/* we turn off the 3 bits and replace them with fill_pattern */
tmp= (tmp & ~(7 << offset)) | (fill_pattern << offset);
int2store(data, tmp);
bitmap->changed= 1 ;
DBUG_EXECUTE("bitmap" , _ma_print_bitmap_changes(bitmap););
DBUG_VOID_RETURN;
}
/*
Allocate data for head block
SYNOPSIS
allocate_head ( )
bitmap bitmap
size Size of data region we need to store
block Store found information here
IMPLEMENTATION
Find the best - fit page to put a region of ' size '
This is defined as the first page of the set of pages
with the smallest free space that can hold ' size ' .
NOTES
Updates bitmap - > full_head_size while scanning data
RETURN
0 ok ( block is updated )
1 error ( no space in bitmap ; block is not touched )
*/
static my_bool allocate_head(MARIA_FILE_BITMAP *bitmap, uint size,
MARIA_BITMAP_BLOCK *block)
{
uint min_bits= size_to_head_pattern(bitmap, size);
uchar *data, *end;
uchar *best_data= 0 ;
uint best_bits= (uint) -1 , UNINIT_VAR(best_pos);
my_bool first_pattern= 0 ; /* if doing insert_order */
my_bool first_found= 1 ;
MARIA_SHARE *share= bitmap->share;
my_bool insert_order=
MY_TEST(share->base.extra_options & MA_EXTRA_OPTIONS_INSERT_ORDER);
DBUG_ENTER("allocate_head" );
DBUG_ASSERT(size <= FULL_PAGE_SIZE(share));
end= bitmap->map + bitmap->used_size;
if (insert_order && bitmap->page == share->last_insert_bitmap)
{
uint last_insert_page= share->last_insert_page;
uint byte= 6 * (last_insert_page / 16 );
first_pattern= last_insert_page % 16 ;
data= bitmap->map+byte;
first_found= 0 ; /* Don't update full_head_size */
DBUG_ASSERT(data <= end);
}
else
data= bitmap->map + (bitmap->full_head_size/6 )*6 ;
for (; data < end; data+= 6 , first_pattern= 0 )
{
ulonglong bits= uint6korr(data); /* 6 bytes = 6*8/3= 16 patterns */
uint i;
/*
Skip common patterns
We can skip empty pages ( if we already found a match ) or
anything matching the following pattern as this will be either
a full page or a tail page
*/
if ((!bits && best_data) ||
((bits & 04444444444444444 LL) == 04444444444444444 LL))
continue ;
for (i= first_pattern, bits >>= (3 * first_pattern); i < 16 ;
i++, bits >>= 3 )
{
uint pattern= (uint) (bits & 7 );
if (pattern <= 3 ) /* Room for more data */
{
if (first_found)
{
first_found= 0 ;
bitmap->full_head_size= (uint)(data - bitmap->map);
}
}
if (pattern <= min_bits)
{
/* There is enough space here, check if we have found better */
if ((int ) pattern > (int ) best_bits)
{
/*
There is more than enough space here and it ' s better than what
we have found so far . Remember it , as we will choose it if we
don ' t find anything in this bitmap page .
*/
best_bits= pattern;
best_data= data;
best_pos= i;
if (pattern == min_bits || bitmap->return_first_match)
goto found; /* Best possible match */
}
}
}
}
if (!best_data) /* Found no place */
{
if (data >= bitmap->map + bitmap->total_size)
DBUG_RETURN(1 ); /* No space in bitmap */
DBUG_ASSERT(uint6korr(data) == 0 );
/* Allocate data at end of bitmap */
bitmap->used_size= (uint) (data - bitmap->map) + 6 ;
best_data= data;
best_pos= best_bits= 0 ;
}
else
{
/*
This is not strictly needed as used_size should be alligned on 6 ,
but for easier debugging lets try to keep it more accurate
*/
uint position= (uint) (best_data - bitmap->map) + 6 ;
set_if_bigger(bitmap->used_size, position);
}
DBUG_ASSERT(bitmap->used_size <= bitmap->total_size);
found:
if (insert_order)
{
share->last_insert_page=
((uint) (best_data - bitmap->map)) / 6 * 16 + best_pos;
share->last_insert_bitmap= bitmap->page;
}
fill_block(bitmap, block, best_data, best_pos, best_bits, FULL_HEAD_PAGE);
DBUG_RETURN(0 );
}
/*
Allocate data for tail block
SYNOPSIS
allocate_tail ( )
bitmap bitmap
size Size of block we need to find
block Store found information here
RETURN
0 ok ( block is updated )
1 error ( no space in bitmap ; block is not touched )
*/
static my_bool allocate_tail(MARIA_FILE_BITMAP *bitmap, uint size,
MARIA_BITMAP_BLOCK *block)
{
uint min_bits= size_to_tail_pattern(bitmap, size);
uchar *data, *end, *best_data= 0 ;
my_bool first_found= 1 ;
uint best_bits= (uint) -1 , UNINIT_VAR(best_pos);
DBUG_ENTER("allocate_tail" );
DBUG_PRINT("enter" , ("size: %u" , size));
data= bitmap->map + (bitmap->full_tail_size/6 )*6 ;
end= bitmap->map + bitmap->used_size;
/*
We have to add DIR_ENTRY_SIZE here as this is not part of the data size
See call to allocate_tail ( ) in find_tail ( ) .
*/
DBUG_ASSERT(size <= MAX_TAIL_SIZE(bitmap->block_size) + DIR_ENTRY_SIZE);
for (; data < end; data += 6 )
{
ulonglong bits= uint6korr(data); /* 6 bytes = 6*8/3= 16 patterns */
uint i;
/*
Skip common patterns
We can skip empty pages ( if we already found a match ) or
the following patterns : 1 - 4 ( head pages , not suitable for tail ) or
7 ( full tail page ) . See ' Dynamic size records ' comment at start of file .
At the moment we only skip full head and tail pages ( ie , all bits are
set ) as this is easy to detect with one simple test and is a
quite common case if we have blobs .
*/
if ((!bits && best_data) || bits == 0 xffffffffffffLL ||
bits == 04444444444444444 LL)
continue ;
for (i= 0 ; i < 16 ; i++, bits >>= 3 )
{
uint pattern= (uint) (bits & 7 );
if (pattern == 0 ||
(pattern > FULL_HEAD_PAGE && pattern < FULL_TAIL_PAGE))
{
/* There is room for tail data */
if (first_found)
{
first_found= 0 ;
bitmap->full_tail_size= (uint)(data - bitmap->map);
}
}
if (pattern <= min_bits && (!pattern || pattern > FULL_HEAD_PAGE))
{
if ((int ) pattern > (int ) best_bits)
{
best_bits= pattern;
best_data= data;
best_pos= i;
if (pattern == min_bits)
goto found; /* Can't be better */
}
}
}
}
if (!best_data)
{
if (data >= bitmap->map + bitmap->total_size)
DBUG_RETURN(1 );
DBUG_ASSERT(uint6korr(data) == 0 );
/* Allocate data at end of bitmap */
best_data= data;
bitmap->used_size= (uint) (data - bitmap->map) + 6 ;
DBUG_ASSERT(bitmap->used_size <= bitmap->total_size);
best_pos= best_bits= 0 ;
}
found:
fill_block(bitmap, block, best_data, best_pos, best_bits, FULL_TAIL_PAGE);
DBUG_RETURN(0 );
}
/*
Allocate data for full blocks
SYNOPSIS
allocate_full_pages ( )
bitmap bitmap
pages_needed Total size in pages ( bitmap - > total_size ) we would like to have
block Store found information here
full_page 1 if we are not allowed to split extent
IMPLEMENTATION
We will return the smallest area > = size . If there is no such
block , we will return the biggest area that satisfies
area_size > = MY_MIN ( BLOB_SEGMENT_MIN_SIZE * full_page_size , size )
To speed up searches , we will only consider areas that has at least 16 free
pages starting on an even boundary . When finding such an area , we will
extend it with all previous and following free pages . This will ensure
we don ' t get holes between areas
RETURN
# Blocks used
0 error ( no space in bitmap ; block is not touched )
*/
static ulong allocate_full_pages(MARIA_FILE_BITMAP *bitmap,
ulong pages_needed,
MARIA_BITMAP_BLOCK *block, my_bool full_page)
{
uchar *data, *data_end, *page_end;
uchar *best_data= 0 ;
uint min_size;
uint best_area_size, UNINIT_VAR(best_prefix_area_size);
uint page, size;
ulonglong UNINIT_VAR(best_prefix_bits);
DBUG_ENTER("allocate_full_pages" );
DBUG_PRINT("enter" , ("pages_needed: %lu" , pages_needed));
min_size= pages_needed;
if (!full_page && min_size > BLOB_SEGMENT_MIN_SIZE)
min_size= BLOB_SEGMENT_MIN_SIZE;
best_area_size= ~(uint) 0 ;
data= bitmap->map + (bitmap->full_head_size/6 )*6 ;
data_end= bitmap->map + bitmap->used_size;
page_end= bitmap->map + bitmap->total_size;
for (; data < page_end; data+= 6 )
{
ulonglong bits= uint6korr(data); /* 6 bytes = 6*8/3= 16 patterns */
uchar *data_start;
ulonglong prefix_bits= 0 ;
uint area_size, prefix_area_size, suffix_area_size;
/* Find area with at least 16 free pages */
if (bits)
continue ;
data_start= data;
/* Find size of area */
for (data+=6 ; data < data_end ; data+= 6 )
{
if ((bits= uint6korr(data)))
break ;
}
/*
Check if we are end of bitmap . In this case we know that
the rest of the bitmap is usable
*/
if (data >= data_end)
data= page_end;
area_size= (uint) (data - data_start) / 6 * 16 ;
if (area_size >= best_area_size)
continue ;
prefix_area_size= suffix_area_size= 0 ;
if (!bits)
{
/*
End of page ; All the rest of the bits on page are part of area
This is needed because bitmap - > used_size only covers the set bits
in the bitmap .
*/
area_size+= (uint) (page_end - data) / 6 * 16 ;
if (area_size >= best_area_size)
break ;
data= page_end;
}
else
{
/* Add bits at end of page */
for (; !(bits & 7 ); bits >>= 3 )
suffix_area_size++;
area_size+= suffix_area_size;
}
if (data_start != bitmap->map)
{
/* Add bits before page */
bits= prefix_bits= uint6korr(data_start - 6 );
DBUG_ASSERT(bits != 0 );
/* 111 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 */
if (!(bits & 07000000000000000 LL))
{
data_start-= 6 ;
do
{
prefix_area_size++;
bits<<= 3 ;
} while (!(bits & 07000000000000000 LL));
area_size+= prefix_area_size;
/* Calculate offset to page from data_start */
prefix_area_size= 16 - prefix_area_size;
}
}
if (area_size >= min_size && area_size <= best_area_size)
{
best_data= data_start;
best_area_size= area_size;
best_prefix_bits= prefix_bits;
best_prefix_area_size= prefix_area_size;
/* Prefer to put data in biggest possible area */
if (area_size <= pages_needed)
min_size= area_size;
else
min_size= pages_needed;
}
}
if (!best_data)
DBUG_RETURN(0 ); /* No room on page */
/*
Now allocate MY_MIN ( pages_needed , area_size ) , starting from
best_start + best_prefix_area_size
*/
if (best_area_size > pages_needed)
best_area_size= pages_needed;
/* For each 6 bytes we have 6*8/3= 16 patterns */
page= ((uint) (best_data - bitmap->map) * 8 ) / 3 + best_prefix_area_size;
block->page= bitmap->page + 1 + page;
block->page_count= best_area_size;
block->empty_space= 0 ;
block->sub_blocks= 0 ;
block->org_bitmap_value= 0 ;
block->used= 0 ;
DBUG_ASSERT(page + best_area_size < bitmap->pages_covered);
DBUG_PRINT("info" , ("page: %lu page_count: %u" ,
(ulong) block->page, block->page_count));
if (best_prefix_area_size)
{
ulonglong tmp;
/* Convert offset back to bits */
best_prefix_area_size= 16 - best_prefix_area_size;
if (best_area_size < best_prefix_area_size)
{
tmp= (1 LL << best_area_size*3 ) - 1 ;
best_area_size= best_prefix_area_size; /* for easy end test */
}
else
tmp= (1 LL << best_prefix_area_size*3 ) - 1 ;
tmp<<= (16 - best_prefix_area_size) * 3 ;
DBUG_ASSERT((best_prefix_bits & tmp) == 0 );
best_prefix_bits|= tmp;
int6store(best_data, best_prefix_bits);
if (!(best_area_size-= best_prefix_area_size))
goto end;
best_data+= 6 ;
}
best_area_size*= 3 ; /* Bits to set */
size= best_area_size/8 ; /* Bytes to set */
bfill(best_data, size, 255 );
best_data+= size;
if ((best_area_size-= size * 8 ))
{
/* fill last uchar */
*best_data|= (uchar) ((1 << best_area_size) -1 );
best_data++;
}
if (data_end < best_data)
{
bitmap->used_size= (uint) (best_data - bitmap->map);
DBUG_ASSERT(bitmap->used_size <= bitmap->total_size);
}
end:
bitmap->changed= 1 ;
DBUG_EXECUTE("bitmap" , _ma_print_bitmap_changes(bitmap););
DBUG_RETURN(block->page_count);
}
/****************************************************************************
Find right bitmaps where to store data
****************************************************************************/
/*
Find right bitmap and position for head block
SYNOPSIS
find_head ( )
info Maria handler
length Size of data region we need store
position Position in bitmap_blocks where to store the
information for the head block .
RETURN
0 ok
1 error
*/
static my_bool find_head(MARIA_HA *info, uint length, uint position)
{
MARIA_FILE_BITMAP *bitmap= &info->s->bitmap;
MARIA_BITMAP_BLOCK *block;
/*
There is always place for the head block in bitmap_blocks as these are
preallocated at _ ma_init_block_record ( ) .
*/
block= dynamic_element(&info->bitmap_blocks, position, MARIA_BITMAP_BLOCK *);
if (info->s->base.extra_options & MA_EXTRA_OPTIONS_INSERT_ORDER)
{
if (bitmap->page != info->s->last_insert_bitmap &&
_ma_change_bitmap_page(info, bitmap,
info->s->last_insert_bitmap))
return 1 ;
/* Don't allocate any blocks from earlier pages */
info->s->state.first_bitmap_with_space= info->s->last_insert_bitmap;
}
/*
We need to have DIRENTRY_SIZE here to take into account that we may
need an extra directory entry for the row
*/
while (allocate_head(bitmap, length + DIR_ENTRY_SIZE, block))
if (move_to_next_bitmap(info, bitmap))
return 1 ;
return 0 ;
}
/*
Find right bitmap and position for tail
SYNOPSIS
find_tail ( )
info Maria handler
length Size of data region we need store
position Position in bitmap_blocks where to store the
information for the head block .
RETURN
0 ok
1 error
*/
static my_bool find_tail(MARIA_HA *info, uint length, size_t position)
{
MARIA_FILE_BITMAP *bitmap= &info->s->bitmap;
MARIA_BITMAP_BLOCK *block;
DBUG_ENTER("find_tail" );
DBUG_ASSERT(length <= info->s->block_size - PAGE_OVERHEAD_SIZE(info->s));
/* Needed, as there is no error checking in dynamic_element */
if (allocate_dynamic(&info->bitmap_blocks, position))
DBUG_RETURN(1 );
block= dynamic_element(&info->bitmap_blocks, position, MARIA_BITMAP_BLOCK *);
/*
We have to add DIR_ENTRY_SIZE to ensure we have space for the tail and
its directory entry on the page
*/
while (allocate_tail(bitmap, length + DIR_ENTRY_SIZE, block))
if (move_to_next_bitmap(info, bitmap))
DBUG_RETURN(1 );
DBUG_RETURN(0 );
}
/*
Find right bitmap and position for full blocks in one extent
SYNOPSIS
find_mid ( )
info Maria handler .
pages How many pages to allocate .
position Position in bitmap_blocks where to store the
information for the head block .
NOTES
This is used to allocate the main extent after the ' head ' block
( Ie , the middle part of the head - middle - tail entry )
RETURN
0 ok
1 error
*/
static my_bool find_mid(MARIA_HA *info, ulong pages, uint position)
{
MARIA_FILE_BITMAP *bitmap= &info->s->bitmap;
MARIA_BITMAP_BLOCK *block;
block= dynamic_element(&info->bitmap_blocks, position, MARIA_BITMAP_BLOCK *);
while (!allocate_full_pages(bitmap, pages, block, 1 ))
{
if (move_to_next_bitmap(info, bitmap))
return 1 ;
}
return 0 ;
}
/*
Find right bitmap and position for putting a blob
SYNOPSIS
find_blob ( )
info Maria handler .
length Length of the blob
NOTES
The extents are stored last in info - > bitmap_blocks
IMPLEMENTATION
Allocate all full pages for the block + optionally one tail
RETURN
0 ok
1 error
*/
static my_bool find_blob(MARIA_HA *info, ulong length)
{
MARIA_FILE_BITMAP *bitmap= &info->s->bitmap;
uint full_page_size= FULL_PAGE_SIZE(info->s);
ulong pages;
uint rest_length, used;
size_t UNINIT_VAR(first_block_pos);
MARIA_BITMAP_BLOCK *first_block= 0 ;
DBUG_ENTER("find_blob" );
DBUG_PRINT("enter" , ("length: %lu" , length));
pages= length / full_page_size;
rest_length= (uint) (length - pages * full_page_size);
if (rest_length >= MAX_TAIL_SIZE(info->s->block_size))
{
pages++;
rest_length= 0 ;
}
first_block_pos= info->bitmap_blocks.elements;
if (pages)
{
MARIA_BITMAP_BLOCK *block;
if (allocate_dynamic(&info->bitmap_blocks,
info->bitmap_blocks.elements +
pages / BLOB_SEGMENT_MIN_SIZE + 2 ))
DBUG_RETURN(1 );
block= dynamic_element(&info->bitmap_blocks, info->bitmap_blocks.elements,
MARIA_BITMAP_BLOCK*);
do
{
/*
We use 0 x3fff here as the two upmost bits are reserved for
TAIL_BIT and START_EXTENT_BIT
*/
used= allocate_full_pages(bitmap,
(pages >= 0 x3fff ? 0 x3fff : (uint) pages),
block, 0 );
if (!used)
{
if (move_to_next_bitmap(info, bitmap))
DBUG_RETURN(1 );
}
else
{
pages-= used;
info->bitmap_blocks.elements++;
block++;
}
} while (pages != 0 );
}
if (rest_length && find_tail(info, rest_length,
info->bitmap_blocks.elements++))
DBUG_RETURN(1 );
first_block= dynamic_element(&info->bitmap_blocks, first_block_pos,
MARIA_BITMAP_BLOCK*);
first_block->sub_blocks= (uint)(info->bitmap_blocks.elements
- first_block_pos);
DBUG_RETURN(0 );
}
/*
Find pages to put ALL blobs
SYNOPSIS
allocate_blobs ( )
info Maria handler
row Information of what is in the row ( from calc_record_size ( ) )
RETURN
0 ok
1 error
*/
static my_bool allocate_blobs(MARIA_HA *info, MARIA_ROW *row)
{
ulong *length, *end;
size_t elements;
/*
Reserve size for :
head block
one extent
tail block
*/
elements= info->bitmap_blocks.elements;
for (length= row->blob_lengths, end= length + info->s->base.blobs;
length < end; length++)
{
if (*length && find_blob(info, *length))
return 1 ;
}
row->extents_count= (uint)(info->bitmap_blocks.elements - elements);
return 0 ;
}
/*
Reserve the current head page
SYNOPSIS
use_head ( )
info Maria handler
page Page number to update
( Note that caller guarantees this is in the active
bitmap )
size How much free space is left on the page
block_position In which info - > bitmap_block we have the
information about the head block .
NOTES
This is used on update where we are updating an existing head page
*/
static void use_head(MARIA_HA *info, pgcache_page_no_t page, uint size,
uint block_position)
{
MARIA_FILE_BITMAP *bitmap= &info->s->bitmap;
MARIA_BITMAP_BLOCK *block;
uchar *data;
uint offset, tmp, offset_page;
DBUG_ENTER("use_head" );
DBUG_ASSERT(page % bitmap->pages_covered);
block= dynamic_element(&info->bitmap_blocks, block_position,
MARIA_BITMAP_BLOCK*);
block->page= page;
block->page_count= 1 + TAIL_BIT;
block->empty_space= size;
block->used= BLOCKUSED_TAIL;
/*
Mark place used by reading / writing 2 bytes at a time to handle
bitmaps in overlapping bytes
*/
offset_page= (uint) (page - bitmap->page - 1 ) * 3 ;
offset= offset_page & 7 ;
data= bitmap->map + offset_page / 8 ;
tmp= uint2korr(data);
block->org_bitmap_value= (tmp >> offset) & 7 ;
tmp= (tmp & ~(7 << offset)) | (FULL_HEAD_PAGE << offset);
int2store(data, tmp);
bitmap->changed= 1 ;
DBUG_EXECUTE("bitmap" , _ma_print_bitmap_changes(bitmap););
DBUG_VOID_RETURN;
}
/*
Find out where to split the row ( ie , what goes in head , middle , tail etc )
SYNOPSIS
find_where_to_split_row ( )
share Maria share
row Information of what is in the row ( from calc_record_size ( ) )
extents Max number of extents we have to store in header
split_size Free size on the page ( The head length must be less
than this )
RETURN
row_length for the head block .
*/
static uint find_where_to_split_row(MARIA_SHARE *share, MARIA_ROW *row,
uint extents, uint split_size)
{
uint *lengths, *lengths_end;
/*
Ensure we have the minimum required space on head page :
- Header + length of field lengths ( row - > min_length )
- Number of extents
- One extent
*/
uint row_length= (row->min_length +
size_to_store_key_length(extents) +
ROW_EXTENT_SIZE);
DBUG_ASSERT(row_length <= split_size);
/*
Store first in all_field_lengths the different parts that are written
to the row . This needs to be in same order as in
ma_block_rec . c : : write_block_record ( )
*/
row->null_field_lengths[-3 ]= extents * ROW_EXTENT_SIZE;
row->null_field_lengths[-2 ]= share->base.fixed_not_null_fields_length;
row->null_field_lengths[-1 ]= row->field_lengths_length;
for (lengths= row->null_field_lengths - EXTRA_LENGTH_FIELDS,
lengths_end= (lengths + share->base.fields - share->base.blobs +
EXTRA_LENGTH_FIELDS); lengths < lengths_end; lengths++)
{
if (row_length + *lengths > split_size)
break ;
row_length+= *lengths;
}
return row_length;
}
/*
Find where to write the middle parts of the row and the tail
SYNOPSIS
write_rest_of_head ( )
info Maria handler
position Position in bitmap_blocks . Is 0 for rows that needs
full blocks ( ie , has a head , middle part and optional tail )
rest_length How much left of the head block to write .
RETURN
0 ok
1 error
*/
static my_bool write_rest_of_head(MARIA_HA *info, uint position,
ulong rest_length)
{
MARIA_SHARE *share= info->s;
uint full_page_size= FULL_PAGE_SIZE(share);
MARIA_BITMAP_BLOCK *block;
DBUG_ENTER("write_rest_of_head" );
DBUG_PRINT("enter" , ("position: %u rest_length: %lu" , position,
rest_length));
if (position == 0 )
{
/* Write out full pages */
uint pages= rest_length / full_page_size;
rest_length%= full_page_size;
if (rest_length >= MAX_TAIL_SIZE(share->block_size))
{
/* Put tail on a full page */
pages++;
rest_length= 0 ;
}
if (find_mid(info, pages, 1 ))
DBUG_RETURN(1 );
/*
Insert empty block after full pages , to allow write_block_record ( ) to
split segment into used + free page
*/
block= dynamic_element(&info->bitmap_blocks, 2 , MARIA_BITMAP_BLOCK*);
block->page_count= 0 ;
block->used= 0 ;
}
if (rest_length)
{
if (find_tail(info, rest_length, ELEMENTS_RESERVED_FOR_MAIN_PART - 1 ))
DBUG_RETURN(1 );
}
else
{
/* Empty tail block */
block= dynamic_element(&info->bitmap_blocks,
ELEMENTS_RESERVED_FOR_MAIN_PART - 1 ,
MARIA_BITMAP_BLOCK *);
block->page_count= 0 ;
block->used= 0 ;
}
DBUG_RETURN(0 );
}
/*
Find where to store one row
SYNPOSIS
_ ma_bitmap_find_place ( )
info Maria handler
row Information about row to write
blocks Store data about allocated places here
RETURN
0 ok
row - > space_on_head_page contains minimum number of bytes we
expect to put on the head page .
1 error
my_errno is set to error
*/
my_bool _ma_bitmap_find_place(MARIA_HA *info, MARIA_ROW *row,
MARIA_BITMAP_BLOCKS *blocks)
{
MARIA_SHARE *share= info->s;
my_bool res= 1 ;
uint full_page_size, position, max_page_size;
uint head_length, row_length, rest_length, extents_length;
DBUG_ENTER("_ma_bitmap_find_place" );
blocks->count= 0 ;
blocks->tail_page_skipped= blocks->page_skipped= 0 ;
row->extents_count= 0 ;
/*
Reserve place for the following blocks :
- Head block
- Full page block
- Marker block to allow write_block_record ( ) to split full page blocks
into full and free part
- Tail block
*/
info->bitmap_blocks.elements= ELEMENTS_RESERVED_FOR_MAIN_PART;
max_page_size= (share->block_size - PAGE_OVERHEAD_SIZE(share));
mysql_mutex_lock(&share->bitmap.bitmap_lock);
if (row->total_length <= max_page_size)
{
/* Row fits in one page */
position= ELEMENTS_RESERVED_FOR_MAIN_PART - 1 ;
if (find_head(info, (uint) row->total_length, position))
goto abort;
row->space_on_head_page= row->total_length;
goto end;
}
/*
First allocate all blobs so that we can find out the needed size for
the main block .
*/
if (row->blob_length && allocate_blobs(info, row))
goto abort;
extents_length= row->extents_count * ROW_EXTENT_SIZE;
/*
The + 3 is reserved for storing the number of segments in the row header .
*/
if ((head_length= (row->head_length + extents_length + 3 )) <=
max_page_size)
{
/* Main row part fits into one page */
position= ELEMENTS_RESERVED_FOR_MAIN_PART - 1 ;
if (find_head(info, head_length, position))
goto abort;
row->space_on_head_page= head_length;
goto end;
}
/* Allocate enough space */
head_length+= ELEMENTS_RESERVED_FOR_MAIN_PART * ROW_EXTENT_SIZE;
/* The first segment size is stored in 'row_length' */
row_length= find_where_to_split_row(share, row, row->extents_count +
ELEMENTS_RESERVED_FOR_MAIN_PART-1 ,
max_page_size);
full_page_size= MAX_TAIL_SIZE(share->block_size);
position= 0 ;
rest_length= head_length - row_length;
if (rest_length <= full_page_size)
position= ELEMENTS_RESERVED_FOR_MAIN_PART -2 ; /* Only head and tail */
if (find_head(info, row_length, position))
goto abort;
row->space_on_head_page= row_length;
if (write_rest_of_head(info, position, rest_length))
goto abort;
end:
blocks->block= dynamic_element(&info->bitmap_blocks, position,
MARIA_BITMAP_BLOCK*);
blocks->block->sub_blocks= ELEMENTS_RESERVED_FOR_MAIN_PART - position;
/* First block's page_count is for all blocks */
blocks->count= (uint)(info->bitmap_blocks.elements - position);
res= 0 ;
abort:
mysql_mutex_unlock(&share->bitmap.bitmap_lock);
DBUG_RETURN(res);
}
/*
Find where to put row on update ( when head page is already defined )
SYNPOSIS
_ ma_bitmap_find_new_place ( )
info Maria handler
row Information about row to write
page On which page original row was stored
free_size Free size on head page
blocks Store data about allocated places here
NOTES
This function is only called when the new row can ' t fit in the space of
the old row in the head page .
This is essentially the same as _ ma_bitmap_find_place ( ) except that
we don ' t call find_head ( ) to search in bitmaps where to put the page .
RETURN
0 ok
1 error
*/
my_bool _ma_bitmap_find_new_place(MARIA_HA *info, MARIA_ROW *row,
pgcache_page_no_t page, uint free_size,
MARIA_BITMAP_BLOCKS *blocks)
{
MARIA_SHARE *share= info->s;
my_bool res= 1 ;
uint position;
uint head_length, row_length, rest_length, extents_length;
ulonglong bitmap_page;
DBUG_ENTER("_ma_bitmap_find_new_place" );
blocks->count= 0 ;
blocks->tail_page_skipped= blocks->page_skipped= 0 ;
row->extents_count= 0 ;
info->bitmap_blocks.elements= ELEMENTS_RESERVED_FOR_MAIN_PART;
mysql_mutex_lock(&share->bitmap.bitmap_lock);
/*
First allocate all blobs ( so that we can find out the needed size for
the main block .
*/
if (row->blob_length && allocate_blobs(info, row))
goto abort;
/* Switch bitmap to current head page */
bitmap_page= page - page % share->bitmap.pages_covered;
if (share->bitmap.page != bitmap_page &&
_ma_change_bitmap_page(info, &share->bitmap, bitmap_page))
goto abort;
extents_length= row->extents_count * ROW_EXTENT_SIZE;
if ((head_length= (row->head_length + extents_length + 3 )) <= free_size)
{
/* Main row part fits into one page */
position= ELEMENTS_RESERVED_FOR_MAIN_PART - 1 ;
use_head(info, page, head_length, position);
row->space_on_head_page= head_length;
goto end;
}
/* Allocate enough space */
head_length+= ELEMENTS_RESERVED_FOR_MAIN_PART * ROW_EXTENT_SIZE;
/*
The first segment size is stored in ' row_length '
We have to add ELEMENTS_RESERVED_FOR_MAIN_PART here as the extent
information may be up to this size when the header splits .
*/
row_length= find_where_to_split_row(share, row, row->extents_count +
ELEMENTS_RESERVED_FOR_MAIN_PART-1 ,
free_size);
position= 0 ;
rest_length= head_length - row_length;
if (rest_length <= MAX_TAIL_SIZE(share->block_size))
position= ELEMENTS_RESERVED_FOR_MAIN_PART -2 ; /* Only head and tail */
use_head(info, page, row_length, position);
row->space_on_head_page= row_length;
if (write_rest_of_head(info, position, rest_length))
goto abort;
end:
blocks->block= dynamic_element(&info->bitmap_blocks, position,
MARIA_BITMAP_BLOCK*);
blocks->block->sub_blocks= ELEMENTS_RESERVED_FOR_MAIN_PART - position;
/* First block's page_count is for all blocks */
blocks->count= (uint)(info->bitmap_blocks.elements - position);
res= 0 ;
abort:
mysql_mutex_unlock(&share->bitmap.bitmap_lock);
DBUG_RETURN(res);
}
/****************************************************************************
Clear and reset bits
****************************************************************************/
/*
Set fill pattern for a page
set_page_bits ( )
info Maria handler
bitmap Bitmap handler
page Adress to page
fill_pattern Pattern ( not size ) for page
NOTES
Page may not be part of active bitmap
RETURN
0 ok
1 error
*/
static my_bool set_page_bits(MARIA_HA *info, MARIA_FILE_BITMAP *bitmap,
pgcache_page_no_t page, uint fill_pattern)
{
pgcache_page_no_t bitmap_page;
uint offset_page, offset, tmp, org_tmp, used_offset;
uchar *data;
DBUG_ENTER("set_page_bits" );
DBUG_ASSERT(fill_pattern <= 7 );
bitmap_page= page - page % bitmap->pages_covered;
if (bitmap_page != bitmap->page &&
_ma_change_bitmap_page(info, bitmap, bitmap_page))
DBUG_RETURN(1 );
/* Find page number from start of bitmap */
offset_page= (uint) (page - bitmap->page - 1 );
/*
Mark place used by reading / writing 2 bytes at a time to handle
bitmaps in overlapping bytes
*/
offset_page*= 3 ;
offset= offset_page & 7 ;
data= bitmap->map + offset_page / 8 ;
org_tmp= tmp= uint2korr(data);
tmp= (tmp & ~(7 << offset)) | (fill_pattern << offset);
if (tmp == org_tmp)
DBUG_RETURN(0 ); /* No changes */
/*
Take care to not write bytes outside of bitmap .
fill_pattern is 3 bits , so we need to write two bytes
if bit position we write to is > ( 8 - 3 )
*/
if (offset > 5 )
int2store(data, tmp);
else
data[0 ]= tmp;
/*
Reset full_head_size or full_tail_size if we are releasing data before
it . Increase used_size if we are allocating data .
*/
used_offset= (uint) (data - bitmap->map);
if (fill_pattern < 4 )
set_if_smaller(bitmap->full_head_size, used_offset);
if (fill_pattern == 0 || (fill_pattern > 4 && fill_pattern < 7 ))
set_if_smaller(bitmap->full_tail_size, used_offset);
if (fill_pattern != 0 )
{
/* Calulcate which was the last changed byte */
used_offset+= offset > 5 ? 2 : 1 ;
set_if_bigger(bitmap->used_size, used_offset);
}
_ma_check_bitmap(bitmap);
bitmap->changed= 1 ;
DBUG_EXECUTE("bitmap" , _ma_print_bitmap_changes(bitmap););
if (fill_pattern != FULL_HEAD_PAGE && fill_pattern != FULL_TAIL_PAGE)
set_if_smaller(info->s->state.first_bitmap_with_space, bitmap_page);
/*
Note that if the condition above is false ( page is full ) , and all pages of
this bitmap are now full , and that bitmap page was
first_bitmap_with_space , we don ' t modify first_bitmap_with_space , indeed
its value still tells us where to start our search for a bitmap with space
( which is for sure after this full one ) .
That does mean that first_bitmap_with_space is only a lower bound .
*/
DBUG_RETURN(0 );
}
/*
Get bitmap pattern for a given page
SYNOPSIS
bitmap_get_page_bits ( )
info Maria handler
bitmap Bitmap handler
page Page number
RETURN
0 - 7 Bitmap pattern
~ 0 Error ( couldn ' t read page )
*/
static uint bitmap_get_page_bits(MARIA_HA *info, MARIA_FILE_BITMAP *bitmap,
pgcache_page_no_t page)
{
pgcache_page_no_t bitmap_page;
uint offset_page, offset, tmp;
uchar *data;
DBUG_ENTER("_ma_bitmap_get_page_bits" );
bitmap_page= page - page % bitmap->pages_covered;
if (bitmap_page != bitmap->page &&
_ma_change_bitmap_page(info, bitmap, bitmap_page))
DBUG_RETURN(~ (uint) 0 );
/* Find page number from start of bitmap */
offset_page= (uint) (page - bitmap->page - 1 );
/*
Mark place used by reading / writing 2 bytes at a time to handle
bitmaps in overlapping bytes
*/
offset_page*= 3 ;
offset= offset_page & 7 ;
data= bitmap->map + offset_page / 8 ;
tmp= uint2korr(data);
DBUG_RETURN((tmp >> offset) & 7 );
}
/* As above, but take a lock while getting the data */
uint _ma_bitmap_get_page_bits(MARIA_HA *info, MARIA_FILE_BITMAP *bitmap,
pgcache_page_no_t page)
{
uint tmp;
mysql_mutex_lock(&bitmap->bitmap_lock);
tmp= bitmap_get_page_bits(info, bitmap, page);
mysql_mutex_unlock(&bitmap->bitmap_lock);
return tmp;
}
/*
Mark all pages in a region as free
SYNOPSIS
_ ma_bitmap_reset_full_page_bits ( )
info Maria handler
bitmap Bitmap handler
page Start page
page_count Number of pages
NOTES
We assume that all pages in region is covered by same bitmap
One must have a lock on info - > s - > bitmap . bitmap_lock
RETURN
0 ok
1 Error ( when reading bitmap )
*/
my_bool _ma_bitmap_reset_full_page_bits(MARIA_HA *info,
MARIA_FILE_BITMAP *bitmap,
pgcache_page_no_t page,
uint page_count)
{
ulonglong bitmap_page;
uint offset, bit_start, bit_count, tmp, byte_offset;
uchar *data;
DBUG_ENTER("_ma_bitmap_reset_full_page_bits" );
DBUG_PRINT("enter" , ("page: %lu page_count: %u" , (ulong) page, page_count));
mysql_mutex_assert_owner(&info->s->bitmap.bitmap_lock);
bitmap_page= page - page % bitmap->pages_covered;
DBUG_ASSERT(page != bitmap_page);
if (bitmap_page != bitmap->page &&
_ma_change_bitmap_page(info, bitmap, bitmap_page))
DBUG_RETURN(1 );
/* Find page number from start of bitmap */
offset= (uint) (page - bitmap->page - 1 );
/* Clear bits from 'page * 3' -> '(page + page_count) * 3' */
bit_start= offset * 3 ;
bit_count= page_count * 3 ;
byte_offset= bit_start/8 ;
data= bitmap->map + byte_offset;
offset= bit_start & 7 ;
tmp= (255 << offset); /* Bits to keep */
if (bit_count + offset < 8 )
{
/* Only clear bits between 'offset' and 'offset+bit_count-1' */
tmp^= (255 << (offset + bit_count));
}
*data&= ~tmp;
set_if_smaller(bitmap->full_head_size, byte_offset);
set_if_smaller(bitmap->full_tail_size, byte_offset);
if ((int ) (bit_count-= (8 - offset)) > 0 )
{
uint fill;
data++;
/*
- 1 is here to avoid one ' if ' statement and to let the following code
handle the last byte
*/
if ((fill= (bit_count - 1 ) / 8 ))
{
bzero(data, fill);
data+= fill;
}
bit_count-= fill * 8 ; /* Bits left to clear */
tmp= (1 << bit_count) - 1 ;
*data&= ~tmp;
}
set_if_smaller(info->s->state.first_bitmap_with_space, bitmap_page);
bitmap->changed= 1 ;
DBUG_EXECUTE("bitmap" , _ma_print_bitmap_changes(bitmap););
DBUG_RETURN(0 );
}
/*
Set all pages in a region as used
SYNOPSIS
_ ma_bitmap_set_full_page_bits ( )
info Maria handler
bitmap Bitmap handler
page Start page
page_count Number of pages
NOTES
We assume that all pages in region is covered by same bitmap
One must have a lock on info - > s - > bitmap . bitmap_lock
RETURN
0 ok
1 Error ( when reading bitmap )
*/
my_bool _ma_bitmap_set_full_page_bits(MARIA_HA *info,
MARIA_FILE_BITMAP *bitmap,
pgcache_page_no_t page, uint page_count)
{
ulonglong bitmap_page;
uint offset, bit_start, bit_count, tmp;
uchar *data;
DBUG_ENTER("_ma_bitmap_set_full_page_bits" );
DBUG_PRINT("enter" , ("page: %lu page_count: %u" , (ulong) page, page_count));
mysql_mutex_assert_owner(&info->s->bitmap.bitmap_lock);
bitmap_page= page - page % bitmap->pages_covered;
if (page == bitmap_page ||
page + page_count > bitmap_page + bitmap->pages_covered)
{
DBUG_ASSERT(0 ); /* Wrong in data */
DBUG_RETURN(1 );
}
if (bitmap_page != bitmap->page &&
_ma_change_bitmap_page(info, bitmap, bitmap_page))
DBUG_RETURN(1 );
/* Find page number from start of bitmap */
offset= (uint) (page - bitmap->page - 1 );
/* Set bits from 'page * 3' -> '(page + page_count) * 3' */
bit_start= offset * 3 ;
bit_count= page_count * 3 ;
data= bitmap->map + bit_start / 8 ;
offset= bit_start & 7 ;
tmp= (255 << offset); /* Bits to keep */
if (bit_count + offset < 8 )
{
/* Only set bits between 'offset' and 'offset+bit_count-1' */
tmp^= (255 << (offset + bit_count));
}
*data|= tmp;
if ((int ) (bit_count-= (8 - offset)) > 0 )
{
uint fill;
data++;
/*
- 1 is here to avoid one ' if ' statement and to let the following code
handle the last byte
*/
if ((fill= (bit_count - 1 ) / 8 ))
{
bfill(data, fill, 255 );
data+= fill;
}
bit_count-= fill * 8 ; /* Bits left to set */
tmp= (1 << bit_count) - 1 ;
*data|= tmp;
}
set_if_bigger(bitmap->used_size, (uint) (data - bitmap->map) + 1 );
_ma_check_bitmap(bitmap);
bitmap->changed= 1 ;
DBUG_EXECUTE("bitmap" , _ma_print_bitmap_changes(bitmap););
DBUG_RETURN(0 );
}
/**
@ brief
Make a transition of MARIA_FILE_BITMAP : : non_flushable .
If the bitmap becomes flushable , which requires that REDO - UNDO has been
logged and all bitmap pages touched by the thread have a correct
allocation , it unpins all bitmap pages , and if _ ma_bitmap_flush_all ( ) is
waiting ( in practice it is a checkpoint ) , it wakes it up .
If the bitmap becomes or stays unflushable , the function merely records it
unless a concurrent _ ma_bitmap_flush_all ( ) is happening , in which case the
function first waits for the flush to be done .
@ note
this sets info - > non_flushable_state to 1 if we have incremented
bitmap - > non_flushable and not yet decremented it .
@ param share Table ' s share
@ param non_flushable_inc Increment of MARIA_FILE_BITMAP : : non_flushable
( - 1 or + 1 ) .
*/
void _ma_bitmap_flushable(MARIA_HA *info, int non_flushable_inc)
{
MARIA_SHARE *share= info->s;
MARIA_FILE_BITMAP *bitmap;
DBUG_ENTER("_ma_bitmap_flushable" );
/*
Not transactional tables are never automatically flushed and need no
protection
*/
if (!share->now_transactional)
DBUG_VOID_RETURN;
bitmap= &share->bitmap;
mysql_mutex_lock(&bitmap->bitmap_lock);
if (non_flushable_inc == -1 )
{
DBUG_ASSERT((int ) bitmap->non_flushable > 0 );
DBUG_ASSERT(info->non_flushable_state == 1 );
if (--bitmap->non_flushable == 0 )
{
/*
We unlock and unpin pages locked and pinned by other threads . It does
not seem to be an issue as all bitmap changes are serialized with
the bitmap ' s mutex .
*/
_ma_bitmap_unpin_all(share);
if (unlikely(bitmap->waiting_for_non_flushable))
{
DBUG_PRINT("info" , ("bitmap flushable waking up flusher" ));
mysql_cond_broadcast(&bitmap->bitmap_cond);
}
}
DBUG_PRINT("info" , ("bitmap->non_flushable: %u" , bitmap->non_flushable));
mysql_mutex_unlock(&bitmap->bitmap_lock);
info->non_flushable_state= 0 ;
DBUG_VOID_RETURN;
}
DBUG_ASSERT(non_flushable_inc == 1 );
DBUG_ASSERT(info->non_flushable_state == 0 );
bitmap->waiting_for_flush_all_requested++;
while (unlikely(bitmap->flush_all_requested))
{
/*
Some other thread is waiting for the bitmap to become
flushable . Not the moment to make the bitmap unflushable or more
unflushable ; let ' s rather back off and wait . If we didn ' t do this , with
multiple writers , there may always be one thread causing the bitmap to
be unflushable and _ ma_bitmap_flush_all ( ) would wait for long .
There should not be a deadlock because if our thread increased
non_flushable ( and thus _ ma_bitmap_flush_all ( ) is waiting for at least
our thread ) , it is not going to increase it more so is not going to come
here .
*/
DBUG_PRINT("info" , ("waiting for bitmap flusher" ));
mysql_cond_wait(&bitmap->bitmap_cond, &bitmap->bitmap_lock);
}
bitmap->waiting_for_flush_all_requested--;
bitmap->non_flushable++;
DBUG_PRINT("info" , ("bitmap->non_flushable: %u" , bitmap->non_flushable));
mysql_mutex_unlock(&bitmap->bitmap_lock);
info->non_flushable_state= 1 ;
DBUG_VOID_RETURN;
}
/*
Correct bitmap pages to reflect the true allocation
SYNOPSIS
_ ma_bitmap_release_unused ( )
info Maria handle
blocks Bitmap blocks
IMPLEMENTATION
If block - > used & BLOCKUSED_TAIL is set :
If block - > used & BLOCKUSED_USED is set , then the bits for the
corresponding page is set according to block - > empty_space
If block - > used & BLOCKUSED_USED is not set , then the bits for
the corresponding page is set to org_bitmap_value ;
If block - > used & BLOCKUSED_TAIL is not set :
if block - > used is not set , the bits for the corresponding page are
cleared
For the first block ( head block ) the logic is same as for a tail block
Note that we may have ' filler blocks ' that are used to split a block
in half ; These can be recognized by that they have page_count = = 0 .
This code also reverse the effect of ma_bitmap_flushable ( . . , 1 ) ;
RETURN
0 ok
1 error ( Couldn ' t write or read bitmap page )
*/
my_bool _ma_bitmap_release_unused(MARIA_HA *info, MARIA_BITMAP_BLOCKS *blocks)
{
MARIA_BITMAP_BLOCK *block= blocks->block, *end= block + blocks->count;
MARIA_FILE_BITMAP *bitmap= &info->s->bitmap;
uint bits, current_bitmap_value;
DBUG_ENTER("_ma_bitmap_release_unused" );
/*
We can skip FULL_HEAD_PAGE ( 4 ) as the page was marked as ' full '
when we allocated space in the page
*/
current_bitmap_value= FULL_HEAD_PAGE;
mysql_mutex_lock(&bitmap->bitmap_lock);
/* First handle head block */
if (block->used & BLOCKUSED_USED)
{
DBUG_PRINT("info" , ("head page: %lu empty_space: %u" ,
(ulong) block->page, block->empty_space));
bits= _ma_free_size_to_head_pattern(bitmap, block->empty_space);
if (block->used & BLOCKUSED_USE_ORG_BITMAP)
current_bitmap_value= block->org_bitmap_value;
}
else
bits= block->org_bitmap_value;
if (bits != current_bitmap_value)
{
if (set_page_bits(info, bitmap, block->page, bits))
goto err;
}
else
{
DBUG_ASSERT(current_bitmap_value ==
bitmap_get_page_bits(info, bitmap, block->page));
}
/* Handle all full pages and tail pages (for head page and blob) */
for (block++; block < end; block++)
{
uint page_count;
if (!block->page_count)
continue ; /* Skip 'filler blocks' */
page_count= block->page_count;
if (block->used & BLOCKUSED_TAIL)
{
current_bitmap_value= FULL_TAIL_PAGE;
/* The bitmap page is only one page */
page_count= 1 ;
if (block->used & BLOCKUSED_USED)
{
DBUG_PRINT("info" , ("tail page: %lu empty_space: %u" ,
(ulong) block->page, block->empty_space));
bits= free_size_to_tail_pattern(bitmap, block->empty_space);
if (block->used & BLOCKUSED_USE_ORG_BITMAP)
current_bitmap_value= block->org_bitmap_value;
}
else
bits= block->org_bitmap_value;
/*
The page has all bits set ; The following test is an optimization
to not set the bits to the same value as before .
*/
DBUG_ASSERT(current_bitmap_value ==
bitmap_get_page_bits(info, bitmap, block->page));
if (bits != current_bitmap_value)
{
if (set_page_bits(info, bitmap, block->page, bits))
goto err;
}
}
else if (!(block->used & BLOCKUSED_USED) &&
_ma_bitmap_reset_full_page_bits(info, bitmap,
block->page, page_count))
goto err;
}
/* This duplicates ma_bitmap_flushable(-1) except it already has mutex */
if (info->non_flushable_state)
{
DBUG_ASSERT(((int ) (bitmap->non_flushable)) > 0 );
info->non_flushable_state= 0 ;
if (--bitmap->non_flushable == 0 )
{
_ma_bitmap_unpin_all(info->s);
if (unlikely(bitmap->waiting_for_non_flushable))
{
DBUG_PRINT("info" , ("bitmap flushable waking up flusher" ));
mysql_cond_broadcast(&bitmap->bitmap_cond);
}
}
}
DBUG_PRINT("info" , ("bitmap->non_flushable: %u" , bitmap->non_flushable));
mysql_mutex_unlock(&bitmap->bitmap_lock);
DBUG_RETURN(0 );
err:
mysql_mutex_unlock(&bitmap->bitmap_lock);
DBUG_RETURN(1 );
}
/*
Free full pages from bitmap and pagecache
SYNOPSIS
_ ma_bitmap_free_full_pages ( )
info Maria handle
extents Extents ( as stored on disk )
count Number of extents
IMPLEMENTATION
Mark all full pages ( not tails ) from extents as free , both in bitmap
and page cache .
RETURN
0 ok
1 error ( Couldn ' t write or read bitmap page )
*/
my_bool _ma_bitmap_free_full_pages(MARIA_HA *info, const uchar *extents,
uint count)
{
MARIA_FILE_BITMAP *bitmap= &info->s->bitmap;
my_bool res;
DBUG_ENTER("_ma_bitmap_free_full_pages" );
for (; count--; extents+= ROW_EXTENT_SIZE)
{
pgcache_page_no_t page= uint5korr(extents);
uint page_count= (uint2korr(extents + ROW_EXTENT_PAGE_SIZE) &
~START_EXTENT_BIT);
if (!(page_count & TAIL_BIT))
{
if (page == 0 && page_count == 0 )
continue ; /* Not used extent */
if (pagecache_delete_pages(info->dfile.pagecache, &info->dfile, page,
page_count, PAGECACHE_LOCK_WRITE, 1 ))
DBUG_RETURN(1 );
mysql_mutex_lock(&bitmap->bitmap_lock);
res= _ma_bitmap_reset_full_page_bits(info, bitmap, page, page_count);
mysql_mutex_unlock(&bitmap->bitmap_lock);
if (res)
DBUG_RETURN(1 );
}
}
DBUG_RETURN(0 );
}
/*
Mark in the bitmap how much free space there is on a page
SYNOPSIS
_ ma_bitmap_set ( )
info Maria handler
page Adress to page
head 1 if page is a head page , 0 if tail page
empty_space How much empty space there is on page
RETURN
0 ok
1 error
*/
my_bool _ma_bitmap_set(MARIA_HA *info, pgcache_page_no_t page, my_bool head,
uint empty_space)
{
MARIA_FILE_BITMAP *bitmap= &info->s->bitmap;
uint bits;
my_bool res;
DBUG_ENTER("_ma_bitmap_set" );
DBUG_PRINT("enter" , ("page: %lu head: %d empty_space: %u" ,
(ulong) page, head, empty_space));
mysql_mutex_lock(&info->s->bitmap.bitmap_lock);
bits= (head ?
_ma_free_size_to_head_pattern(bitmap, empty_space) :
free_size_to_tail_pattern(bitmap, empty_space));
res= set_page_bits(info, bitmap, page, bits);
mysql_mutex_unlock(&info->s->bitmap.bitmap_lock);
DBUG_RETURN(res);
}
/*
Check that bitmap pattern is correct for a page
NOTES
Used in maria_chk
SYNOPSIS
_ ma_check_bitmap_data ( )
info Maria handler
page_type What kind of page this is
page Adress to page
empty_space Empty space on page
bitmap_pattern Bitmap pattern for page ( from bitmap )
RETURN
0 ok
1 error
*/
my_bool _ma_check_bitmap_data(MARIA_HA *info, enum en_page_type page_type,
uint empty_space, uint bitmap_pattern)
{
uint bits;
switch (page_type) {
case UNALLOCATED_PAGE:
case MAX_PAGE_TYPE:
bits= 0 ;
break ;
case HEAD_PAGE:
bits= _ma_free_size_to_head_pattern(&info->s->bitmap, empty_space);
break ;
case TAIL_PAGE:
bits= free_size_to_tail_pattern(&info->s->bitmap, empty_space);
break ;
case BLOB_PAGE:
bits= FULL_TAIL_PAGE;
break ;
default :
bits= 0 ; /* to satisfy compiler */
DBUG_ASSERT(0 );
}
return (bitmap_pattern != bits);
}
/**
Check that bitmap looks correct
- All data before full_head_size and full_tail_size are allocated
- There is no allocated data after used_size
All of the above need to be correct only according to 6 byte
alignment as all loops reads 6 bytes at a time and we check both
start and end position according to the current 6 byte position .
*/
#ifndef DBUG_OFF
static void _ma_check_bitmap(MARIA_FILE_BITMAP *bitmap)
{
uchar *data= bitmap->map;
uchar *end= bitmap->map + bitmap->total_size;
uchar *full_head_end=0 , *full_tail_end=0 , *first_empty= bitmap->map;
for (; data < end; data+= 6 )
{
ulonglong bits= uint6korr(data); /* 6 bytes = 6*8/3= 16 patterns */
uint i;
if (bits == 04444444444444444 LL || bits == 0 xffffffffffffLL)
{
first_empty= data + 6 ;
continue ; /* block fully used */
}
if (bits == 0 )
{
if (!full_head_end)
full_head_end= data;
if (!full_tail_end)
full_tail_end= data;
continue ;
}
first_empty= data + 6 ;
if (!full_head_end || !full_tail_end)
{
for (i= 0 , bits >>= 0 ; i < 16 ; i++, bits >>= 3 )
{
uint pattern= (uint) (bits & 7 );
if (pattern == FULL_HEAD_PAGE || pattern == FULL_TAIL_PAGE)
continue ;
if (pattern < 4 && !full_head_end)
full_head_end= data;
if ((pattern == 0 || (pattern > 4 && pattern < 7 )) && !full_tail_end)
full_tail_end= data;
}
}
}
if (!full_head_end)
full_head_end= data;
if (!full_tail_end)
full_tail_end= data;
/* used_size must point after the last byte that had some data) */
DBUG_ASSERT(bitmap->used_size <= bitmap->total_size);
DBUG_ASSERT((bitmap->map + (bitmap->used_size+5 )/6 *6 ) >= first_empty);
/* full_xxxx_size can't point after the first block that has free data */
DBUG_ASSERT((bitmap->map + (bitmap->full_head_size/6 *6 )) <= full_head_end);
DBUG_ASSERT((bitmap->map + (bitmap->full_tail_size/6 *6 )) <= full_tail_end);
}
#endif
/*
Check if the page type matches the one that we have in the bitmap
SYNOPSIS
_ ma_check_if_right_bitmap_type ( )
info Maria handler
page_type What kind of page this is
page Adress to page
bitmap_pattern Store here the pattern that was in the bitmap for the
page . This is always updated .
NOTES
Used in maria_chk
RETURN
0 ok
1 error
*/
my_bool _ma_check_if_right_bitmap_type(MARIA_HA *info,
enum en_page_type page_type,
pgcache_page_no_t page,
uint *bitmap_pattern)
{
if ((*bitmap_pattern= _ma_bitmap_get_page_bits(info, &info->s->bitmap,
page)) > 7 )
return 1 ; /* Couldn't read page */
switch (page_type) {
case HEAD_PAGE:
return *bitmap_pattern < 1 || *bitmap_pattern > 4 ;
case TAIL_PAGE:
return *bitmap_pattern < 5 ;
case BLOB_PAGE:
return *bitmap_pattern != 7 ;
default :
break ;
}
DBUG_ASSERT(0 );
return 1 ;
}
/**
@ brief create the first bitmap page of a freshly created data file
@ param share table ' s share
@ return Operation status
@ retval 0 OK
@ retval ! = 0 Error
*/
int _ma_bitmap_create_first(MARIA_SHARE *share)
{
uint block_size= share->bitmap.block_size;
size_t error;
File file= share->bitmap.file.file;
uchar *temp_buff;
if (!(temp_buff= (uchar*) my_alloca(block_size)))
return 1 ;
bzero(temp_buff, block_size);
/*
Next write operation of the page will write correct CRC
if it is needed
*/
int4store(temp_buff + block_size - CRC_SIZE, MARIA_NO_CRC_BITMAP_PAGE);
error= my_pwrite(file, temp_buff, block_size, 0 , MYF(MY_NABP | MY_WME));
my_afree(temp_buff);
if (error)
return 1 ;
share->state.state.data_file_length= block_size;
_ma_bitmap_delete_all(share);
return 0 ;
}
/**
@ brief Pagecache callback to get the TRANSLOG_ADDRESS to flush up to , when a
bitmap page needs to be flushed .
@ param page Page ' s content
@ param page_no Page ' s number ( < offset > / < page length > )
@ param data_ptr Callback data pointer ( pointer to MARIA_SHARE )
@ retval TRANSLOG_ADDRESS to flush up to .
*/
static my_bool
flush_log_for_bitmap(PAGECACHE_IO_HOOK_ARGS *args __attribute__ ((unused)))
{
#ifdef DBUG_ASSERT_EXISTS
const MARIA_SHARE *share= (MARIA_SHARE*)args->data;
#endif
DBUG_ENTER("flush_log_for_bitmap" );
DBUG_ASSERT(share->now_transactional);
/*
WAL imposes that UNDOs reach disk before bitmap is flushed . We don ' t know
the LSN of the last UNDO about this bitmap page , so we flush whole log .
*/
DBUG_RETURN(translog_flush(translog_get_horizon()));
}
/**
@ brief Set callbacks for bitmap pages
@ note
We don ' t use pagecache_file_init here , as we want to keep the
code readable
*/
void _ma_bitmap_set_pagecache_callbacks(PAGECACHE_FILE *file,
MARIA_SHARE *share)
{
pagecache_file_set_null_hooks(file);
file->callback_data= (uchar*) share;
file->flush_log_callback= maria_flush_log_for_page_none;
file->post_write_hook= maria_page_write_failure;
if (share->temporary)
{
file->post_read_hook= &maria_page_crc_check_none;
file->pre_write_hook= &maria_page_filler_set_none;
}
else
{
file->post_read_hook= &maria_page_crc_check_bitmap;
if (share->options & HA_OPTION_PAGE_CHECKSUM)
file->pre_write_hook= &maria_page_crc_set_normal;
else
file->pre_write_hook= &maria_page_filler_set_bitmap;
if (share->now_transactional)
file->flush_log_callback= flush_log_for_bitmap;
}
}
/**
Extends data file with zeroes and creates new bitmap pages into page cache .
Writes all bitmap pages in [ from , to ] .
Non - bitmap pages of zeroes are correct as they are marked empty in
bitmaps . Bitmap pages will not be zeroes : they will get their CRC fixed when
flushed . And if there is a crash before flush ( so they are zeroes at
restart ) , a REDO will re - create them in page cache .
*/
static my_bool
_ma_bitmap_create_missing_into_pagecache(MARIA_SHARE *share,
MARIA_FILE_BITMAP *bitmap,
pgcache_page_no_t from,
pgcache_page_no_t to,
uchar *zeroes)
{
pgcache_page_no_t i;
/*
We do not use my_chsize ( ) because there can be a race between when it
reads the physical size and when it writes ( assume data_file_length is 10 ,
physical length is 8 and two data pages are in cache , and here we do a
my_chsize : my_chsize sees physical length is 8 , then the two data pages go
to disk then my_chsize writes from page 8 and so overwrites the two data
pages , wrongly ) .
We instead rely on the filesystem filling gaps with zeroes .
*/
for (i= from; i <= to; i+= bitmap->pages_covered)
{
/**
No need to keep them pinned , they are new so flushable .
@ todo but we may want to keep them pinned , as an optimization : if they
are not pinned they may go to disk before the data pages go ( so , the
physical pages would be in non - ascending " sparse " order on disk ) , or the
filesystem may fill gaps with zeroes physically which is a waste of
time .
*/
if (pagecache_write(bitmap->file.pagecache,
&bitmap->file, i, 0 ,
zeroes, PAGECACHE_PLAIN_PAGE,
PAGECACHE_LOCK_LEFT_UNLOCKED,
PAGECACHE_PIN_LEFT_UNPINNED,
PAGECACHE_WRITE_DELAY, 0 , LSN_IMPOSSIBLE))
goto err;
}
/*
Data pages after data_file_length are full of zeroes but that is allowed
as they are marked empty in the bitmap .
*/
return FALSE ;
err:
_ma_set_fatal_error_with_share(share, my_errno);
return TRUE ;
}
/**
Creates missing bitmaps when we extend the data file .
At run - time , when we need a new bitmap page we come here ; and only one bitmap
page at a time is created .
In some recovery cases we insert at a large offset in the data file , way
beyond state . data_file_length , so can need to create more than one bitmap
page in one go . Known case is :
Start a transaction in Maria ;
delete last row of very large table ( with delete_row )
do a bulk insert
crash
Then UNDO_BULK_INSERT will truncate table files , and
UNDO_ROW_DELETE will want to put the row back to its original position ,
extending the data file a lot : bitmap page * s * in the hole must be created ,
or he table would look corrupted .
We need to log REDOs for bitmap creation , consider : we apply a REDO for a
data page , which creates the first data page covered by a new bitmap
not yet created . If the data page is flushed but the bitmap page is not and
there is a crash , re - execution of the REDO will complain about the zeroed
bitmap page ( see it as corruption ) . Thus a REDO is needed to re - create the
bitmap .
@ param info Maria handler
@ param bitmap Bitmap handler
@ param page Last bitmap page to create
@ note When this function is called this must be true :
( ( page + 1 ) * bitmap - > block_size > info - > s - > state . state . data_file_length )
*/
static my_bool _ma_bitmap_create_missing(MARIA_HA *info,
MARIA_FILE_BITMAP *bitmap,
pgcache_page_no_t page)
{
MARIA_SHARE *share= info->s;
uint block_size= bitmap->block_size;
pgcache_page_no_t from, to;
my_off_t data_file_length= share->state.state.data_file_length;
DBUG_ENTER("_ma_bitmap_create_missing" );
DBUG_PRINT("enter" , ("page: %lld" , (longlong) page));
/* First (in offset order) bitmap page to create */
if (data_file_length < block_size)
goto err; /* corrupted, should have first bitmap page */
if (page * block_size >= share->base.max_data_file_length)
{
my_errno= HA_ERR_RECORD_FILE_FULL;
goto err;
}
from= (data_file_length / block_size - 1 ) / bitmap->pages_covered + 1 ;
from*= bitmap->pages_covered;
/*
page > = from because :
( page + 1 ) * bs > dfl , and page = = k * pc so :
( k * pc + 1 ) * bs > dfl ; k * pc + 1 > dfl / bs ; k * pc > dfl / bs - 1
k > ( dfl / bs - 1 ) / pc ; k > = ( dfl / bs - 1 ) / pc + 1
k * pc > = ( ( dfl / bs - 1 ) / pc + 1 ) * pc = = from .
*/
DBUG_ASSERT(page >= from);
if (share->now_transactional)
{
LSN lsn;
uchar log_data[FILEID_STORE_SIZE + PAGE_STORE_SIZE * 2 ];
LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 1 ];
page_store(log_data + FILEID_STORE_SIZE, from);
page_store(log_data + FILEID_STORE_SIZE + PAGE_STORE_SIZE, page);
log_array[TRANSLOG_INTERNAL_PARTS + 0 ].str= log_data;
log_array[TRANSLOG_INTERNAL_PARTS + 0 ].length= sizeof (log_data);
/*
We don ' t use info - > trn so that this REDO is always executed even though
the UNDO does not reach disk due to crash . This is also consistent with
the fact that the new bitmap pages are not pinned .
*/
if (translog_write_record(&lsn, LOGREC_REDO_BITMAP_NEW_PAGE,
&dummy_transaction_object, info,
(translog_size_t)sizeof (log_data),
TRANSLOG_INTERNAL_PARTS + 1 , log_array,
log_data, NULL))
goto err;
/*
No need to flush the log : the bitmap pages we are going to create will
flush it when they go to disk .
*/
}
/*
Last bitmap page . It has special creation : will go to the page cache
only later as we are going to modify it very soon .
*/
bzero(bitmap->map, bitmap->block_size);
bitmap->used_size= bitmap->full_head_size= bitmap->full_tail_size= 0 ;
bitmap->changed=1 ;
#ifndef DBUG_OFF
/*
Make a copy of the page to be able to print out bitmap changes during
debugging
*/
memcpy(bitmap->map + bitmap->block_size, bitmap->map, bitmap->block_size);
#endif
/* Last bitmap page to create before 'page' */
DBUG_ASSERT(page >= bitmap->pages_covered);
to= page - bitmap->pages_covered;
/*
In run - time situations , from > = to is always false , i . e . we always create
one bitmap at a time ( ' page ' ) .
*/
if ((from <= to) &&
_ma_bitmap_create_missing_into_pagecache(share, bitmap, from, to,
bitmap->map))
goto err;
share->state.state.data_file_length= (page + 1 ) * bitmap->block_size;
if (info->s->tracked &&
_ma_update_tmp_file_size(&share->track_data,
share->state.state.data_file_length))
goto err;
DBUG_RETURN(FALSE );
err:
DBUG_RETURN(TRUE );
}
my_bool _ma_apply_redo_bitmap_new_page(MARIA_HA *info,
LSN lsn __attribute__ ((unused)),
const uchar *header)
{
MARIA_SHARE *share= info->s;
MARIA_FILE_BITMAP *bitmap= &share->bitmap;
my_bool error;
pgcache_page_no_t from, to, min_from;
DBUG_ENTER("_ma_apply_redo_bitmap_new_page" );
from= page_korr(header);
to= page_korr(header + PAGE_STORE_SIZE);
DBUG_PRINT("info" , ("from: %lu to: %lu" , (ulong)from, (ulong)to));
if ((from > to) ||
(from % bitmap->pages_covered) != 0 ||
(to % bitmap->pages_covered) != 0 )
{
error= TRUE ; /* corrupted log record */
goto err;
}
min_from= (share->state.state.data_file_length / bitmap->block_size - 1 ) /
bitmap->pages_covered + 1 ;
min_from*= bitmap->pages_covered;
if (from < min_from)
{
DBUG_PRINT("info" , ("overwrite bitmap pages from %lu" , (ulong)min_from));
/*
We have to overwrite . It could be that there was a bitmap page in
memory , covering a data page which went to disk , then crash : the
bitmap page is now full of zeros and is = = min_from , we have to overwrite
it with correct checksum .
*/
}
share->state.changed|= STATE_CHANGED;
bzero(info->buff, bitmap->block_size);
if (!(error=
_ma_bitmap_create_missing_into_pagecache(share, bitmap, from, to,
info->buff)))
share->state.state.data_file_length= (to + 1 ) * bitmap->block_size;
err:
DBUG_RETURN(error);
}
Messung V0.5 in Prozent C=94 H=73 G=83
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.158Bemerkung:
(vorverarbeitet am 2026-10-08)
¤
*Bot Zugriff