mirror of
				https://github.com/MariaDB/server.git
				synced 2025-11-04 12:56:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			891 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			891 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* Copyright (C) 2008 Sun AB and Michael Widenius
 | 
						|
 | 
						|
   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 */
 | 
						|
 | 
						|
/*
 | 
						|
  Functions to maintain live statistics for Maria transactional tables
 | 
						|
  and versioning for not transactional tables
 | 
						|
 | 
						|
  See WL#3138; Maria - fast "SELECT COUNT(*) FROM t;" and "CHECKSUM TABLE t"
 | 
						|
  for details about live number of rows and live checksums
 | 
						|
 | 
						|
  TODO
 | 
						|
   - Allocate MA_USED_TABLES and MA_HISTORY_STATE from a global pool (to
 | 
						|
     avoid calls to malloc()
 | 
						|
   - In trnamn_end_trans_hook(), don't call _ma_remove_not_visible_states()
 | 
						|
     every time. One could for example call it if there has been more than
 | 
						|
     10 ended transactions since last time it was called.
 | 
						|
*/
 | 
						|
 | 
						|
#include "maria_def.h"
 | 
						|
#include "trnman.h"
 | 
						|
#include "ma_trnman.h"
 | 
						|
#include "ma_blockrec.h"
 | 
						|
 | 
						|
/**
 | 
						|
   @brief Setup initial start-of-transaction state for a table
 | 
						|
 | 
						|
   @fn     _ma_setup_live_state
 | 
						|
   @param info		Maria handler
 | 
						|
 | 
						|
   @notes
 | 
						|
     This function ensures that trn->used_tables contains a list of
 | 
						|
     start and live states for tables that are part of the transaction
 | 
						|
     and that info->state points to the current live state for the table.
 | 
						|
 | 
						|
   @TODO
 | 
						|
     Change trn->table_list to a hash and share->state_history to a binary tree
 | 
						|
 | 
						|
   @return
 | 
						|
   @retval 0  ok
 | 
						|
   @retval 1  error (out of memory)
 | 
						|
*/
 | 
						|
 | 
						|
my_bool _ma_setup_live_state(MARIA_HA *info)
 | 
						|
{
 | 
						|
  TRN *trn;
 | 
						|
  MARIA_SHARE *share= info->s;
 | 
						|
  MARIA_USED_TABLES *tables;
 | 
						|
  MARIA_STATE_HISTORY *history;
 | 
						|
  DBUG_ENTER("_ma_setup_live_state");
 | 
						|
  DBUG_PRINT("enter", ("info: %p", info));
 | 
						|
 | 
						|
  DBUG_ASSERT(share->lock_key_trees);
 | 
						|
 | 
						|
  if (maria_create_trn_hook(info))
 | 
						|
    DBUG_RETURN(1);
 | 
						|
 | 
						|
  trn= info->trn;
 | 
						|
  for (tables= (MARIA_USED_TABLES*) trn->used_tables;
 | 
						|
       tables;
 | 
						|
       tables= tables->next)
 | 
						|
  {
 | 
						|
    if (tables->share == share)
 | 
						|
    {
 | 
						|
      /* Table is already used by transaction */
 | 
						|
      goto end;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /* Table was not used before, create new table state entry */
 | 
						|
  if (!(tables= (MARIA_USED_TABLES*) my_malloc(PSI_INSTRUMENT_ME,
 | 
						|
                                 sizeof(*tables), MYF(MY_WME | MY_ZEROFILL))))
 | 
						|
    DBUG_RETURN(1);
 | 
						|
  tables->next= trn->used_tables;
 | 
						|
  trn->used_tables= tables;
 | 
						|
  tables->share= share;
 | 
						|
 | 
						|
  mysql_mutex_lock(&share->intern_lock);
 | 
						|
  share->in_trans++;
 | 
						|
  DBUG_PRINT("info", ("share: %p  in_trans: %d",
 | 
						|
                     share, share->in_trans));
 | 
						|
 | 
						|
  history= share->state_history;
 | 
						|
 | 
						|
  /*
 | 
						|
    We must keep share locked to ensure that we don't access a history
 | 
						|
    link that is deleted by concurrently running checkpoint.
 | 
						|
 | 
						|
    It's enough to compare trids here (instead of calling
 | 
						|
    tranman_can_read_from) as history->trid is a commit_trid
 | 
						|
  */
 | 
						|
  while (trn->trid <= history->trid)
 | 
						|
    history= history->next;
 | 
						|
  mysql_mutex_unlock(&share->intern_lock);
 | 
						|
  /* The current item can't be deleted as it's the first one visible for us */
 | 
						|
  tables->state_start=  tables->state_current= history->state;
 | 
						|
  tables->state_current.changed= tables->state_current.no_transid= 0;
 | 
						|
 | 
						|
  DBUG_PRINT("info", ("records: %ld", (ulong) tables->state_start.records));
 | 
						|
 | 
						|
end:
 | 
						|
  info->state_start= &tables->state_start;
 | 
						|
  info->state= &tables->state_current;
 | 
						|
  info->used_tables= tables;
 | 
						|
  tables->use_count++;
 | 
						|
 | 
						|
  /*
 | 
						|
    Mark in transaction state if we are not using transid (versioning)
 | 
						|
    on rows. If not, then we will in _ma_trnman_end_trans_hook()
 | 
						|
    ensure that the state is visible for all at end of transaction
 | 
						|
  */
 | 
						|
  tables->state_current.no_transid|= !(info->row_flag & ROW_FLAG_TRANSID);
 | 
						|
 | 
						|
  DBUG_PRINT("exit", ("tables: %p  info->state: %p", tables, info->state));
 | 
						|
  DBUG_RETURN(0);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
   @brief Remove states that are not visible by anyone
 | 
						|
 | 
						|
   @fn   _ma_remove_not_visible_states()
 | 
						|
   @param org_history    List to history
 | 
						|
   @param all            1 if we should delete the first state if it's
 | 
						|
                         visible for all.  For the moment this is only used
 | 
						|
                         on close() of table.
 | 
						|
   @param trnman_is_locked  Set to 1 if we have already a lock on trnman.
 | 
						|
 | 
						|
   @notes
 | 
						|
     The assumption is that items in the history list is ordered by
 | 
						|
     commit_trid.
 | 
						|
 | 
						|
     A state is not visible anymore if there is no new transaction
 | 
						|
     that has been started between the commit_trid's of two states
 | 
						|
 | 
						|
     As long as some states exists, we keep the newest = (last commit)
 | 
						|
     state as first state in the history.  This is to allow us to just move
 | 
						|
     the history from the global list to the share when we open the table.
 | 
						|
 | 
						|
     Note that if 'all' is set trnman_is_locked must be 0, becasue
 | 
						|
     trnman_get_min_trid() will take a lock on trnman.
 | 
						|
 | 
						|
   @return
 | 
						|
   @retval Pointer to new history list
 | 
						|
*/
 | 
						|
 | 
						|
MARIA_STATE_HISTORY
 | 
						|
*_ma_remove_not_visible_states(MARIA_STATE_HISTORY *org_history,
 | 
						|
                               my_bool all,
 | 
						|
                               my_bool trnman_is_locked)
 | 
						|
{
 | 
						|
  TrID last_trid;
 | 
						|
  MARIA_STATE_HISTORY *history, **parent, *next;
 | 
						|
  DBUG_ENTER("_ma_remove_not_visible_states");
 | 
						|
 | 
						|
  if (!org_history)
 | 
						|
    DBUG_RETURN(0);                          /* Not versioned table */
 | 
						|
 | 
						|
  last_trid= org_history->trid;
 | 
						|
  parent= &org_history->next;
 | 
						|
  for (history= org_history->next; history; history= next)
 | 
						|
  {
 | 
						|
    next= history->next;
 | 
						|
    if (!trnman_exists_active_transactions(history->trid, last_trid,
 | 
						|
                                           trnman_is_locked))
 | 
						|
    {
 | 
						|
      DBUG_PRINT("info", ("removing history->trid: %lu  next: %lu",
 | 
						|
                          (ulong) history->trid, (ulong) last_trid));
 | 
						|
      my_free(history);
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
    *parent= history;
 | 
						|
    parent= &history->next;
 | 
						|
    last_trid= history->trid;
 | 
						|
  }
 | 
						|
  *parent= 0;
 | 
						|
 | 
						|
  if (all && parent == &org_history->next)
 | 
						|
  {
 | 
						|
    /* There is only one state left. Delete this if it's visible for all */
 | 
						|
    if (last_trid < trnman_get_min_trid())
 | 
						|
    {
 | 
						|
      my_free(org_history);
 | 
						|
      org_history= 0;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  DBUG_RETURN(org_history);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
   @brief Remove not used state history
 | 
						|
 | 
						|
   @param share          Maria table information
 | 
						|
   @param all            1 if we should delete the first state if it's
 | 
						|
                         visible for all.  For the moment this is only used
 | 
						|
                         on close() of table.
 | 
						|
 | 
						|
   @notes
 | 
						|
   share and trnman are not locked.
 | 
						|
 | 
						|
   We must first lock trnman and then share->intern_lock. This is becasue
 | 
						|
   _ma_trnman_end_trans_hook() has a lock on trnman and then
 | 
						|
   takes share->intern_lock.
 | 
						|
*/
 | 
						|
 | 
						|
void _ma_remove_not_visible_states_with_lock(MARIA_SHARE *share,
 | 
						|
                                             my_bool all)
 | 
						|
{
 | 
						|
  my_bool is_lock_trman;
 | 
						|
  if ((is_lock_trman= trman_is_inited()))
 | 
						|
    trnman_lock();
 | 
						|
 | 
						|
  mysql_mutex_lock(&share->intern_lock);
 | 
						|
  share->state_history=  _ma_remove_not_visible_states(share->state_history,
 | 
						|
                                                       all, 1);
 | 
						|
  mysql_mutex_unlock(&share->intern_lock);
 | 
						|
  if (is_lock_trman)
 | 
						|
    trnman_unlock();
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
  Free state history information from share->history and reset information
 | 
						|
  to current state.
 | 
						|
 | 
						|
  @notes
 | 
						|
  Used after repair/rename/drop as then all rows are visible for everyone
 | 
						|
*/
 | 
						|
 | 
						|
void _ma_reset_state(MARIA_HA *info)
 | 
						|
{
 | 
						|
  MARIA_SHARE *share= info->s;
 | 
						|
  MARIA_STATE_HISTORY *history= share->state_history;
 | 
						|
  DBUG_ENTER("_ma_reset_state");
 | 
						|
 | 
						|
  /* Always true if share->now_transactional is set */
 | 
						|
  if (history && share->have_versioning)
 | 
						|
  {
 | 
						|
    MARIA_STATE_HISTORY *next;
 | 
						|
    DBUG_PRINT("info", ("resetting history"));
 | 
						|
 | 
						|
    /* Set the current history to current state */
 | 
						|
    share->state_history->state= share->state.state;
 | 
						|
    /* Set current table handler to point to new history state */
 | 
						|
    info->state= info->state_start= &share->state_history->state;
 | 
						|
    for (history= history->next ; history ; history= next)
 | 
						|
    {
 | 
						|
      next= history->next;
 | 
						|
      my_free(history);
 | 
						|
    }
 | 
						|
    share->state_history->next= 0;
 | 
						|
    share->state_history->trid= 0;              /* Visible for all */
 | 
						|
  }
 | 
						|
  DBUG_VOID_RETURN;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/****************************************************************************
 | 
						|
  The following functions are called by thr_lock() in threaded applications
 | 
						|
  for not transactional tables
 | 
						|
****************************************************************************/
 | 
						|
 | 
						|
/*
 | 
						|
  Create a copy of the current status for the table
 | 
						|
 | 
						|
  SYNOPSIS
 | 
						|
    _ma_get_status()
 | 
						|
    param		Pointer to Myisam handler
 | 
						|
    concurrent_insert	Set to 1 if we are going to do concurrent inserts
 | 
						|
			(THR_WRITE_CONCURRENT_INSERT was used)
 | 
						|
*/
 | 
						|
 | 
						|
my_bool _ma_get_status(void* param, my_bool concurrent_insert)
 | 
						|
{
 | 
						|
  MARIA_HA *info=(MARIA_HA*) param;
 | 
						|
  DBUG_ENTER("_ma_get_status");
 | 
						|
  DBUG_PRINT("info",("key_file: %ld  data_file: %ld  concurrent_insert: %d",
 | 
						|
		     (long) info->s->state.state.key_file_length,
 | 
						|
		     (long) info->s->state.state.data_file_length,
 | 
						|
                     concurrent_insert));
 | 
						|
#ifndef DBUG_OFF
 | 
						|
  if (info->state->key_file_length > info->s->state.state.key_file_length ||
 | 
						|
      info->state->data_file_length > info->s->state.state.data_file_length)
 | 
						|
    DBUG_PRINT("warning",("old info:  key_file: %ld  data_file: %ld",
 | 
						|
			  (long) info->state->key_file_length,
 | 
						|
			  (long) info->state->data_file_length));
 | 
						|
#endif
 | 
						|
  info->state_save= info->s->state.state;
 | 
						|
  info->state= &info->state_save;
 | 
						|
  info->state->changed= 0;
 | 
						|
  info->append_insert_at_end= concurrent_insert;
 | 
						|
  DBUG_RETURN(0);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void _ma_update_status(void* param)
 | 
						|
{
 | 
						|
  MARIA_HA *info=(MARIA_HA*) param;
 | 
						|
  /*
 | 
						|
    Because someone may have closed the table we point at, we only
 | 
						|
    update the state if its our own state.  This isn't a problem as
 | 
						|
    we are always pointing at our own lock or at a read lock.
 | 
						|
    (This is enforced by thr_multi_lock.c)
 | 
						|
  */
 | 
						|
  if (info->state == &info->state_save)
 | 
						|
  {
 | 
						|
    MARIA_SHARE *share= info->s;
 | 
						|
#ifndef DBUG_OFF
 | 
						|
    DBUG_PRINT("info",("updating status:  key_file: %ld  data_file: %ld",
 | 
						|
		       (long) info->state->key_file_length,
 | 
						|
		       (long) info->state->data_file_length));
 | 
						|
    if (info->state->key_file_length < share->state.state.key_file_length ||
 | 
						|
	info->state->data_file_length < share->state.state.data_file_length)
 | 
						|
      DBUG_PRINT("warning",("old info:  key_file: %ld  data_file: %ld",
 | 
						|
			    (long) share->state.state.key_file_length,
 | 
						|
			    (long) share->state.state.data_file_length));
 | 
						|
#endif
 | 
						|
    /*
 | 
						|
      we are going to modify the state without lock's log, this would break
 | 
						|
      recovery if done with a transactional table.
 | 
						|
    */
 | 
						|
    DBUG_ASSERT(!info->s->base.born_transactional);
 | 
						|
    share->state.state= *info->state;
 | 
						|
    info->state= &share->state.state;
 | 
						|
    DBUG_PRINT("info", ("invalidator... '%s' (status update)",
 | 
						|
                        info->s->data_file_name.str));
 | 
						|
    DBUG_ASSERT(info->s->chst_invalidator != NULL);
 | 
						|
    (*info->s->chst_invalidator)((const char *)info->s->data_file_name.str);
 | 
						|
 | 
						|
  }
 | 
						|
  info->append_insert_at_end= 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
  Same as ma_update_status() but take a lock in the table lock, to protect
 | 
						|
  against someone calling ma_get_status() from thr_lock() at the same time.
 | 
						|
*/
 | 
						|
 | 
						|
void _ma_update_status_with_lock(MARIA_HA *info)
 | 
						|
{
 | 
						|
  my_bool locked= 0;
 | 
						|
  if (info->state == &info->state_save)
 | 
						|
  {
 | 
						|
    locked= 1;
 | 
						|
    mysql_mutex_lock(&info->s->lock.mutex);
 | 
						|
  }
 | 
						|
  (*info->s->lock.update_status)(info->lock.status_param);
 | 
						|
  if (locked)
 | 
						|
    mysql_mutex_unlock(&info->s->lock.mutex);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void _ma_restore_status(void *param)
 | 
						|
{
 | 
						|
  MARIA_HA *info= (MARIA_HA*) param;
 | 
						|
  info->state= &info->s->state.state;
 | 
						|
  info->append_insert_at_end= 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void _ma_copy_status(void* to, void *from)
 | 
						|
{
 | 
						|
  ((MARIA_HA*) to)->state= &((MARIA_HA*) from)->state_save;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
my_bool _ma_reset_update_flag(void *param,
 | 
						|
                              my_bool concurrent_insert __attribute__((unused)))
 | 
						|
{
 | 
						|
  MARIA_HA *info=(MARIA_HA*) param;
 | 
						|
  info->state->changed= 0;
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
my_bool _ma_start_trans(void* param)
 | 
						|
{
 | 
						|
  MARIA_HA *info=(MARIA_HA*) param;
 | 
						|
  if (!info->s->lock_key_trees)
 | 
						|
  {
 | 
						|
    info->state=  info->state_start;
 | 
						|
    *info->state= info->s->state.state;
 | 
						|
  }
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
   @brief Check if should allow concurrent inserts
 | 
						|
 | 
						|
   @implementation
 | 
						|
     Allow concurrent inserts if we don't have a hole in the table or
 | 
						|
     if there is no active write lock and there is active read locks and
 | 
						|
     maria_concurrent_insert == 2. In this last case the new
 | 
						|
     row('s) are inserted at end of file instead of filling up the hole.
 | 
						|
 | 
						|
     The last case is to allow one to inserts into a heavily read-used table
 | 
						|
     even if there is holes.
 | 
						|
 | 
						|
   @notes
 | 
						|
     If there is a an rtree indexes in the table, concurrent inserts are
 | 
						|
     disabled in maria_open()
 | 
						|
 | 
						|
  @return
 | 
						|
  @retval 0  ok to use concurrent inserts
 | 
						|
  @retval 1  not ok
 | 
						|
*/
 | 
						|
 | 
						|
my_bool _ma_check_status(void *param)
 | 
						|
{
 | 
						|
  MARIA_HA *info=(MARIA_HA*) param;
 | 
						|
  /*
 | 
						|
    The test for w_locks == 1 is here because this thread has already done an
 | 
						|
    external lock (in other words: w_locks == 1 means no other threads has
 | 
						|
    a write lock)
 | 
						|
  */
 | 
						|
  DBUG_PRINT("info",("dellink: %ld  r_locks: %u  w_locks: %u",
 | 
						|
                     (long) info->s->state.dellink, (uint) info->s->r_locks,
 | 
						|
                     (uint) info->s->w_locks));
 | 
						|
  return (my_bool) !(info->s->state.dellink == HA_OFFSET_ERROR ||
 | 
						|
                     (maria_concurrent_insert == 2 && info->s->r_locks &&
 | 
						|
                      info->s->w_locks == 1));
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
   @brief write hook at end of trans to store status for all used table
 | 
						|
 | 
						|
   @Notes
 | 
						|
   This function must be called under trnman_lock in trnman_end_trn()
 | 
						|
   because of the following reasons:
 | 
						|
   - After trnman_end_trn() is called, the current transaction will be
 | 
						|
   regarded as committed and all used tables state_history will be
 | 
						|
   visible to other transactions.  To do this, we loop over all used
 | 
						|
   tables and create/update a history entries that contains the correct
 | 
						|
   state_history for them.
 | 
						|
*/
 | 
						|
 | 
						|
my_bool _ma_trnman_end_trans_hook(TRN *trn, my_bool commit,
 | 
						|
                                  my_bool active_transactions)
 | 
						|
{
 | 
						|
  my_bool error= 0;
 | 
						|
  MARIA_USED_TABLES *tables, *next;
 | 
						|
  DBUG_ENTER("_ma_trnman_end_trans_hook");
 | 
						|
  DBUG_PRINT("enter", ("trn: %p  used_tables: %p", trn, trn->used_tables));
 | 
						|
 | 
						|
  for (tables= (MARIA_USED_TABLES*) trn->used_tables;
 | 
						|
       tables;
 | 
						|
       tables= next)
 | 
						|
  {
 | 
						|
    MARIA_SHARE *share= tables->share;
 | 
						|
    next= tables->next;
 | 
						|
    if (commit)
 | 
						|
    {
 | 
						|
      MARIA_STATE_HISTORY *history;
 | 
						|
 | 
						|
      mysql_mutex_lock(&share->intern_lock);
 | 
						|
 | 
						|
      /* We only have to update history state if something changed */
 | 
						|
      if (tables->state_current.changed)
 | 
						|
      {
 | 
						|
        if (tables->state_current.no_transid)
 | 
						|
        {
 | 
						|
          /*
 | 
						|
            The change was done without using transid on rows (like in
 | 
						|
            bulk insert). In this case this thread is the only one
 | 
						|
            that is using the table and all rows will be visible
 | 
						|
            for all transactions.
 | 
						|
          */
 | 
						|
          _ma_reset_history(share);
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
          if (active_transactions && share->now_transactional &&
 | 
						|
              trnman_exists_active_transactions(share->state_history->trid,
 | 
						|
                                                trn->commit_trid, 1))
 | 
						|
          {
 | 
						|
            /*
 | 
						|
              There exist transactions that are still using the current
 | 
						|
              share->state_history.  Create a new history item for this
 | 
						|
              commit and add it first in the state_history list. This
 | 
						|
              ensures that all history items are stored in the list in
 | 
						|
              decresing trid order.
 | 
						|
            */
 | 
						|
            if (!(history= my_malloc(PSI_INSTRUMENT_ME, sizeof(*history),
 | 
						|
                                     MYF(MY_WME))))
 | 
						|
            {
 | 
						|
              /* purecov: begin inspected */
 | 
						|
              error= 1;
 | 
						|
              mysql_mutex_unlock(&share->intern_lock);
 | 
						|
              my_free(tables);
 | 
						|
              continue;
 | 
						|
              /* purecov: end */
 | 
						|
            }
 | 
						|
            history->state= share->state_history->state;
 | 
						|
            history->next= share->state_history;
 | 
						|
            share->state_history= history;
 | 
						|
          }
 | 
						|
          else
 | 
						|
          {
 | 
						|
            /* Previous history can't be seen by anyone, reuse old memory */
 | 
						|
            history= share->state_history;
 | 
						|
            DBUG_PRINT("info", ("removing history->trid: %lu  new: %lu",
 | 
						|
                                (ulong) history->trid,
 | 
						|
                                (ulong) trn->commit_trid));
 | 
						|
          }
 | 
						|
 | 
						|
          history->state.records+= (tables->state_current.records -
 | 
						|
                                    tables->state_start.records);
 | 
						|
          history->state.checksum+= (tables->state_current.checksum -
 | 
						|
                                     tables->state_start.checksum);
 | 
						|
          history->trid= trn->commit_trid;
 | 
						|
 | 
						|
          share->state.last_change_trn= trn->commit_trid;
 | 
						|
 | 
						|
          if (history->next)
 | 
						|
          {
 | 
						|
            /* Remove not visible states */
 | 
						|
            share->state_history= _ma_remove_not_visible_states(history, 0, 1);
 | 
						|
          }
 | 
						|
          DBUG_PRINT("info", ("share: %p in_trans: %d",
 | 
						|
                              share, share->in_trans));
 | 
						|
        }
 | 
						|
      }
 | 
						|
      /* The following calls frees &share->intern_lock */
 | 
						|
      decrement_share_in_trans(share);
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
      /*
 | 
						|
        We need to keep share->in_trans correct because of the check
 | 
						|
        in free_maria_share()
 | 
						|
      */
 | 
						|
      mysql_mutex_lock(&share->intern_lock);
 | 
						|
      decrement_share_in_trans(share);
 | 
						|
    }
 | 
						|
    my_free(tables);
 | 
						|
  }
 | 
						|
  trn->used_tables= 0;
 | 
						|
  trn->used_instances= 0;
 | 
						|
  DBUG_RETURN(error);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
   Remove table from trnman_list
 | 
						|
 | 
						|
   @notes
 | 
						|
     This is used when we unlock a table from a group of locked tables
 | 
						|
     just before doing a rename or drop table.
 | 
						|
 | 
						|
     share->internal_lock must be locked when function is called
 | 
						|
*/
 | 
						|
 | 
						|
void _ma_remove_table_from_trnman(MARIA_HA *info)
 | 
						|
{
 | 
						|
  MARIA_SHARE *share= info->s;
 | 
						|
  TRN *trn= info->trn;
 | 
						|
  MARIA_USED_TABLES *tables, **prev;
 | 
						|
  DBUG_ENTER("_ma_remove_table_from_trnman");
 | 
						|
  DBUG_PRINT("enter", ("trn: %p  used_tables: %p  share: %p  in_trans: %d",
 | 
						|
                       trn, trn->used_tables, share, share->in_trans));
 | 
						|
 | 
						|
  mysql_mutex_assert_owner(&share->intern_lock);
 | 
						|
 | 
						|
  if (trn == &dummy_transaction_object)
 | 
						|
    DBUG_VOID_RETURN;
 | 
						|
 | 
						|
  /* First remove share from used_tables */
 | 
						|
  for (prev= (MARIA_USED_TABLES**) (char*) &trn->used_tables;
 | 
						|
       (tables= *prev);
 | 
						|
       prev= &tables->next)
 | 
						|
  {
 | 
						|
    if (tables->share == share)
 | 
						|
    {
 | 
						|
      *prev= tables->next;
 | 
						|
      /*
 | 
						|
        We don't have to and can't call decrement_share_in_trans(share) here
 | 
						|
        as we know there is an active MARIA_HA handler around.
 | 
						|
      */
 | 
						|
      share->in_trans--;
 | 
						|
      my_free(tables);
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if (!tables)
 | 
						|
  {
 | 
						|
    /*
 | 
						|
      This can only happens in case of rename of intermediate table as
 | 
						|
      part of alter table
 | 
						|
    */
 | 
						|
    DBUG_PRINT("warning", ("share: %p where not in used_tables_list", share));
 | 
						|
  }
 | 
						|
 | 
						|
  /* Reset trn and remove table from used_instances */
 | 
						|
  _ma_reset_trn_for_table(info);
 | 
						|
 | 
						|
  DBUG_VOID_RETURN;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/****************************************************************************
 | 
						|
  The following functions are called by thr_lock() in threaded applications
 | 
						|
  for transactional tables.
 | 
						|
****************************************************************************/
 | 
						|
 | 
						|
/*
 | 
						|
  Create a copy of the current status for the table
 | 
						|
 | 
						|
  SYNOPSIS
 | 
						|
    _ma_get_status()
 | 
						|
    param		Pointer to Aria handler
 | 
						|
    concurrent_insert	Set to 1 if we are going to do concurrent inserts
 | 
						|
			(THR_WRITE_CONCURRENT_INSERT was used)
 | 
						|
*/
 | 
						|
 | 
						|
my_bool _ma_block_get_status(void* param, my_bool concurrent_insert)
 | 
						|
{
 | 
						|
  MARIA_HA *info=(MARIA_HA*) param;
 | 
						|
  DBUG_ENTER("_ma_block_get_status");
 | 
						|
  DBUG_PRINT("enter", ("concurrent_insert %d", concurrent_insert));
 | 
						|
 | 
						|
  info->row_base_length= info->s->base_length;
 | 
						|
  info->row_flag= info->s->base.default_row_flag;
 | 
						|
  DBUG_ASSERT(!concurrent_insert ||
 | 
						|
              info->lock.type == TL_WRITE_CONCURRENT_INSERT);
 | 
						|
  if (concurrent_insert || !info->autocommit)
 | 
						|
  {
 | 
						|
    info->row_flag|= ROW_FLAG_TRANSID;
 | 
						|
    info->row_base_length+= TRANSID_SIZE;
 | 
						|
  }
 | 
						|
  else
 | 
						|
  {
 | 
						|
    DBUG_ASSERT(info->lock.type != TL_WRITE_CONCURRENT_INSERT);
 | 
						|
  }
 | 
						|
  DBUG_RETURN(0);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
my_bool _ma_block_start_trans(void* param)
 | 
						|
{
 | 
						|
  MARIA_HA *info=(MARIA_HA*) param;
 | 
						|
  DBUG_ENTER("_ma_block_start_trans");
 | 
						|
 | 
						|
  if (info->s->lock_key_trees)
 | 
						|
  {
 | 
						|
    /*
 | 
						|
      Assume for now that this doesn't fail (It can only fail in
 | 
						|
      out of memory conditions)
 | 
						|
      TODO: Fix this by having one extra state pre-allocated
 | 
						|
    */
 | 
						|
    DBUG_RETURN(_ma_setup_live_state(info));
 | 
						|
  }
 | 
						|
  else
 | 
						|
  {
 | 
						|
    /*
 | 
						|
      We come here in the following cases:
 | 
						|
      - The table is a temporary table
 | 
						|
      - It's a table which is crash safe but not yet versioned, for
 | 
						|
      example a table with fulltext or rtree keys
 | 
						|
 | 
						|
      Set the current state to point to save_state so that the
 | 
						|
      block_format code don't count the same record twice.
 | 
						|
      Copy also the current state. This may have been wrong if the
 | 
						|
      same file was used several times in the last statement
 | 
						|
    */
 | 
						|
    info->state=  info->state_start;
 | 
						|
    *info->state= info->s->state.state;
 | 
						|
  }
 | 
						|
 | 
						|
  /*
 | 
						|
    Info->trn is set if this table is already handled and we are
 | 
						|
    called from maria_versioning()
 | 
						|
  */
 | 
						|
  if (info->s->base.born_transactional && !info->trn)
 | 
						|
  {
 | 
						|
    /*
 | 
						|
      Assume for now that this doesn't fail (It can only fail in
 | 
						|
      out of memory conditions)
 | 
						|
    */
 | 
						|
    DBUG_RETURN(maria_create_trn_hook(info) != 0);
 | 
						|
  }
 | 
						|
  DBUG_RETURN(0);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void _ma_block_update_status(void *param __attribute__((unused)))
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
void _ma_block_restore_status(void *param __attribute__((unused)))
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
  Check if should allow concurrent inserts
 | 
						|
 | 
						|
  @return
 | 
						|
  @retval 0  ok to use concurrent inserts
 | 
						|
  @retval 1  not ok
 | 
						|
*/
 | 
						|
 | 
						|
my_bool _ma_block_check_status(void *param __attribute__((unused)))
 | 
						|
{
 | 
						|
  return (my_bool) 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* Get status when transactional but not versioned */
 | 
						|
 | 
						|
my_bool _ma_block_start_trans_no_versioning(void* param)
 | 
						|
{
 | 
						|
  MARIA_HA *info=(MARIA_HA*) param;
 | 
						|
  DBUG_ENTER("_ma_block_start_trans_no_versioning");
 | 
						|
  DBUG_ASSERT(info->s->base.born_transactional && !info->s->lock_key_trees);
 | 
						|
 | 
						|
  info->state->changed= 0;              /* from _ma_reset_update_flag() */
 | 
						|
  info->state=  info->state_start;
 | 
						|
  *info->state= info->s->state.state;
 | 
						|
  if (!info->trn)
 | 
						|
  {
 | 
						|
    /*
 | 
						|
      Assume for now that this doesn't fail (It can only fail in
 | 
						|
      out of memory conditions)
 | 
						|
    */
 | 
						|
    DBUG_RETURN(maria_create_trn_hook(info));
 | 
						|
  }
 | 
						|
  DBUG_RETURN(0);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
  Enable/disable versioning
 | 
						|
*/
 | 
						|
 | 
						|
void maria_versioning(MARIA_HA *info, my_bool versioning)
 | 
						|
{
 | 
						|
  MARIA_SHARE *share= info->s;
 | 
						|
  DBUG_ENTER("maria_versioning");
 | 
						|
 | 
						|
  /* For now, this is a hack */
 | 
						|
  if (share->have_versioning)
 | 
						|
  {
 | 
						|
    enum thr_lock_type save_lock_type;
 | 
						|
    share->lock_key_trees= versioning;
 | 
						|
    /* Set up info->lock.type temporary for _ma_block_get_status() */
 | 
						|
    save_lock_type= info->lock.type;
 | 
						|
    info->lock.type= versioning ? TL_WRITE_CONCURRENT_INSERT : TL_WRITE;
 | 
						|
    _ma_block_get_status((void*) info, versioning);
 | 
						|
    info->lock.type= save_lock_type;
 | 
						|
    if (versioning)
 | 
						|
      info->state= &share->state.common;
 | 
						|
    else
 | 
						|
      info->state= &share->state.state;	/* Change global values by default */
 | 
						|
    info->state_start= info->state;             /* Initial values */
 | 
						|
  }
 | 
						|
  DBUG_VOID_RETURN;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
   Update data_file_length to new length
 | 
						|
 | 
						|
   NOTES
 | 
						|
     Only used by block records
 | 
						|
*/
 | 
						|
 | 
						|
int _ma_set_share_data_file_length(MARIA_HA *info, ulonglong new_length)
 | 
						|
{
 | 
						|
  MARIA_SHARE *share= info->s;
 | 
						|
  my_bool updated= 0;
 | 
						|
  if (!share->internal_table)
 | 
						|
    mysql_mutex_lock(&share->intern_lock);
 | 
						|
  if (share->state.state.data_file_length < new_length)
 | 
						|
  {
 | 
						|
    updated= share->tracked;
 | 
						|
    share->state.state.data_file_length= new_length;
 | 
						|
    if (new_length >= share->base.max_data_file_length)
 | 
						|
    {
 | 
						|
      /* Give an error on next insert */
 | 
						|
      share->state.changed|= STATE_DATA_FILE_FULL;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if (!share->internal_table)
 | 
						|
    mysql_mutex_unlock(&share->intern_lock);
 | 
						|
  else
 | 
						|
  {
 | 
						|
    if (updated &&
 | 
						|
        _ma_update_tmp_file_size(&share->track_data,
 | 
						|
                                 share->state.state.data_file_length))
 | 
						|
    {
 | 
						|
      share->state.changed|= STATE_DATA_FILE_FULL;
 | 
						|
      return 1;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
   Copy state information that where updated while the table was used
 | 
						|
   in not transactional mode
 | 
						|
*/
 | 
						|
 | 
						|
void _ma_copy_nontrans_state_information(MARIA_HA *info)
 | 
						|
{
 | 
						|
  info->s->state.state.records=          info->state->records;
 | 
						|
  info->s->state.state.checksum=         info->state->checksum;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
   Reset history
 | 
						|
   This is only called during repair when we are the only one using the table.
 | 
						|
*/
 | 
						|
 | 
						|
void _ma_reset_history(MARIA_SHARE *share)
 | 
						|
{
 | 
						|
  MARIA_STATE_HISTORY *history, *next;
 | 
						|
  DBUG_ENTER("_ma_reset_history");
 | 
						|
 | 
						|
  share->state_history->trid= 0;          /* Visibly by all */
 | 
						|
  share->state_history->state= share->state.state;
 | 
						|
  history= share->state_history->next;
 | 
						|
  share->state_history->next= 0;
 | 
						|
 | 
						|
  for (; history; history= next)
 | 
						|
  {
 | 
						|
    next= history->next;
 | 
						|
    my_free(history);
 | 
						|
  }
 | 
						|
  DBUG_VOID_RETURN;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/****************************************************************************
 | 
						|
  Virtual functions to check if row is visible
 | 
						|
****************************************************************************/
 | 
						|
 | 
						|
/**
 | 
						|
   Row is always visible
 | 
						|
   This is for tables without concurrent insert
 | 
						|
*/
 | 
						|
 | 
						|
my_bool _ma_row_visible_always(MARIA_HA *info __attribute__((unused)))
 | 
						|
{
 | 
						|
  return 1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
   Row visibility for non transactional tables with concurrent insert
 | 
						|
 | 
						|
   @implementation
 | 
						|
   When we got our table lock, we saved the current
 | 
						|
   data_file_length. Concurrent inserts always go to the end of the
 | 
						|
   file. So we can test if the found key references a new record.
 | 
						|
*/
 | 
						|
 | 
						|
my_bool _ma_row_visible_non_transactional_table(MARIA_HA *info)
 | 
						|
{
 | 
						|
  return info->cur_row.lastpos < info->state->data_file_length;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
   Row visibility for transactional tables with versioning
 | 
						|
 | 
						|
 | 
						|
   @TODO
 | 
						|
   Add test if found key was marked deleted and it was deleted by
 | 
						|
   us. In that case we should return 0
 | 
						|
*/
 | 
						|
 | 
						|
my_bool _ma_row_visible_transactional_table(MARIA_HA *info)
 | 
						|
{
 | 
						|
  return trnman_can_read_from(info->trn, info->cur_row.trid);
 | 
						|
}
 |