/* First 4 bytes in the .par file is the number of 32-bit words in the file */ #define PAR_WORD_SIZE 4 /* offset to the .par file checksum */ #define PAR_CHECKSUM_OFFSET 4 /* offset to the total number of partitions */ #define PAR_NUM_PARTS_OFFSET 8 /* offset to the engines array */ #define PAR_ENGINES_OFFSET 12 #define PARTITION_ENABLED_TABLE_FLAGS (HA_FILE_BASED | \
HA_REC_NOT_IN_SEQ | \
HA_CAN_REPAIR | \
HA_REUSES_FILE_NAMES) #define PARTITION_DISABLED_TABLE_FLAGS (HA_DUPLICATE_POS | \
HA_CAN_INSERT_DELAYED | \
HA_READ_BEFORE_WRITE_REMOVAL |\
HA_CAN_TABLES_WITHOUT_ROLLBACK)
/* m_part_info is only NULL when we failed to create a partition table */ if (m_part_info)
{
part= m_part_info->partitions.head(); /* part->engine_type may be 0 when we failed to create the partition */ if (part->engine_type &&
(part->engine_type)->create_partitioning_metadata &&
((part->engine_type)->create_partitioning_metadata)(path, old_path,
action_flag))
{
my_error(ER_CANT_CREATE_HANDLER_FILE, MYF(0));
DBUG_RETURN(1);
}
}
DBUG_RETURN(0);
}
int ha_partition::analyze(THD *thd, HA_CHECK_OPT *check_opt)
{
DBUG_ENTER("ha_partition::analyze");
int result= handle_opt_partitions(thd, check_opt, ANALYZE_PARTS);
if ((result == 0) && m_file[0]
&& (m_file[0]->ha_table_flags() & HA_ONLINE_ANALYZE))
{ /* If this is ANALYZE TABLE that will not force table definition cache
eviction, update statistics for the partition handler. */ this->info(HA_STATUS_CONST | HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK);
}
/*
Cleanup by removing all created partitions after error
SYNOPSIS
cleanup_new_partition()
part_count Number of partitions to remove
RETURN VALUE
NONE
DESCRIPTION
This functionis called immediately after prepare_new_partition() in case the latter fails.
In prepare_new_partition() last call that may return failure is
external_lock(). That means if prepare_new_partition() fails,
partition does not have external lock. Thus no need to call
external_lock(F_UNLCK) here.
TODO:
We must ensure that in the case that we get an error during the process
that we call external_lock with F_UNLCK, close the table and delete the
table in the case where we have been successful with prepare_handler.
We solve this by keeping an arrayof successful calls to prepare_handler
which can then be used to undo the call.
*/
/*
Implement the partition changes defined by ALTER TABLE of partitions
SYNOPSIS
change_partitions()
create_info HA_CREATE_INFO object describing all
fields and indexes in table
path Complete path of db and table name out: copied Output parameter where number of copied
records are added out: deleted Output parameter where number of deleted
records are added
pack_frm_data Reference topacked frm file
pack_frm_len Length ofpacked frm file
RETURN VALUE
>0 Failure 0 Success
DESCRIPTION
Add and copy if needed a number of partitions, during this operation
no other operation is ongoing in the server. This is used by
ADD PARTITION all types as well as by REORGANIZE PARTITION. For
one-phased implementations it is used also by DROP and COALESCE
PARTITIONs.
One-phased implementation needs the new frm file, other handlers will
get zero length and a NULL reference here.
*/
/*
Assert that it works without HA_FILE_BASED and lower_case_table_name = 2.
We use m_file[0] as long as all partitions have the same storage engine.
*/
DBUG_ASSERT(m_file[0]->is_canonical_filename(Lex_cstring_strlen(path)));
m_reorged_parts= 0; if (!m_part_info->is_sub_partitioned())
num_subparts= 1;
/*
Step 1:
Calculate number of reorganised partitions and allocate space for
their handler references.
*/ if (temp_partitions)
{
m_reorged_parts= temp_partitions * num_subparts;
} else
{ do
{
partition_element *part_elem= part_it++; if (part_elem->part_state == PART_CHANGED ||
part_elem->part_state == PART_REORGED_DROPPED)
{
m_reorged_parts+= num_subparts;
}
} while (++i < num_parts);
} if (m_reorged_parts &&
!(m_reorged_file= thd->calloc<handler*>(m_reorged_parts + 1)))
{
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
}
/*
Step 2:
Calculate number of partitions after change and allocate space for
their handler references.
*/
num_remain_partitions= 0; if (temp_partitions)
{
num_remain_partitions= num_parts * num_subparts;
} else
{
part_it.rewind();
i= 0; do
{
partition_element *part_elem= part_it++; if (part_elem->part_state == PART_NORMAL ||
part_elem->part_state == PART_TO_BE_ADDED ||
part_elem->part_state == PART_CHANGED)
{
num_remain_partitions+= num_subparts;
}
} while (++i < num_parts);
} if (!(new_file_array= thd->calloc<handler*>(2*(num_remain_partitions + 1))))
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
m_added_file= &new_file_array[num_remain_partitions + 1];
/*
Step 3:
Fill m_reorged_file with handler references and NULL at the end
*/ if (m_reorged_parts)
{
i= 0;
part_count= 0;
first= TRUE;
part_it.rewind(); do
{
partition_element *part_elem= part_it++; if (part_elem->part_state == PART_CHANGED ||
part_elem->part_state == PART_REORGED_DROPPED)
{
memcpy((void*)&m_reorged_file[part_count],
(void*)&m_file[i*num_subparts],
sizeof(handler*)*num_subparts);
part_count+= num_subparts;
} elseif (first && temp_partitions &&
part_elem->part_state == PART_TO_BE_ADDED)
{
/*
When doing an ALTER TABLE REORGANIZE PARTITION a number of
partitions isto be reorganised into a setof new partitions.
The reorganised partitions are in this casein the temp_partitions
list. We copy all of them in one batch and thus we only do this until we find the first partition with state PART_TO_BE_ADDED
since this is where the new partitions go inand where the old
ones used to be.
*/
first= FALSE;
DBUG_ASSERT(((i*num_subparts) + m_reorged_parts) <= m_file_tot_parts);
memcpy((void*)m_reorged_file, &m_file[i*num_subparts],
sizeof(handler*)*m_reorged_parts);
}
} while (++i < num_parts);
}
/*
Step 4:
Fill new_array_file with handler references. Create the handlers if
needed.
*/
i= 0;
part_count= 0;
orig_count= 0;
first= TRUE;
part_it.rewind(); do
{
partition_element *part_elem= part_it++; if (part_elem->part_state == PART_NORMAL)
{
DBUG_ASSERT(orig_count + num_subparts <= m_file_tot_parts);
memcpy((void*)&new_file_array[part_count], (void*)&m_file[orig_count],
sizeof(handler*)*num_subparts);
part_count+= num_subparts;
orig_count+= num_subparts;
} elseif (part_elem->part_state == PART_CHANGED ||
part_elem->part_state == PART_TO_BE_ADDED)
{
uint j= 0;
Parts_share_refs *p_share_refs;
/*
The Handler_shares for each partition's handler can be allocated
within this handler, since there will not be any more instances of the
new partitions, until the table is reopened after the ALTER succeeded.
*/
p_share_refs= new Parts_share_refs; if (!p_share_refs)
DBUG_RETURN(HA_ERR_OUT_OF_MEM); if (p_share_refs->init(num_subparts))
DBUG_RETURN(HA_ERR_OUT_OF_MEM); if (m_new_partitions_share_refs.push_back(p_share_refs, thd->mem_root))
DBUG_RETURN(HA_ERR_OUT_OF_MEM); do
{
handler **new_file= &new_file_array[part_count++]; if (!(*new_file=
get_new_handler(table->s,
thd->mem_root,
part_elem->engine_type)))
{
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
} if ((*new_file)->set_ha_share_ref(&p_share_refs->ha_shares[j]))
{
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
}
} while (++j < num_subparts); if (part_elem->part_state == PART_CHANGED)
orig_count+= num_subparts; elseif (temp_partitions && first)
{
orig_count+= (num_subparts * temp_partitions);
first= FALSE;
}
}
} while (++i < num_parts);
first= FALSE;
/*
Step 5:
Create the new partitions and also open, lock and call external_lock
on them to prepare them for copy phase and also for later close
calls
*/
i= 0;
part_count= 0;
part_it.rewind(); do
{
partition_element *part_elem= part_it++; if (part_elem->part_state == PART_TO_BE_ADDED ||
part_elem->part_state == PART_CHANGED)
{
/*
A new partition needs to be created PART_TO_BE_ADDED means an
entirely new partition and PART_CHANGED means a changed partition
that will still exist with either more or less data in it.
*/
uint name_variant= NORMAL_PART_NAME; if (part_elem->part_state == PART_CHANGED ||
(part_elem->part_state == PART_TO_BE_ADDED && temp_partitions))
name_variant= TEMP_PART_NAME; if (m_part_info->is_sub_partitioned())
{
List_iterator<partition_element> sub_it(part_elem->subpartitions);
uint j= 0, part; do
{
partition_element *sub_elem= sub_it++; if (unlikely((error=
create_subpartition_name(part_name_buff,
sizeof(part_name_buff), path,
part_elem->partition_name,
sub_elem->partition_name,
name_variant))))
{
cleanup_new_partition(part_count);
DBUG_RETURN(error);
}
part= i * num_subparts + j;
DBUG_PRINT("info", ("Add subpartition %s", part_name_buff)); if (unlikely((error=
prepare_new_partition(table, create_info,
new_file_array[part],
(constchar *)part_name_buff,
sub_elem))))
{
cleanup_new_partition(part_count);
DBUG_RETURN(error);
}
m_added_file[part_count++]= new_file_array[i];
}
}
} while (++i < num_parts);
/*
Step 6:
State update to prepare for next write of the frm file.
*/
i= 0;
part_it.rewind(); do
{
partition_element *part_elem= part_it++; if (part_elem->part_state == PART_TO_BE_ADDED)
part_elem->part_state= PART_IS_ADDED; elseif (part_elem->part_state == PART_CHANGED)
part_elem->part_state= PART_IS_CHANGED; elseif (part_elem->part_state == PART_REORGED_DROPPED)
part_elem->part_state= PART_TO_BE_DROPPED;
} while (++i < num_parts); for (i= 0; i < temp_partitions; i++)
{
partition_element *part_elem= t_it++;
DBUG_ASSERT(part_elem->part_state == PART_TO_BE_REORGED);
part_elem->part_state= PART_TO_BE_DROPPED;
}
DBUG_ASSERT(m_new_file == 0);
m_new_file= new_file_array; for (i= 0; i < part_count; i++)
m_added_file[i]->extra(HA_EXTRA_BEGIN_COPY);
error= copy_partitions(copied, deleted); for (i= 0; i < part_count; i++)
m_added_file[i]->extra(error
? HA_EXTRA_ABORT_COPY
: HA_EXTRA_END_COPY); if (unlikely(error))
{
/*
Close and unlock the new temporary partitions.
They will later be deleted through the ddl-log.
*/
cleanup_new_partition(part_count);
m_new_file= 0;
}
DBUG_RETURN(error);
}
/*
Copy partitions as part of ALTER TABLE of partitions
SYNOPSIS
copy_partitions() out:copied Number of records copied out:deleted Number of records deleted
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
change_partitions has done all the preparations, now it is time to
actually copy the data from the reorganised partitions to the new
partitions.
*/
int ha_partition::copy_partitions(ulonglong * const copied,
ulonglong * const deleted)
{
uint reorg_part= 0;
int result= 0;
longlong func_value;
DBUG_ENTER("ha_partition::copy_partitions");
if (m_part_info->linear_hash_ind)
{ if (m_part_info->part_type == HASH_PARTITION)
set_linear_hash_mask(m_part_info, m_part_info->num_parts); else
set_linear_hash_mask(m_part_info, m_part_info->num_subparts);
} elseif (m_part_info->part_type == VERSIONING_PARTITION)
{ if (m_part_info->check_constants(ha_thd(), m_part_info))
{
result= HA_ERR_PARTITION_LIST; goto init_error;
}
}
while (reorg_part < m_reorged_parts)
{
handler *file= m_reorged_file[reorg_part];
uint32 new_part;
late_extra_cache(reorg_part); if (unlikely((result= file->ha_rnd_init_with_error(1)))) goto init_error; while (TRUE)
{ if ((result= file->ha_rnd_next(table->record[0])))
{ if (result != HA_ERR_END_OF_FILE) goto error;
/* End-of-file reached, break outto continue with next partition or end the copy process.
*/
break;
}
/* Found recordto insert into new handler */ if (m_part_info->get_partition_id(m_part_info, &new_part,
&func_value))
{
/*
This recordisin the original table but will not be in the new
table since it doesn't fit into any partition any longer due to
changed partitioning ranges or list values.
*/
(*deleted)++;
} else
{ if (m_new_file[new_part]->m_lock_type != F_WRLCK)
{
m_last_part= reorg_part;
m_err_rec= table->record[0];
result= HA_ERR_ROW_IN_WRONG_PARTITION; goto error;
}
/* Copy recordto new handler */
(*copied)++;
DBUG_ASSERT(!m_new_file[new_part]->row_logging);
result= m_new_file[new_part]->ha_write_row(table->record[0]); if (result) goto error;
}
}
late_extra_no_cache(reorg_part); file->ha_rnd_end();
reorg_part++;
}
DBUG_EXECUTE_IF("debug_abort_copy_partitions",
DBUG_RETURN(HA_ERR_UNSUPPORTED); );
DBUG_RETURN(FALSE);
error:
m_reorged_file[reorg_part]->ha_rnd_end();
init_error:
DBUG_RETURN(result);
}
/*
Update create info as part of ALTER TABLE
SYNOPSIS
update_create_info()
create_info Create info from ALTER TABLE
RETURN VALUE
NONE
DESCRIPTION
Forward this handler call to the storage engine foreach
partition handler. The data_file_name for each partition may
need to be reset if the tablespace was moved. Use a dummy
HA_CREATE_INFO structure and transfer necessary data.
*/
/*
Fix for bug#38751, some engines needs info-calls in ALTER.
Archive need this since it flushes in ::info.
HA_STATUS_AUTO is optimized so it will not always be forwarded to all partitions, but HA_STATUS_VARIABLE will.
*/
info(HA_STATUS_VARIABLE | HA_STATUS_OPEN);
info(HA_STATUS_AUTO);
if (!(create_info->used_fields & HA_CREATE_USED_AUTO))
create_info->auto_increment_value= stats.auto_increment_value;
/*
DATA DIRECTORY and INDEX DIRECTORY are never applied to the whole
partitioned table, only its parts.
*/
my_bool from_alter= (create_info->data_file_name == (constchar*) -1);
create_info->data_file_name= create_info->index_file_name= NULL;
/*
We donot need to update the individual partition DATA DIRECTORY settings
since they can be changed by ALTER TABLE ... REORGANIZE PARTITIONS.
*/ if (from_alter)
DBUG_VOID_RETURN;
/*
send Handler::update_create_info() to the storage engine for each
partition that currently has a handler object. Using a dummy
HA_CREATE_INFO structure to collect DATA and INDEX DIRECTORYs.
*/
/*
Since update_create_info() can be called from mysql_prepare_alter_table()
when not all handlers are set up, we look for that condition first. If all handlers are not available, donot call update_create_info for any.
*/
uint i, j, part; for (i= 0; i < num_parts; i++)
{
part_elem= part_it++; if (!part_elem)
DBUG_VOID_RETURN; if (m_is_sub_partitioned)
{
List_iterator<partition_element> subpart_it(part_elem->subpartitions); for (j= 0; j < num_subparts; j++)
{
sub_elem= subpart_it++; if (!sub_elem)
DBUG_VOID_RETURN;
part= i * num_subparts + j; if (part >= m_file_tot_parts || !m_file[part])
DBUG_VOID_RETURN;
}
} else
{ if (!m_file[i])
DBUG_VOID_RETURN;
}
}
part_it.rewind();
for (i= 0; i < num_parts; i++)
{
part_elem= part_it++;
DBUG_ASSERT(part_elem); if (m_is_sub_partitioned)
{
List_iterator<partition_element> subpart_it(part_elem->subpartitions); for (j= 0; j < num_subparts; j++)
{
sub_elem= subpart_it++;
DBUG_ASSERT(sub_elem);
part= i * num_subparts + j;
DBUG_ASSERT(part < m_file_tot_parts);
DBUG_ASSERT(m_file[part]);
dummy_info.data_file_name= dummy_info.index_file_name = NULL;
/*
store_optimizer_context()/show_create_table() may attempt to produce DDL for a table which has only some partitions open.
We can't get options for unopened partitions. They are not relevant for purpose, so it's ok to skip printing their options.
*/ if (m_file[part]->is_open())
m_file[part]->update_create_info(&dummy_info); else
dummy_info.init();
sub_elem->data_file_name = (char*) dummy_info.data_file_name;
sub_elem->index_file_name = (char*) dummy_info.index_file_name;
}
} else
{
DBUG_ASSERT(m_file[i]);
dummy_info.data_file_name= dummy_info.index_file_name= NULL;
/*
A partition might not be open, see above note about
store_optimizer_context()
*/ if (m_file[i]->is_open())
m_file[i]->update_create_info(&dummy_info); else
dummy_info.init();
part_elem->data_file_name = (char*) dummy_info.data_file_name;
part_elem->index_file_name = (char*) dummy_info.index_file_name;
}
}
DBUG_VOID_RETURN;
}
/**
Change the internal TABLE_SHARE pointer
@param table_arg TABLE object
@param share New share to use
@note Is used in error handling in delete_table.
All handlers should exist (lock_partitions should not be used)
*/
void ha_partition::change_table_ptr(TABLE *table_arg, TABLE_SHARE *share)
{
handler **file_array;
table= table_arg;
table_share= share;
/*
m_file can be NULL when using an old cached table in DROP TABLE, when the
table just has REMOVED PARTITIONING, see Bug#42438
*/ if (m_file)
{
file_array= m_file;
DBUG_ASSERT(*file_array); do
{
(*file_array)->change_table_ptr(table_arg, share);
} while (*(++file_array));
}
if (m_added_file && m_added_file[0])
{
/* ifin middle of a drop/rename etc */
file_array= m_added_file; do
{
(*file_array)->change_table_ptr(table_arg, share);
} while (*(++file_array));
}
}
/**
Handle delete and rename table
@param from Full path of old table
@param to Full path of new table. May be NULL incaseof delete
@return Operation status
@retval >0 Error
@retval 0 Success
@note Common routine to handle delete_table and rename_table.
The routine uses the partition handler fileto get the
names of the partition instances. Both these routines
are called after creating the handler without table objectand thus the fileis needed to discover the
names of the partitions and the underlying storage engines.
*/
if (get_from_handler_file(from, thd->mem_root, false))
DBUG_RETURN(my_errno ? my_errno : ENOENT);
DBUG_ASSERT(m_file_buffer);
DBUG_PRINT("enter", ("from: (%s) to: (%s)", from, to ? to : "(nil)"));
name_buffer_ptr= m_name_buffer_ptr;
file= m_file;
/* The command should be logged withIF EXISTS if using a shared table */ if (m_file[0]->ht->flags & HTON_TABLE_MAY_NOT_EXIST_ON_SLAVE)
thd->replication_flags|= OPTION_IF_EXISTS;
if (to == NULL)
{
/*
Delete table, start by delete the .par file. If error, break, otherwise
delete as much as possible.
*/ if (unlikely((error= handler::delete_table(from))))
DBUG_RETURN(error);
}
if (ha_check_if_updates_are_ignored(thd, partition_ht(), to ? "RENAME" : "DROP"))
DBUG_RETURN(0);
/*
Since ha_partition has HA_FILE_BASED, it must alter underlying table names if they donot have HA_FILE_BASED and lower_case_table_names == 2.
See Bug#37402, for Mac OS X.
The appended #P#<partname>[#SP#<subpartname>] will remain in current case.
Using the first partitions handler, since mixing handlers isnot allowed.
*/
from_path= file[0]->get_canonical_filename(Lex_cstring_strlen(from),
&from_lc_buff).str; if (to != NULL)
to_path= file[0]->get_canonical_filename(Lex_cstring_strlen(to),
&to_lc_buff).str; do
{ if (unlikely((error= create_partition_name(from_buff, sizeof(from_buff),
from_path, name_buffer_ptr,
NORMAL_PART_NAME, FALSE)))) goto rename_error;
if (to != NULL)
{ // Rename branch if (unlikely((error= create_partition_name(to_buff, sizeof(to_buff),
to_path, name_buffer_ptr,
NORMAL_PART_NAME, FALSE)))) goto rename_error;
error= (*file)->ha_rename_table(from_buff, to_buff); if (unlikely(error)) goto rename_error;
} else// delete branch
{
error= (*file)->delete_table(from_buff);
}
name_buffer_ptr= strend(name_buffer_ptr) + 1; if (unlikely(error))
save_error= error;
} while (*(++file)); if (to != NULL)
{ if (unlikely((error= handler::rename_table(from, to))))
{
/* Tryto revert everything, ignore errors */
(void) handler::rename_table(to, from); goto rename_error;
}
}
/* Update .par filein the handlers that supports it */ if ((*m_file)->ht->create_partitioning_metadata)
{
error= (*m_file)->ht->create_partitioning_metadata(to, from, to == NULL ?
CHF_DELETE_FLAG :
CHF_RENAME_FLAG);
DBUG_EXECUTE_IF("failed_create_partitioning_metadata",
{ my_message_sql(ER_OUT_OF_RESOURCES,"Simulated crash",MYF(0));
error= 1;
}); if (error)
{ if (to)
{
(void) handler::rename_table(to, from);
(void) (*m_file)->ht->create_partitioning_metadata(from, to,
CHF_RENAME_FLAG); goto rename_error;
} else
save_error=error;
}
}
DBUG_RETURN(save_error);
rename_error:
name_buffer_ptr= m_name_buffer_ptr; for (abort_file= file, file= m_file; file < abort_file; file++)
{
/* Revert the rename, back from 'to'to the original 'from' */ if (!create_partition_name(from_buff, sizeof(from_buff), from_path,
name_buffer_ptr, NORMAL_PART_NAME, FALSE) &&
!create_partition_name(to_buff, sizeof(to_buff), to_path,
name_buffer_ptr, NORMAL_PART_NAME, FALSE))
{
/* Ignore error here */
(void) (*file)->ha_rename_table(to_buff, from_buff);
}
name_buffer_ptr= strend(name_buffer_ptr) + 1;
}
DBUG_RETURN(error);
}
uint ha_partition::count_query_cache_dependant_tables(uint8 *tables_type)
{
DBUG_ENTER("ha_partition::count_query_cache_dependant_tables");
/* Here we rely on the fact that all tables are of the same type */
uint8 type= m_file[0]->table_cache_type();
(*tables_type)|= type;
DBUG_PRINT("enter", ("cnt: %u", (uint) m_tot_parts));
/*
We need save underlying tables only for HA_CACHE_TBL_ASKTRANSACT:
HA_CACHE_TBL_NONTRANSACT - because all changes goes through partition table
HA_CACHE_TBL_NOCACHE - because will not be cached
HA_CACHE_TBL_TRANSACT - QC need to know that such type present
*/
DBUG_RETURN(type == HA_CACHE_TBL_ASKTRANSACT ? m_tot_parts : 0);
}
my_bool ha_partition::
reg_query_cache_dependant_table(THD *thd, char *engine_key, uint engine_key_len, char *cache_key, uint cache_key_len,
uint8 type,
Query_cache *cache,
Query_cache_block_table **block_table,
handler *file,
uint *n)
{
DBUG_ENTER("ha_partition::reg_query_cache_dependant_table");
qc_engine_callback engine_callback;
ulonglong engine_data;
/* ask undelying engine */ if (!file->register_query_cache_table(thd, engine_key,
engine_key_len,
&engine_callback,
&engine_data))
{
DBUG_PRINT("qcache", ("Handler does not allow caching for %.*s",
engine_key_len, engine_key));
/* As this can change from call to call, don't reset set
thd->lex->safe_to_cache_query
*/
thd->query_cache_is_applicable= 0; // Query can't be cached
DBUG_RETURN(TRUE);
}
(++(*block_table))->n= ++(*n); if (!cache->insert_table(thd, cache_key_len,
cache_key, (*block_table),
(uint32) table_share->db.length,
(uint8) (cache_key_len -
table_share->table_cache_key.length), type,
engine_callback, engine_data,
FALSE))
DBUG_RETURN(TRUE);
DBUG_RETURN(FALSE);
}
/* see ha_partition::count_query_cache_dependant_tables */ if (m_file[0]->table_cache_type() != HA_CACHE_TBL_ASKTRANSACT)
DBUG_RETURN(FALSE); // nothing to register
/* prepare static part of the key */
memcpy(engine_key, table_share->normalized_path.str,
table_share->normalized_path.length);
memcpy(query_cache_key, table_share->table_cache_key.str,
table_share->table_cache_key.length);
i= 0; do
{
partition_element *part_elem= part_it++; char *engine_pos= strmov(engine_key_end, part_elem->partition_name.str); if (m_is_sub_partitioned)
{
List_iterator<partition_element> subpart_it(part_elem->subpartitions);
partition_element *sub_elem;
uint j= 0, part;
engine_pos[0]= engine_pos[3]= '#';
engine_pos[1]= 'S';
engine_pos[2]= 'P';
engine_pos += 4; do
{ char *end;
uint length;
sub_elem= subpart_it++;
part= i * num_subparts + j;
/* we store the end \0as part of the key */ end= strmov(engine_pos, sub_elem->partition_name.str) + 1;
length= (uint)(end - engine_key);
/* Copy the suffix andend0to query cache key */
memcpy(query_cache_key_end, engine_key_end, (end - engine_key_end)); if (reg_query_cache_dependant_table(thd, engine_key, length,
query_cache_key,
length + diff_length,
m_file[part]->table_cache_type(),
cache,
block_table, m_file[part],
n))
DBUG_RETURN(TRUE);
} while (++j < num_subparts);
} else
{ char *end= engine_pos+1; // copy end \0
uint length= (uint)(end - engine_key);
/* Copy the suffix andend0to query cache key */
memcpy(query_cache_key_end, engine_key_end, (end - engine_key_end)); if (reg_query_cache_dependant_table(thd, engine_key, length,
query_cache_key,
length + diff_length,
m_file[i]->table_cache_type(),
cache,
block_table, m_file[i],
n))
DBUG_RETURN(TRUE);
}
} while (++i < num_parts);
DBUG_PRINT("info", ("cnt: %u", (uint)m_tot_parts));
DBUG_RETURN(FALSE);
}
/** Set up table share object before calling create on underlying handler
@param table Table object
@param info Create info
@param part_elem[in,out] Pointer to used partition_element, searched if NULL
@return status
@retval TRUE Error
@retval FALSE Success
@details Set up 1) Comment on partition 2) MAX_ROWS, MIN_ROWS on partition 3) Index file name on partition 4) Data file name on partition 5) Engine-defined attributes on partition
*/
int ha_partition::set_up_table_before_create(TABLE *tbl, constchar *partition_name_with_path, HA_CREATE_INFO *info,
partition_element *part_elem)
{
int error= 0;
LEX_CSTRING part_name;
THD *thd= ha_thd();
DBUG_ENTER("set_up_table_before_create");
SYNOPSIS
name_add() out:dest Destination string
first_name First name
sec_name Second name
RETURN VALUE
>0 Error 0 Success
DESCRIPTION
Routine used to add two names with'_'in between then. Service routine to create_handler_file
Include the NULL in the count of characters since it is needed as separator
between the partition names.
*/
if (m_file_buffer)
DBUG_RETURN(0);
fn_format(buff, name, "", ha_par_ext, MY_APPEND_EXT);
/* Following could be done with mysql_file_stat to read in whole file */ if ((file= mysql_file_open(key_file_ha_partition_par,
buff, O_RDONLY | O_SHARE, MYF(0))) < 0)
DBUG_RETURN(1); if (mysql_file_read(file, (uchar *) &buff[0], PAR_WORD_SIZE, MYF(MY_NABP))) goto err1;
len_words= uint4korr(buff);
len_bytes= PAR_WORD_SIZE * len_words; if (mysql_file_seek(file, 0, MY_SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR) goto err1; if (!(file_buffer= (uchar*) alloc_root(&m_mem_root, len_bytes))) goto err1; if (mysql_file_read(file, file_buffer, len_bytes, MYF(MY_NABP))) goto err2;
chksum= 0; for (i= 0; i < len_words; i++)
chksum ^= uint4korr((file_buffer) + PAR_WORD_SIZE * i); if (chksum) goto err2;
m_tot_parts= uint4korr((file_buffer) + PAR_NUM_PARTS_OFFSET);
DBUG_PRINT("info", ("No of parts: %u", m_tot_parts));
tot_partition_words= (m_tot_parts + PAR_WORD_SIZE - 1) / PAR_WORD_SIZE;
tot_name_len_offset= file_buffer + PAR_ENGINES_OFFSET +
PAR_WORD_SIZE * tot_partition_words;
tot_name_words= (uint4korr(tot_name_len_offset) + PAR_WORD_SIZE - 1) /
PAR_WORD_SIZE;
/*
Verify the total length = tot size word, checksum word, num parts word +
engines array + name length word + name array.
*/ if (len_words != (tot_partition_words + tot_name_words + 4)) goto err2;
m_file_buffer= file_buffer; // Will be freed in clear_handler_file()
m_name_buffer_ptr= (char*) (tot_name_len_offset + PAR_WORD_SIZE);
(void) mysql_file_close(file, MYF(0));
DBUG_RETURN(0);
Compare_keys ha_partition::compare_key_parts( const Field &old_field, const Column_definition &new_field, const KEY_PART_INFO &old_part, const KEY_PART_INFO &new_part) const
{
Compare_keys res= m_file[0]->compare_key_parts(old_field, new_field,
old_part, new_part);
/*
Partitions have the same storage engine (until MDEV-22168) so the
calls should all return the same value for now.
*/ for (uint i= 1; i < m_tot_parts; i++) if (res != m_file[i]->compare_key_parts(old_field, new_field,
old_part, new_part))
return Compare_keys::NotEqual;
return res;
}
/**
Setup m_engine_array
@param mem_root MEM_ROOT to use for allocating new handlers
@return Operation status
@retval false Success
@retval true Failure
*/
if (mysql_file_fstat(file, &state, MYF(MY_WME))) goto err; if (state.st_size <= 64) goto err; if ((ulonglong)state.st_size >= SIZE_T_MAX) /* Whole file need to fit into memory*/ goto err; if (!(frm_image= (uchar*)my_malloc(key_memory_Partition_share,
(size_t)state.st_size, MYF(MY_WME)))) goto err; if (mysql_file_read(file, frm_image, (size_t)state.st_size, MYF(MY_NABP))) goto err;
if ((error= read_par_file(name)))
{ if (error != 1 || is_clone || re_create_par_file(name))
DBUG_RETURN(true); if (read_par_file(name)) // Test file
DBUG_RETURN(true);
}
handlerton *default_engine= get_def_part_engine(name); if (!default_engine)
DBUG_RETURN(true);
if (!is_clone && setup_engine_array(mem_root, default_engine))
DBUG_RETURN(true);
DBUG_RETURN(false);
}
/*
Create .par file from SQL syntax.
This is only used with partitioned tables from MySQL 5.6or5.7
which donot have a .par file.
*/
/* Share can be NULL in case of delete of non existing table */
if (!share ||
!(share->mysql_version >= 50600 && share->mysql_version <= 50799))
DBUG_RETURN(1);
/**
Insert a partition name in the partition_name_hash.
@param name Name of partition
@param part_id Partition id (number)
@param is_subpart Set if the name belongs to a subpartition
@return Operation status
@retval true Failure
@retval false Success
*/
bool ha_partition::insert_partition_name_in_hash(const char *name, uint part_id,
bool is_subpart)
{
PART_NAME_DEF *part_def;
uchar *part_name;
size_t part_name_length;
DBUG_ENTER("ha_partition::insert_partition_name_in_hash");
/*
Calculate and store the length here, to avoid doing it when
searching the hash.
*/
part_name_length= strlen(name);
/*
Must use memory that lives as long as table_share.
Freed in the Partition_share destructor.
Since we use my_multi_malloc, then my_free(part_def) will also free
part_name, as a part of my_hash_free.
*/
if (!my_multi_malloc(key_memory_Partition_share, MY_WME,
&part_def, sizeof(PART_NAME_DEF),
&part_name, part_name_length + 1,
NULL))
DBUG_RETURN(true);
memcpy(part_name, name, part_name_length + 1);
part_def->partition_name= part_name;
part_def->length= (uint)part_name_length;
part_def->part_id= part_id;
part_def->is_subpart= is_subpart;
if (my_hash_insert(&part_share->partition_name_hash, (uchar *) part_def))
{
my_free(part_def);
DBUG_RETURN(true);
}
DBUG_RETURN(false);
}
/**
Populate the partition_name_hash in part_share.
*/
/* Initialize the bitmap we use to minimize ha_start_bulk_insert calls */
if (my_bitmap_init(&m_bulk_insert_started, NULL, m_tot_parts + 1))
DBUG_RETURN(true);
/* Initialize the bitmap we use to keep track of locked partitions */
if (my_bitmap_init(&m_locked_partitions, NULL, m_tot_parts))
DBUG_RETURN(true);
/*
Initialize the bitmap we use to keep track of partitions which may have
something to reset in ha_reset().
*/
if (my_bitmap_init(&m_partitions_to_reset, NULL, m_tot_parts))
DBUG_RETURN(true);
/*
Initialize the bitmap we use to keep track of partitions which returned
HA_ERR_KEY_NOT_FOUND from index_read_map.
*/
if (my_bitmap_init(&m_key_not_found_partitions, NULL, m_tot_parts))
DBUG_RETURN(true);
if (my_bitmap_init(&m_mrr_used_partitions, NULL, m_tot_parts))
DBUG_RETURN(true);
if (my_bitmap_init(&m_opened_partitions, NULL, m_tot_parts))
DBUG_RETURN(true);
m_file_sample= NULL;
/* Initialize the bitmap for read/lock_partitions */
if (!m_is_clone_of)
{
DBUG_ASSERT(!m_clone_mem_root);
if (m_part_info->set_partition_bitmaps(NULL))
DBUG_RETURN(true);
}
DBUG_RETURN(false);
}
/*
Open handler object
SYNOPSIS
open()
name Full path of table name
mode Open mode flags
test_if_locked ?
RETURN VALUE
>0 Error 0 Success
DESCRIPTION
Used for opening tables. The name will be the name of the file.
A table is opened when it needs to be opened. For instance
when a request comes in for a select on the table (tables are not
open and closed for each request, they are cached).
Called from handler.cc by handler::ha_open(). The server opens all tables
by calling ha_open() which then calls the handler specific open().
*/
int ha_partition::open(const char *name, int mode, uint test_if_locked)
{
int error= HA_ERR_INITIALIZATION;
handler **file;
char name_buff[FN_REFLEN + 1];
ulonglong check_table_flags;
DBUG_ENTER("ha_partition::open");
DBUG_ASSERT(table->s == table_share);
ref_length= 0;
m_mode= mode;
m_open_test_lock= test_if_locked;
m_part_field_array= m_part_info->full_part_field_array;
if (get_from_handler_file(name, &table->mem_root, MY_TEST(m_is_clone_of)))
DBUG_RETURN(error);
if (populate_partition_name_hash())
{
DBUG_RETURN(HA_ERR_INITIALIZATION);
}
m_start_key.length= 0;
m_rec_length= table_share->reclength;
if (!m_part_ids_sorted_by_num_of_records)
{
if (!(m_part_ids_sorted_by_num_of_records=
(uint32*) my_malloc(key_memory_ha_partition_part_ids,
m_tot_parts * sizeof(uint32), MYF(MY_WME))))
DBUG_RETURN(error);
uint32 i;
/* Initialize it with all partition ids. */
for (i= 0; i < m_tot_parts; i++)
m_part_ids_sorted_by_num_of_records[i]= i;
}
if (init_partition_bitmaps())
goto err_alloc;
if (!MY_TEST(m_is_clone_of) &&
unlikely((error=
m_part_info->set_partition_bitmaps(m_partitions_to_open))))
goto err_alloc;
if (m_is_clone_of)
{
uint i, alloc_len;
char *name_buffer_ptr;
DBUG_ASSERT(m_clone_mem_root);
/* Allocate an array of handler pointers for the partitions handlers. */
alloc_len= (m_tot_parts + 1) * sizeof(handler*);
if (!(m_file= (handler **) alloc_root(m_clone_mem_root, alloc_len)))
{
error= HA_ERR_INITIALIZATION;
goto err_alloc;
}
memset(m_file, 0, alloc_len);
name_buffer_ptr= m_name_buffer_ptr;
/*
Populate them by cloning the original partitions. This also opens them.
Note that file->ref is allocated too.
*/
file= m_is_clone_of->m_file;
for (i= 0; i < m_tot_parts; i++)
{
if (!bitmap_is_set(&m_is_clone_of->m_opened_partitions, i))
{
/* Here we should just create the handler instance, not open it. */
if (!(m_file[i]= get_new_handler(table->s, m_clone_mem_root,
file[i]->ht)))
{
error= HA_ERR_INITIALIZATION;
file= &m_file[i];
goto err_handler;
}
if (m_file[i]->set_ha_share_ref(file[i]->ha_share))
{
error= HA_ERR_INITIALIZATION;
goto err_handler;
}
continue;
}
if (unlikely((error= create_partition_name(name_buff, sizeof(name_buff),
name, name_buffer_ptr,
NORMAL_PART_NAME, FALSE))))
goto err_handler;
/* ::clone() will also set ha_share from the original. */
if (!(m_file[i]= file[i]->clone(name_buff, m_clone_mem_root)))
{
error= HA_ERR_INITIALIZATION;
file= &m_file[i];
goto err_handler;
}
if (!m_file_sample)
m_file_sample= m_file[i];
name_buffer_ptr+= strlen(name_buffer_ptr) + 1;
bitmap_set_bit(&m_opened_partitions, i);
}
}
else
{
check_insert_or_replace_autoincrement();
if (unlikely((error= open_read_partitions(name_buff, sizeof(name_buff)))))
goto err_handler;
m_num_locks= m_file_sample->lock_count();
}
/*
We want to know the upper bound for locks, to allocate enough memory.
There is no performance lost if we simply return in lock_count() the
maximum number locks needed, only some minor over allocation of memory
in get_lock_data().
*/
m_num_locks*= m_tot_parts;
file= m_file;
ref_length= get_open_file_sample()->ref_length;
check_table_flags= ((get_open_file_sample()->ha_table_flags() &
~(PARTITION_DISABLED_TABLE_FLAGS)) |
(PARTITION_ENABLED_TABLE_FLAGS));
while (*(++file))
{
if (!bitmap_is_set(&m_opened_partitions, (uint)(file - m_file)))
continue;
/* MyISAM can have smaller ref_length for partitions with MAX_ROWS set */
set_if_bigger(ref_length, ((*file)->ref_length));
/*
Verify that all partitions have the same set of table flags.
Mask all flags that partitioning enables/disables.
*/
if (check_table_flags != (((*file)->ha_table_flags() &
~(PARTITION_DISABLED_TABLE_FLAGS)) |
(PARTITION_ENABLED_TABLE_FLAGS)))
{
error= HA_ERR_INITIALIZATION;
/* set file to last handler, so all of them are closed */
file= &m_file[m_tot_parts - 1];
goto err_handler;
}
}
key_used_on_scan= get_open_file_sample()->key_used_on_scan;
implicit_emptied= get_open_file_sample()->implicit_emptied;
/*
Add 2 bytes for partition id in position ref length.
ref_length=max_in_all_partitions(ref_length) + PARTITION_BYTES_IN_POS
*/
ref_length+= PARTITION_BYTES_IN_POS;
m_ref_length= ref_length;
/*
Release buffer read from .par file. It will not be reused again after
being opened once.
*/
clear_handler_file();
DBUG_ASSERT(part_share);
lock_shared_ha_data();
/* Protect against cloned file, for which we don't need engine name */
if (m_file[0])
part_share->partition_engine_name= real_table_type();
else
part_share->partition_engine_name= 0; // Checked in ha_table_exists()
unlock_shared_ha_data();
/*
Some handlers update statistics as part of the open call. This will in
some cases corrupt the statistics of the partition handler and thus
to ensure we have correct statistics we call info from open after
calling open on all individual handlers.
*/
m_handler_status= handler_opened;
if (m_part_info->part_expr)
m_part_func_monotonicity_info=
m_part_info->part_expr->get_monotonicity_info();
else if (m_part_info->list_of_part_fields)
m_part_func_monotonicity_info= MONOTONIC_STRICT_INCREASING;
if ((error= info(HA_STATUS_VARIABLE | HA_STATUS_CONST | HA_STATUS_OPEN)))
goto err_handler;
DBUG_RETURN(0);
err_handler:
DEBUG_SYNC(ha_thd(), "partition_open_error");
DBUG_ASSERT(m_tot_parts > 0);
for (uint i= m_tot_parts - 1; ; --i)
{
if (bitmap_is_set(&m_opened_partitions, i))
m_file[i]->ha_close();
if (!i)
break;
}
err_alloc:
free_partition_bitmaps();
my_free(m_range_info);
m_range_info= 0;
DBUG_RETURN(error);
}
/*
Disabled since it is not possible to prune yet.
without pruning, it need to rebind/unbind every partition in every
statement which uses a table from the table cache. Will also use
as many PSI_tables as there are partitions.
*/
/**
Clone the open and locked partitioning handler.
@param mem_root MEM_ROOT to use.
@return Pointer to the successfully created clone or NULL
@details
This function creates a new ha_partition handler as a clone/copy. The
original (this) must already be opened and locked. The clone will use
the originals m_part_info.
It also allocates memory for ref + ref_dup.
In ha_partition::open() it will clone its original handlers partitions
which will allocate then on the correct MEM_ROOT and also open them.
*/
DBUG_ENTER("ha_partition::clone");
new_handler= new (mem_root) ha_partition(ht, table_share, m_part_info,
this, mem_root);
if (!new_handler)
DBUG_RETURN(NULL);
/*
We will not clone each partition's handler here, it will be done in
ha_partition::open() for clones. Also set_ha_share_ref is not needed
here, since 1) ha_share is copied in the constructor used above 2) each partition's cloned handler will set it from its original.
*/
/*
Allocate new_handler->ref here because otherwise ha_open will allocate it
on this->table->mem_root and we will not be able to reclaim that memory
when the clone handler object is destroyed.
*/
if (!(new_handler->ref= (uchar*) alloc_root(mem_root,
ALIGN_SIZE(m_ref_length)*2)))
goto err;
if (new_handler->ha_open(table, name,
table->db_stat,
HA_OPEN_IGNORE_IF_LOCKED | HA_OPEN_NO_PSI_CALL))
goto err;
if (handler_stats)
new_handler->loop_partitions(set_part_handler_stats, handler_stats);
DBUG_RETURN((handler*) new_handler);
err:
delete new_handler;
DBUG_RETURN(NULL);
}
/*
Update all sub partitions to point to handler stats
*/
DESCRIPTION
Called from sql_base.cc, sql_select.cc, and table.cc.
In sql_select.cc it is only used to close up temporary tables or during
the process where a temporary table is converted over to being a
myisam table.
For sql_base.cc look at close_data_tables().
*/
/****************************************************************************
MODULE start/end statement
****************************************************************************/
/*
A number of methods to define various constants for the handler. In
the case of the partition handler we need to use some max and min
of the underlying handlers in most cases.
*/
/*
Set external locks on table
SYNOPSIS
external_lock()
thd Thread object
lock_type Type of external lock
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
First you should go read the section "locking functions for mysql" in
lock.cc to understand this.
This create a lock on the table. If you are implementing a storage engine
that can handle transactions look at ha_berkeley.cc to see how you will
want to go about doing this. Otherwise you should consider calling
flock() here.
Originally this method was used to set locks on file level to enable
several MySQL Servers to work on the same data. For transactional
engines it has been "abused" to also mean start and end of statements
to enable proper rollback of statements and transactions. When LOCK
TABLES has been issued the start_stmt method takes over the role of
indicating start of statement but in this case there is no end of
statement indicator(?).
Called from lock.cc by lock_external() and unlock_external(). Also called
from sql_table.cc by copy_data_between_tables().
*/
int ha_partition::external_lock(THD *thd, int lock_type)
{
int error;
uint i, first_used_partition;
MY_BITMAP *used_partitions;
DBUG_ENTER("ha_partition::external_lock");
/*
Get the lock(s) for the table and perform conversion of locks if needed
SYNOPSIS
store_lock()
thd Thread object
to Lock object array
lock_type Table lock type
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
The idea with handler::store_lock() is the following:
The statement decided which locks we should need for the table
for updates/deletes/inserts we get WRITE locks, for SELECT... we get
read locks.
Before adding the lock into the table lock handler (see thr_lock.c)
mysqld calls store lock with the requested locks. Store lock can now
modify a write lock to a read lock (or some other lock), ignore the
lock (if we don't want to use MySQL table locks at all) or add locks
for many tables (like we do when we are using a MERGE handler).
Berkeley DB for partition changes all WRITE locks to TL_WRITE_ALLOW_WRITE
(which signals that we are doing WRITES, but we are still allowing other
reader's and writer's.
When releasing locks, store_lock() is also called. In this case one
usually doesn't have to do anything.
store_lock is called when holding a global mutex to ensure that only
one thread at a time changes the locking information of tables.
In some exceptional cases MySQL may send a request for a TL_IGNORE;
This means that we are requesting the same lock as last time and this
should also be ignored. (This may happen when someone does a flush
table when we have opened a part of the tables, in which case mysqld
closes and reopens the tables and tries to get the same locks as last
time). In the future we will probably try to remove this.
for (i= bitmap_get_first_set(used_partitions);
i < m_tot_parts;
i= bitmap_get_next_set(used_partitions, i))
{
DBUG_PRINT("info", ("store lock %u iteration", i));
to= m_file[i]->store_lock(thd, to, lock_type);
}
DBUG_RETURN(to);
}
/*
Start a statement when table is locked
SYNOPSIS
start_stmt()
thd Thread object
lock_type Type of external lock
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
This method is called instead of external lock when the table is locked
before the statement is executed.
*/
int ha_partition::start_stmt(THD *thd, thr_lock_type lock_type)
{
int error= 0;
uint i;
/* Assert that read_partitions is included in lock_partitions */
DBUG_ASSERT(bitmap_is_subset(&m_part_info->read_partitions,
&m_part_info->lock_partitions));
/*
m_locked_partitions is set in previous external_lock/LOCK TABLES.
Current statement's lock requests must not include any partitions
not previously locked.
*/
DBUG_ASSERT(bitmap_is_subset(&m_part_info->lock_partitions,
&m_locked_partitions));
DBUG_ENTER("ha_partition::start_stmt");
for (i= bitmap_get_first_set(&(m_part_info->lock_partitions));
i < m_tot_parts;
i= bitmap_get_next_set(&m_part_info->lock_partitions, i))
{
if (unlikely((error= m_file[i]->start_stmt(thd, lock_type))))
DBUG_RETURN(error);
/* Add partition to be called in reset(). */
bitmap_set_bit(&m_partitions_to_reset, i);
}
if (lock_type >= TL_FIRST_WRITE)
{
if (m_part_info->part_expr)
m_part_info->part_expr->walk(&Item::register_field_in_read_map, 0, WALK_SUBQUERY);
}
DBUG_RETURN(error);
}
/**
Get number of lock objects returned in store_lock
@returns Number of locks returned in call to store_lock
@desc
Returns the maximum possible number of store locks needed in call to
store lock.
*/
RETURN VALUE
TRUE Previous read was a semi consistent read
FALSE Previous read was not a semi consistent read
DESCRIPTION
See handler.h:
In an UPDATE or DELETE, if the row under the cursor was locked by another
transaction, and the engine used an optimistic read of the last
committed row value under the cursor, then the engine returns 1 from this
function. MySQL must NOT try to update this optimistic value. If the
optimistic value does not match the WHERE condition, MySQL can decide to
skip over this row. Currently only works for InnoDB. This can be used to
avoid unnecessary lock waits.
If this method returns nonzero, it will also signal the storage
engine that the next read will be a locking re-read of the row.
*/
bool ha_partition::was_semi_consistent_read()
{
DBUG_ENTER("ha_partition::was_semi_consistent_read");
DBUG_ASSERT(m_last_part < m_tot_parts);
DBUG_ASSERT(bitmap_is_set(&(m_part_info->read_partitions), m_last_part));
DBUG_RETURN(m_file[m_last_part]->was_semi_consistent_read());
}
/**
Use semi consistent read if possible
SYNOPSIS
try_semi_consistent_read()
yes Turn on semi consistent read
RETURN VALUE
NONE
DESCRIPTION
See handler.h:
Tell the engine whether it should avoid unnecessary lock waits.
If yes, in an UPDATE or DELETE, if the row under the cursor was locked
by another transaction, the engine may try an optimistic read of
the last committed row value under the cursor.
Note: prune_partitions are already called before this call, so using
pruning is OK.
*/
void ha_partition::try_semi_consistent_read(bool yes)
{
uint i;
DBUG_ENTER("ha_partition::try_semi_consistent_read");
i= bitmap_get_first_set(&(m_part_info->read_partitions));
DBUG_ASSERT(i != MY_BIT_NONE);
for (;
i < m_tot_parts;
i= bitmap_get_next_set(&m_part_info->read_partitions, i))
{
m_file[i]->try_semi_consistent_read(yes);
}
DBUG_VOID_RETURN;
}
/****************************************************************************
MODULE change record
****************************************************************************/
/*
Insert a row to the table
SYNOPSIS
write_row()
buf The row in MySQL Row Format
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
write_row() inserts a row. buf() is a byte array of data, normally
record[0].
You can use the field information to extract the data from the native byte
array type.
Example of this would be:
for (Field **field=table->field ; *field ; field++)
{
...
}
See ha_tina.cc for a variant of extracting all of the data as strings.
ha_berkeley.cc has a variant of how to store it intact by "packing" it
for ha_berkeley's own native storage type.
Called from item_sum.cc, item_sum.cc, sql_acl.cc, sql_insert.cc,
sql_insert.cc, sql_select.cc, sql_table.cc, sql_udf.cc, and sql_update.cc.
ADDITIONAL INFO:
We have to set auto_increment fields, because those may be used in
determining which partition the row should be written to.
*/
// Need to read partition-related columns, to locate the row's partition:
DBUG_ASSERT(bitmap_is_subset(&m_part_info->full_part_field_set,
table->read_set));
#ifndef DBUG_OFF /* Theprotocolforupdatingarowis: 1)positionthehandler(cursor)ontherowtobeupdated, eitherthroughthelastreadrow(rndorindex)orbyrnd_pos. 2)callupdate_rowwithbotholdandnewfullrecordsasarguments.
int ha_partition::delete_all_rows()
{
int error;
uint i;
DBUG_ENTER("ha_partition::delete_all_rows");
for (i= bitmap_get_first_set(&m_part_info->read_partitions);
i < m_tot_parts;
i= bitmap_get_next_set(&m_part_info->read_partitions, i))
{ /* Can be pruned, like DELETE FROM t PARTITION (pX) */ if (unlikely((error= m_file[i]->ha_delete_all_rows())))
DBUG_RETURN(error);
}
DBUG_RETURN(0);
}
/** Manuallytruncatethetable.
@retval0Success. @retval>0Errorcode.
*/
int ha_partition::truncate()
{
int error;
handler **file;
DBUG_ENTER("ha_partition::truncate");
m_bulk_inserted_rows= 0;
bitmap_clear_all(&m_bulk_insert_started); /* use the last bit for marking if bulk_insert_started was called */
bitmap_set_bit(&m_bulk_insert_started, m_tot_parts);
DBUG_VOID_RETURN;
}
/* Checkifstart_bulk_inserthasbeencalledforthispartition, ifnot,callitandmarkitcalled
*/
void ha_partition::start_part_bulk_insert(THD *thd, uint part_id)
{
long old_buffer_size; if (!bitmap_is_set(&m_bulk_insert_started, part_id) &&
bitmap_is_set(&m_bulk_insert_started, m_tot_parts))
{
DBUG_ASSERT(bitmap_is_set(&(m_part_info->lock_partitions), part_id));
old_buffer_size= thd->variables.read_buff_size; /* Update read_buffer_size for this partition */
thd->variables.read_buff_size= estimate_read_buffer_size(old_buffer_size);
m_file[part_id]->ha_start_bulk_insert(guess_bulk_insert_rows());
bitmap_set_bit(&m_bulk_insert_started, part_id);
thd->variables.read_buff_size= old_buffer_size;
}
m_bulk_inserted_rows++;
}
if (estimation_rows_to_insert < 10)
DBUG_RETURN(estimation_rows_to_insert);
/* If first insert/partition and monotonic partition function, guess 50%. */ if (!m_bulk_inserted_rows &&
m_part_func_monotonicity_info != NON_MONOTONIC &&
m_tot_parts > 1)
DBUG_RETURN(estimation_rows_to_insert / 2);
/* Else guess on equal distribution (+1 is to avoid returning 0/Unknown) */ if (m_bulk_inserted_rows < estimation_rows_to_insert)
DBUG_RETURN(((estimation_rows_to_insert - m_bulk_inserted_rows)
/ m_tot_parts) + 1); /* The estimation was wrong, must say 'Unknown' */
DBUG_RETURN(0);
}
int ha_partition::rnd_init(bool scan)
{
int error;
uint i= 0;
uint32 part_id;
DBUG_ENTER("ha_partition::rnd_init");
/* Foroperationsthatmayneedtochangedata,wemayneedtoextend read_set.
*/ if (get_lock_type() == F_WRLCK)
{ /* Ifwrite_setcontainsanyofthefieldsusedinpartitionand subpartitionexpression,weneedtosetallbitsinread_setbecause therowmayneedtobeinsertedinadifferent[sub]partition.In otherwordsupdate_row()canbeconvertedintowrite_row(),which requiresacompleterecord.
*/ if (bitmap_is_overlapping(&m_part_info->full_part_field_set,
table->write_set))
{
DBUG_PRINT("info", ("partition set full bitmap"));
bitmap_set_all(table->read_set);
}
else
{ /* Somehandlersonlyreadfieldsasspecifiedbythebitmapforthe readset.Forpartitionedhandlerswealwaysrequirethatthe fieldsofthepartitionfunctionsarereadsuchthatwecan calculatethepartitionidtoplaceupdatedanddeletedrecords.
*/
DBUG_PRINT("info", ("partition set part_field bitmap"));
bitmap_union(table->read_set, &m_part_info->full_part_field_set);
}
}
/* Now we see what the index of our first important partition is */
DBUG_PRINT("info", ("m_part_info->read_partitions: %p",
m_part_info->read_partitions.bitmap));
part_id= bitmap_get_first_set(&(m_part_info->read_partitions));
DBUG_PRINT("info", ("m_part_spec.start_part: %u", (uint) part_id));
if (part_id == MY_BIT_NONE)
{ error= 0;
goto err1;
}
/* Wehaveapartitionandwearescanningwithrnd_next sowebumpourcache
*/
DBUG_PRINT("info", ("rnd_init on partition: %u", (uint) part_id)); if (scan)
{ /* rnd_end()isneededforpartitioningtoresetinternaldataifscan isalreadyinuse
*/
rnd_end();
late_extra_cache(part_id);
m_index_scan_type= partition_no_index_scan;
}
for (i= part_id;
i < m_tot_parts;
i= bitmap_get_next_set(&m_part_info->read_partitions, i))
{ if (unlikely((error= m_file[i]->ha_rnd_init(scan))))
goto err;
}
SYNOPSIS
position()
record Current record in MySQL Row Format
RETURN VALUE
NONE
DESCRIPTION
position() is called after each call to rnd_next() if the data needs
to be ordered. You can do something like the following to store
the position:
ha_store_ptr(ref, ref_length, current_position);
The server uses ref to store data. ref_length in the above case is
the size needed to store current_position. ref is just a byte array
that the server will maintain. If you are using offsets to mark rows, then
current_position should be the offset. If it is a primary key like in
BDB, then it needs to be a primary key.
Called from filesort.cc, sql_select.cc, sql_delete.cc and sql_update.cc.
*/
SYNOPSIS
rnd_pos()
out:buf Row read in MySQL Row Format
position Position of read row
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
This is like rnd_next, but you are given a position to use
to determine the row. The position will be of the type that you stored in
ref. You can use ha_get_ptr(pos,ref_length) to retrieve whatever key
or position you saved when position() was called.
Called from filesort.cc records.cc sql_insert.cc sql_select.cc
sql_update.cc.
*/
/*
Read row using position using given record to find
SYNOPSIS
rnd_pos_by_record()
record Current record in MySQL Row Format
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
this works as position()+rnd_pos() functions, but does some extra work,
calculating m_last_part - the partition to where the 'record'
should go.
called from replication (log_event.cc)
*/
int ha_partition::rnd_pos_by_record(uchar *record)
{
DBUG_ENTER("ha_partition::rnd_pos_by_record");
if (unlikely(get_part_for_buf(record, table->record[0], m_part_info, &m_last_part)))
DBUG_RETURN(1);
int err= m_file[m_last_part]->rnd_pos_by_record(record);
DBUG_RETURN(err);
}
/****************************************************************************
MODULE index scan
****************************************************************************/
/*
Positions an index cursor to the index specified in the handle. Fetches the
row if available. If the key value is null, begin at the first key of the
index.
There are loads of optimisations possible here for the partition handler.
The same optimisations can also be checked for full table scan although
only through conditions and not from index ranges.
Phase one optimisations:
Check if the fields of the partition function are bound. If so only use
the single partition it becomes bound to.
Phase two optimisations:
If it can be deducted through range or list partitioning that only a
subset of the partitions are used, then only use those partitions.
*/
/**
Setup the ordered record buffer and the priority queue.
*/
bool ha_partition::init_record_priority_queue()
{
DBUG_ENTER("ha_partition::init_record_priority_queue");
DBUG_ASSERT(!m_ordered_rec_buffer);
/*
Initialize the ordered record buffer.
*/
size_t alloc_len;
uint used_parts= bitmap_bits_set(&m_part_info->read_partitions);
if (used_parts == 0) /* Do nothing since no records expected. */
DBUG_RETURN(false);
/* Allocate record buffer for each used partition. */
m_priority_queue_rec_len= m_rec_length + ORDERED_REC_OFFSET;
if (!m_using_extended_keys)
m_priority_queue_rec_len+= get_open_file_sample()->ref_length;
alloc_len= used_parts * m_priority_queue_rec_len;
/* Allocate a key for temporary use when setting up the scan. */
alloc_len+= table_share->max_key_length;
Ordered_blob_storage **blob_storage;
Ordered_blob_storage *objs;
const size_t n_all= used_parts * table->s->blob_fields;
/*
We set-up one record per partition and each record has 2 bytes in
front where the partition id is written. This is used by ordered
index_read.
We also set-up a reference to the first record for temporary use in
setting up the scan.
*/
char *ptr= (char*) m_ordered_rec_buffer;
uint i;
for (i= bitmap_get_first_set(&m_part_info->read_partitions);
i < m_tot_parts;
i= bitmap_get_next_set(&m_part_info->read_partitions, i))
{
DBUG_PRINT("info", ("init rec-buf for part %u", i));
if (table->s->blob_fields)
{
for (uint j= 0; j < table->s->blob_fields; ++j, ++objs)
blob_storage[j]= new (objs) Ordered_blob_storage;
*((Ordered_blob_storage ***) ptr)= blob_storage;
blob_storage+= table->s->blob_fields;
}
int2store(ptr + sizeof(String **), i);
DBUG_ASSERT(m_rec_length == table->s->reclength);
memcpy(ptr + ORDERED_REC_OFFSET, table->s->default_values, m_rec_length);
ptr+= m_priority_queue_rec_len;
}
m_start_key.key= (const uchar*)ptr;
active_index= inx;
m_part_spec.start_part= NO_CURRENT_PART_ID;
m_start_key.length= 0;
m_ordered= sorted;
m_ordered_scan_ongoing= FALSE;
m_curr_key_info[0]= table->key_info+inx;
if (pk_is_clustering_key(table->s->primary_key))
{
/*
if PK is clustered, then the key cmp must use the pk to
differentiate between equal key in given index.
*/
DBUG_PRINT("info", ("Clustered pk, using pk as secondary cmp"));
m_curr_key_info[1]= table->key_info+table->s->primary_key;
m_curr_key_info[2]= NULL;
m_using_extended_keys= TRUE;
}
else
{
m_curr_key_info[1]= NULL;
m_using_extended_keys= FALSE;
}
if (init_record_priority_queue())
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
/*
Some handlers only read fields as specified by the bitmap for the
read set. For partitioned handlers we always require that the
fields of the partition functions are read such that we can
calculate the partition id to place updated and deleted records.
But this is required for operations that may need to change data only.
*/
if (get_lock_type() == F_WRLCK)
{
DBUG_PRINT("info", ("partition set part_field bitmap"));
bitmap_union(table->read_set, &m_part_info->full_part_field_set);
}
if (sorted)
{
/*
An ordered scan is requested. We must make sure all fields of the
used index are in the read set, as partitioning requires them for
sorting (see ha_partition::handle_ordered_index_scan).
The SQL layer may request an ordered index scan without having index
fields in the read set when
- it needs to do an ordered scan over an index prefix.
- it evaluates ORDER BY with SELECT COUNT(*) FROM t1.
TODO: handle COUNT(*) queries via unordered scan.
*/
KEY **key_info= m_curr_key_info;
do
{
for (i= 0; i < (*key_info)->user_defined_key_parts; i++)
(*key_info)->key_part[i].field->register_field_in_read_map();
} while (*(++key_info));
}
for (i= bitmap_get_first_set(&m_part_info->read_partitions);
i < m_tot_parts;
i= bitmap_get_next_set(&m_part_info->read_partitions, i))
{
if (unlikely((error= m_file[i]->ha_index_init(inx, sorted))))
goto err;
DBUG_EXECUTE_IF("ha_partition_fail_index_init", {
i++;
error= HA_ERR_NO_PARTITION_FOUND;
goto err;
});
}
err:
if (unlikely(error))
{
/* End the previously initialized indexes. */
uint j;
for (j= bitmap_get_first_set(&m_part_info->read_partitions);
j < i;
j= bitmap_get_next_set(&m_part_info->read_partitions, j))
{
(void) m_file[j]->ha_index_end();
}
destroy_record_priority_queue();
}
DBUG_RETURN(error);
}
/*
End of index scan
SYNOPSIS
index_end()
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
index_end is called at the end of an index scan to clean up any
things needed to clean up.
*/
int ha_partition::index_end()
{
int error= 0;
handler **file;
DBUG_ENTER("ha_partition::index_end");
active_index= MAX_KEY;
m_part_spec.start_part= NO_CURRENT_PART_ID;
file= m_file;
do
{
if ((*file)->inited == INDEX)
{
int tmp;
if ((tmp= (*file)->ha_index_end()))
error= tmp;
}
else if ((*file)->inited == RND)
{
// Possible due to MRR
int tmp;
if ((tmp= (*file)->ha_rnd_end()))
error= tmp;
}
} while (*(++file));
destroy_record_priority_queue();
DBUG_RETURN(error);
}
/*
Read one record in an index scan and start an index scan
SYNOPSIS
index_read_map()
buf Read row in MySQL Row Format
key Key parts in consecutive order
keypart_map Which part of key is used
find_flag What type of key condition is used
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
index_read_map starts a new index scan using a start key. The MySQL Server
will check the end key on its own. Thus to function properly the
partitioned handler need to ensure that it delivers records in the sort
order of the MySQL Server.
index_read_map can be restarted without calling index_end on the previous
index scan and without calling index_init. In this case the index_read_map
is on the same index as the previous index_scan. This is particularly
used in conjunction with multi read ranges.
*/
/**
Common routine for a number of index_read variants
@param buf Buffer where the record should be returned.
@param have_start_key TRUE <=> the left endpoint is available, i.e.
we're in index_read call or in read_range_first
call and the range has left endpoint.
FALSE <=> there is no left endpoint (we're in
read_range_first() call and the range has no left
endpoint).
@return Operation status
@retval 0 OK
@retval HA_ERR_END_OF_FILE Whole index scanned, without finding the record.
@retval HA_ERR_KEY_NOT_FOUND Record not found, but index cursor positioned.
@retval other error code.
@details
Start scanning the range (when invoked from read_range_first()) or doing
an index lookup (when invoked from index_read_XXX):
- If possible, perform partition selection
- Find the set of partitions we're going to use
- Depending on whether we need ordering:
NO: Get the first record from first used partition (see
handle_unordered_scan_next_partition)
YES: Fill the priority queue and get the record that is the first in
the ordering
*/
int ha_partition::common_index_read(uchar *buf, bool have_start_key)
{
int error;
uint UNINIT_VAR(key_len); /* used if have_start_key==TRUE */
bool reverse_order= FALSE;
DBUG_ENTER("ha_partition::common_index_read");
if (have_start_key)
{
m_start_key.length= key_len= calculate_key_len(table, active_index,
m_start_key.key,
m_start_key.keypart_map);
DBUG_PRINT("info", ("have_start_key map %lu find_flag %u len %u",
m_start_key.keypart_map, m_start_key.flag, key_len));
DBUG_ASSERT(key_len);
}
if (unlikely((error= partition_scan_set_up(buf, have_start_key))))
{
DBUG_RETURN(error);
}
if (have_start_key &&
(m_start_key.flag == HA_READ_PREFIX_LAST ||
m_start_key.flag == HA_READ_PREFIX_LAST_OR_PREV ||
m_start_key.flag == HA_READ_BEFORE_KEY))
{
reverse_order= TRUE;
}
DBUG_PRINT("info", ("m_ordered %u m_o_scan_ong %u have_start_key %u",
m_ordered, m_ordered_scan_ongoing, have_start_key));
if (!m_ordered_scan_ongoing)
{
/*
We use unordered index scan when read_range is used and flag
is set to not use ordered.
We also use an unordered index scan when the number of partitions to
scan is only one.
The unordered index scan will use the partition set created.
*/
DBUG_PRINT("info", ("doing unordered scan"));
error= handle_pre_scan(reverse_order, FALSE);
if (likely(!error))
error= handle_unordered_scan_next_partition(buf, reverse_order);
}
else
{
/*
In all other cases we will use the ordered index scan. This will use
the partition set created by the get_partition_set method.
*/
error= handle_ordered_index_scan(buf, reverse_order);
}
DBUG_RETURN(error);
}
/*
Start an index scan from leftmost record and return first record
SYNOPSIS
index_first()
buf Read row in MySQL Row Format
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
index_first() asks for the first key in the index.
This is similar to index_read except that there is no start key since
the scan starts from the leftmost entry and proceeds forward with
index_next.
Called from opt_range.cc, opt_sum.cc, sql_handler.cc,
and sql_select.cc.
*/
int ha_partition::index_first(uchar * buf)
{
DBUG_ENTER("ha_partition::index_first");
decrement_statistics(&SSV::ha_read_first_count);
/*
Start an index scan from rightmost record and return first record
SYNOPSIS
index_last()
buf Read row in MySQL Row Format
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
index_last() asks for the last key in the index.
This is similar to index_read except that there is no start key since
the scan starts from the rightmost entry and proceeds forward with
index_prev.
Called from opt_range.cc, opt_sum.cc, sql_handler.cc,
and sql_select.cc.
*/
int ha_partition::index_last(uchar * buf)
{
DBUG_ENTER("ha_partition::index_last");
decrement_statistics(&SSV::ha_read_last_count);
int ha_partition::common_first_last(uchar *buf)
{
int error;
bool reverse_order= (m_index_scan_type == partition_index_last);
if (table->all_partitions_pruned_away)
return HA_ERR_END_OF_FILE; // No rows matching WHERE
if (unlikely((error= partition_scan_set_up(buf, FALSE))))
return error;
if (!m_ordered_scan_ongoing)
{
if (unlikely((error= handle_pre_scan(reverse_order, FALSE))))
return error;
return handle_unordered_scan_next_partition(buf, reverse_order);
}
return handle_ordered_index_scan(buf, FALSE);
}
/*
Optimization of the default implementation to take advantage of dynamic
partition pruning.
*/
int ha_partition::index_read_idx_map(uchar *buf, uint index,
const uchar *key,
key_part_map keypart_map,
enum ha_rkey_function find_flag)
{
int error= HA_ERR_KEY_NOT_FOUND;
DBUG_ENTER("ha_partition::index_read_idx_map");
decrement_statistics(&SSV::ha_read_key_count);
/* The start part is must be marked as used. */
DBUG_ASSERT(m_part_spec.start_part > m_part_spec.end_part ||
bitmap_is_set(&(m_part_info->read_partitions),
m_part_spec.start_part));
for (part= m_part_spec.start_part;
part <= m_part_spec.end_part;
part= bitmap_get_next_set(&m_part_info->read_partitions, part))
{
error= m_file[part]->ha_index_read_idx_map(buf, index, key,
keypart_map, find_flag);
if (likely(error != HA_ERR_KEY_NOT_FOUND &&
error != HA_ERR_END_OF_FILE))
break;
}
if (part <= m_part_spec.end_part)
m_last_part= part;
}
else
{
/*
If not only used with READ_EXACT, we should investigate if possible
to optimize for other find_flag's as well.
*/
DBUG_ASSERT(0);
/* fall back on the default implementation */
error= handler::index_read_idx_map(buf, index, key, keypart_map, find_flag);
}
DBUG_RETURN(error);
}
/*
Read next record in a forward index scan
SYNOPSIS
index_next()
buf Read row in MySQL Row Format
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
Used to read forward through the index.
*/
int ha_partition::index_next(uchar * buf)
{
DBUG_ENTER("ha_partition::index_next");
decrement_statistics(&SSV::ha_read_next_count);
/*
TODO(low priority):
If we want partition to work with the HANDLER commands, we
must be able to do index_last() -> index_prev() -> index_next()
and if direction changes, we must step back those partitions in
the record queue so we don't return a value from the wrong direction.
*/
if (m_index_scan_type == partition_index_last)
DBUG_RETURN(HA_ERR_WRONG_COMMAND);
if (!m_ordered_scan_ongoing)
{
DBUG_RETURN(handle_unordered_next(buf, FALSE));
}
DBUG_RETURN(handle_ordered_next(buf, FALSE));
}
/*
Read next record special
SYNOPSIS
index_next_same()
buf Read row in MySQL Row Format
key Key
keylen Length of key
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
This routine is used to read the next but only if the key is the same
as supplied in the call.
*/
/*
Read next record when performing index scan backwards
SYNOPSIS
index_prev()
buf Read row in MySQL Row Format
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
Used to read backwards through the index.
*/
int ha_partition::index_prev(uchar * buf)
{
DBUG_ENTER("ha_partition::index_prev");
decrement_statistics(&SSV::ha_read_prev_count);
/* TODO: read comment in index_next */
if (m_index_scan_type == partition_index_first)
DBUG_RETURN(HA_ERR_WRONG_COMMAND);
if (m_ordered_scan_ongoing)
DBUG_RETURN(handle_ordered_prev(buf));
DBUG_RETURN(handle_unordered_prev(buf));
}
/*
Start a read of one range with start and end key
SYNOPSIS
read_range_first()
start_key Specification of start key
end_key Specification of end key
eq_range_arg Is it equal range
sorted Should records be returned in sorted order
RETURN VALUE
>0 Error code 0 Success
DESCRIPTION
We reimplement read_range_first since we don't want the compare_key
check at the end. This is already performed in the partition handler.
read_range_next is very much different due to that we need to scan
all underlying handlers.
*/
int ha_partition::read_range_first(const key_range *start_key,
const key_range *end_key,
bool eq_range_arg, bool sorted)
{
int error;
DBUG_ENTER("ha_partition::read_range_first");
/**
Create a copy of all keys used by multi_range_read()
@retval 0 ok
@retval HA_ERR_END_OF_FILE no keys in range
@retval other value: error
TODO to save memory:
- If (mrr_mode & HA_MRR_MATERIALIZED_KEYS) is set then the keys data is
stable and we don't have to copy the keys, only store a pointer to the
key.
- When allocating key data, store things in a MEM_ROOT buffer instead of
a malloc() per key. This will simplify and speed up the current code
and use less memory.
*/
int ha_partition::multi_range_key_create_key(RANGE_SEQ_IF *seq,
range_seq_t seq_it)
{
uint i, length;
key_range *start_key, *end_key;
KEY_MULTI_RANGE *range;
DBUG_ENTER("ha_partition::multi_range_key_create_key");
bitmap_clear_all(&m_mrr_used_partitions);
m_mrr_range_length= 0;
bzero(m_part_mrr_range_length,
sizeof(*m_part_mrr_range_length) * m_tot_parts);
if (!m_mrr_range_first)
{
if (!(m_mrr_range_first= (PARTITION_KEY_MULTI_RANGE *)
my_multi_malloc(PSI_INSTRUMENT_ME, MYF(MY_WME),
&m_mrr_range_current, sizeof(PARTITION_KEY_MULTI_RANGE),
NullS)))
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
/* Copy key to those partitions that needs it */
for (i= m_part_spec.start_part; i <= m_part_spec.end_part; i++)
{
if (bitmap_is_set(&(m_part_info->read_partitions), i))
{
bitmap_set_bit(&m_mrr_used_partitions, i);
m_part_mrr_range_length[i]++;
m_part_mrr_range_current[i]->partition_key_multi_range=
m_mrr_range_current;
if (!m_part_mrr_range_current[i]->next)
{
PARTITION_PART_KEY_MULTI_RANGE *tmp_part_mrr_range;
if (!(tmp_part_mrr_range= (PARTITION_PART_KEY_MULTI_RANGE *)
my_malloc(PSI_INSTRUMENT_ME, sizeof(PARTITION_PART_KEY_MULTI_RANGE),
MYF(MY_WME | MY_ZEROFILL))))
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
if (!m_mrr_range_current->next)
{
/* Add end of range sentinel */
PARTITION_KEY_MULTI_RANGE *tmp_mrr_range;
if (!(tmp_mrr_range= (PARTITION_KEY_MULTI_RANGE *)
my_malloc(PSI_INSTRUMENT_ME, sizeof(PARTITION_KEY_MULTI_RANGE), MYF(MY_WME))))
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
for (i= 0; i < m_tot_parts; i++)
{
FT_INFO *m_handler= info->part_ft_info[i];
DBUG_ASSERT(!m_handler ||
(m_handler->please && m_handler->please->close_search));
if (m_handler &&
m_handler->please &&
m_handler->please->close_search)
m_handler->please->close_search(m_handler);
}
DBUG_VOID_RETURN;
}
/* Partition Full Text search function table */
_ft_vft partition_ft_vft =
{
NULL, // partition_ft_read_next
partition_ft_find_relevance,
partition_ft_close_search,
partition_ft_get_relevance,
NULL // partition_ft_reinit_search
};
/**
Initialize a full text search.
*/
int ha_partition::ft_init()
{
int error;
uint i= 0;
uint32 part_id;
DBUG_ENTER("ha_partition::ft_init");
DBUG_PRINT("info", ("partition this: %p", this));
/*
For operations that may need to change data, we may need to extend
read_set.
*/
if (get_lock_type() == F_WRLCK)
{
/*
If write_set contains any of the fields used in partition and
subpartition expression, we need to set all bits in read_set because
the row may need to be inserted in a different [sub]partition. In
other words update_row() can be converted into write_row(), which
requires a complete record.
*/
if (bitmap_is_overlapping(&m_part_info->full_part_field_set,
table->write_set))
bitmap_set_all(table->read_set);
else
{
/*
Some handlers only read fields as specified by the bitmap for the
read set. For partitioned handlers we always require that the
fields of the partition functions are read such that we can
calculate the partition id to place updated and deleted records.
*/
bitmap_union(table->read_set, &m_part_info->full_part_field_set);
}
}
/* Now we see what the index of our first important partition is */
DBUG_PRINT("info", ("m_part_info->read_partitions: %p",
(void *) m_part_info->read_partitions.bitmap));
part_id= bitmap_get_first_set(&(m_part_info->read_partitions));
DBUG_PRINT("info", ("m_part_spec.start_part %u", (uint) part_id));
if (part_id == MY_BIT_NONE)
{
error= 0;
goto err1;
}
DBUG_PRINT("info", ("ft_init on partition %u", (uint) part_id));
/*
ft_end() is needed for partitioning to reset internal data if scan
is already in use
*/
if (m_pre_calling)
{
if (unlikely((error= pre_ft_end())))
goto err1;
}
else
ft_end();
m_index_scan_type= partition_ft_read;
for (i= part_id; i < m_tot_parts; i++)
{
if (bitmap_is_set(&(m_part_info->read_partitions), i))
{
error= m_pre_calling ? m_file[i]->pre_ft_init() : m_file[i]->ft_init();
if (unlikely(error))
goto err2;
}
}
m_scan_value= 1;
m_part_spec.start_part= part_id;
m_part_spec.end_part= m_tot_parts - 1;
m_ft_init_and_first= TRUE;
DBUG_PRINT("info", ("m_scan_value: %u", m_scan_value));
DBUG_RETURN(0);
err2:
late_extra_no_cache(part_id);
while ((int)--i >= (int)part_id)
{
if (bitmap_is_set(&(m_part_info->read_partitions), i))
{
if (m_pre_calling)
m_file[i]->pre_ft_end();
else
m_file[i]->ft_end();
}
}
err1:
m_scan_value= 2;
m_part_spec.start_part= NO_CURRENT_PART_ID;
DBUG_RETURN(error);
}
/**
Initialize a full text search during a bulk access request.
*/
int ha_partition::pre_ft_init()
{
bool save_m_pre_calling;
int error;
DBUG_ENTER("ha_partition::pre_ft_init");
save_m_pre_calling= m_pre_calling;
m_pre_calling= TRUE;
error= ft_init();
m_pre_calling= save_m_pre_calling;
DBUG_RETURN(error);
}
if (restore)
{
/*
We protect only blob cache (value or read_value). If the cache was
empty that doesn't mean the blob was empty. Blobs allocated by a
storage engine should work just fine.
*/
if (!s.blob.is_empty())
blob->swap(s.blob, s.set_read_value);
}
else
{
bool set_read_value;
String *cached= blob->cached(&set_read_value);
if (cached)
{
cached->swap(s.blob);
s.set_read_value= set_read_value;
}
}
}
table->move_fields(table->field, table->record[0], rec_buf);
}
/**
Initialize a full text search using the extended API.
@param flags Search flags
@param inx Key number
@param key Key value
@return FT_INFO structure if successful
NULL otherwise
*/
if (part_id == NO_CURRENT_PART_ID)
{
/*
The original set of partitions to scan was empty and thus we report
the result here.
*/
DBUG_PRINT("info", ("NO_CURRENT_PART_ID"));
goto end;
}
DBUG_ASSERT(m_scan_value == 1);
if (m_ft_init_and_first) // First call to ft_read()
{
m_ft_init_and_first= FALSE;
if (!bulk_access_executing)
{
error= handle_pre_scan(FALSE, check_parallel_search());
if (m_pre_calling || error)
DBUG_RETURN(error);
}
late_extra_cache(part_id);
}
file= m_file[part_id];
while (TRUE)
{
if (!(result= file->ft_read(buf)))
{
/* Found row: remember position and return it. */
m_part_spec.start_part= m_last_part= part_id;
table->status= 0;
DBUG_RETURN(0);
}
/*
if we get here, then the current partition ft_next returned failure
*/
if (result != HA_ERR_END_OF_FILE)
goto end_dont_reset_start_part; // Return error
/* End current partition */
late_extra_no_cache(part_id);
DBUG_PRINT("info", ("stopping using partition %u", (uint) part_id));
/* Shift to next partition */
while (++part_id < m_tot_parts &&
!bitmap_is_set(&(m_part_info->read_partitions), part_id))
;
if (part_id >= m_tot_parts)
{
result= HA_ERR_END_OF_FILE;
break;
}
m_part_spec.start_part= m_last_part= part_id;
file= m_file[part_id];
DBUG_PRINT("info", ("now using partition %u", (uint) part_id));
late_extra_cache(part_id);
}
/*
@brief
Check if current index scan needs to use a priority queue for merging
index scan outputs of involved partitions. If not, it can just scan
one partition after another.
@detail
An index read operation expected to produce records in [reverse] index
order. This holds for for each partition.
Then, we need to do either of the two: 1."Merging", ha_partition also calls this "Ordered Scan": ordered
streams of records from multiple partitions are merged together (using
a Priority Queue) to produce one single ordered stream. 2."No merging", ha partition also calls it "Unordered Scan": first return
records from partition P_x, then P_{x+1}, and so forth.
Records will come in the desired order. 2.1 Enumerate partitions in the reverse order: P_y, P_{y-1}, etc.
Basic case when No-Merging can be done:
Case 1:
CREATE TABLE t (
...
INDEX(col1, ...),
) PARTITION BY RANGE(col1) ...;
In this case any record in partition Px should come before any record
in partition P{x+1} so can just scan P1, P2, ...
Note that subpartitioning (which can only be done by hash(some_column)
cannot be handled: we'll need to order records from subpartitions.
Case 2: The index has a prefix but it's constant for the range we're
scanning:
CREATE TABLE t (
...
INDEX(prefix, col1, suffix),
) PARTITION BY RANGE(col1) ...;
SELECT * FROM t1 WHERE prefix=const ... ORDER BY col1, suffix;
@return
true - Yes, can use Unordered Scan and records will come in the required
order.
false - No, use Ordered Scan: merge ordered streams with Priority Queue.
*/
bool ha_partition::can_skip_merging_scans()
{
Field *part_field= NULL;
uint i;
m_unordered_prefix_len= 0;
while (table_list->parent_l)
table_list= table_list->parent_l;
select_lex= table_list->select_lex;
DBUG_PRINT("info",("partition select_lex: %p", select_lex)); if (!select_lex) goto not_parallel; if (!select_lex->limit_params.explicit_limit)
{
DBUG_PRINT("info",("partition not using explicit_limit")); goto parallel;
}
for (i= m_part_spec.start_part; i <= m_part_spec.end_part; i++)
{ if (!(bitmap_is_set(&(m_part_info->read_partitions), i))) continue; int error;
handler *file= m_file[i];
switch (m_index_scan_type) { case partition_index_read:
error= file->pre_index_read_map(m_start_key.key,
m_start_key.keypart_map,
m_start_key.flag,
use_parallel); break; case partition_index_first:
error= file->pre_index_first(use_parallel); break; case partition_index_last:
error= file->pre_index_last(use_parallel); break; case partition_index_read_last:Thejava.lang.StringIndexOutOfBoundsException: Range [33, 29) out of bounds for length 69
error= file->
m_start_key java.lang.StringIndexOutOfBoundsException: Range [14, 13) out of bounds for length 64
use_parallel); break programifnottheFoundationjava.lang.StringIndexOutOfBoundsException: Index 73 out of bounds for length 73 case partition_read_range:
error=file->re_read_range_firstmstart_keykey m_start_key: NULL,
end_range,eq_range,TRUE use_parallel)java.lang.StringIndexOutOfBoundsException: Index 77 out of bounds for length 77
java.lang.StringIndexOutOfBoundsException: Index 12 out of bounds for length 12 case partition_read_multi_range: ifjava.lang.StringIndexOutOfBoundsException: Range [47, 46) out of bounds for length 49 continue;
break; case partition_ft_read
error= file->pre_ft_read(use_parallel); break;
if (i == NO_CURRENT_PART_ID)
DBUG_RETURN(HA_ERR_END_OF_FILE); if (i >= m_tot_parts)
{ /* Should never happen! */
DBUG_ASSERT(0);
DBUG_RETURN(HA_ERR_END_OF_FILE);
}
file= m_file[i];
int ha_partition::handle_unordered_scan_next_partition(uchar * buf, bool is_last_or_prev)
{ /* Whether to start from the highest partition. */ bool reverse_order= (is_last_or_prev != m_unordered_reverse_scan);
uint i= reverse_order ? m_part_spec.end_part : m_part_spec.start_part; int saved_error= HA_ERR_END_OF_FILE;
DBUG_ENTER("ha_partition::handle_unordered_scan_next_partition");
if (m_pi_scan_method == INDEX_SCAN_ORDERED)
m_pi_scan_method= INDEX_SCAN_BOTH; else
m_pi_scan_method= INDEX_SCAN_UNORDERED; /* Find the first partition to scan. */ if (reverse_order)
{ if (i < m_part_info->read_partitions.n_bits - 1)
i= bitmap_get_prev_set(&m_part_info->read_partitions, i + 1); else
i= bitmap_get_last_set(&m_part_info->read_partitions);
} else
{ if (i)
i= bitmap_get_next_set(&m_part_info->read_partitions, i - 1); else
i= bitmap_get_first_set(&m_part_info->read_partitions);
}
while (i >= m_part_spec.start_part && i <= m_part_spec.end_part)
{ int error;
handler *file= m_file[i]; if (reverse_order)
m_part_spec.end_part= i; else
m_part_spec.start_part= i;
switch (m_index_scan_type) { case partition_read_multi_range: if (!bitmap_is_set(&m_mrr_used_partitions, i)) continue;
DBUG_PRINT("info", ("read_multi_range on partition %u", i));
error= file->multi_range_read_next(&m_range_info[i]); break; case partition_read_range:
DBUG_PRINT("info", ("read_range_first on partition %u", i));
error= file->read_range_first(m_start_key.key? &m_start_key: NULL,
end_range, eq_range, FALSE); break; case partition_index_read:
DBUG_PRINT("info", ("index_read on partition %u", i));
error= file->ha_index_read_map(buf, m_start_key.key,
m_start_key.keypart_map,
m_start_key.flag); break; case partition_index_first:
DBUG_PRINT("info", ("index_first on partition %u", i));
error= file->ha_index_first(buf); break; case partition_index_last:
DBUG_PRINT("info", ("index_last on partition %u", i));
error= file->ha_index_last(buf); break; default:
DBUG_ASSERT(FALSE);
DBUG_RETURN(1);
} if (likely(!error))
{
m_last_part= i;
DBUG_RETURN(0);
} if (likely((error != HA_ERR_END_OF_FILE) &&
(error != HA_ERR_KEY_NOT_FOUND)))
DBUG_RETURN(error);
/* IfHA_ERR_KEY_NOT_FOUND,wemustreturnthaterrorinsteadof HA_ERR_END_OF_FILE,tobeabletocontinuesearch.
*/ if (saved_error != HA_ERR_KEY_NOT_FOUND)
saved_error= error;
DBUG_PRINT("info", ("END_OF_FILE/KEY_NOT_FOUND on partition %u", i)); if (reverse_order)
i= bitmap_get_prev_set(&m_part_info->read_partitions, i); else
i= bitmap_get_next_set(&m_part_info->read_partitions, i);
} if (saved_error == HA_ERR_END_OF_FILE)
m_part_spec.start_part= NO_CURRENT_PART_ID;
DBUG_RETURN(saved_error);
}
if (m_pi_scan_method == INDEX_SCAN_UNORDERED)
m_pi_scan_method= INDEX_SCAN_BOTH;
else
m_pi_scan_method= INDEX_SCAN_ORDERED;
if (m_pre_calling)
error= handle_pre_scan(reverse_order, m_pre_call_use_parallel);
else
error= handle_pre_scan(reverse_order, check_parallel_search());
if (unlikely(error))
DBUG_RETURN(error);
if (m_key_not_found)
{
/* m_key_not_found was set in the previous call to this function */
m_key_not_found= false;
bitmap_clear_all(&m_key_not_found_partitions);
}
m_top_entry= NO_CURRENT_PART_ID;
DBUG_PRINT("info", ("partition queue_remove_all(1)"));
queue_remove_all(&m_queue);
DBUG_ASSERT(bitmap_is_set(&m_part_info->read_partitions,
m_part_spec.start_part));
/*
Position part_rec_buf_ptr to point to the first used partition >=
start_part. There may be partitions marked by used_partitions,
but is before start_part. These partitions has allocated record buffers
but is dynamically pruned, so those buffers must be skipped.
*/
for (i= bitmap_get_first_set(&m_part_info->read_partitions);
i < m_part_spec.start_part;
i= bitmap_get_next_set(&m_part_info->read_partitions, i))
{
part_rec_buf_ptr+= m_priority_queue_rec_len;
}
DBUG_PRINT("info", ("m_part_spec.start_part %u first_used_part %u",
m_part_spec.start_part, i));
for (/* continue from above */ ;
i <= m_part_spec.end_part ;
i= bitmap_get_next_set(&m_part_info->read_partitions, i),
part_rec_buf_ptr+= m_priority_queue_rec_len)
{
DBUG_PRINT("info", ("reading from part %u (scan_type: %u)",
i, m_index_scan_type));
DBUG_ASSERT(i == uint2korr(part_rec_buf_ptr + ORDERED_PART_NUM_OFFSET));
uchar *rec_buf_ptr= part_rec_buf_ptr + ORDERED_REC_OFFSET;
handler *file= m_file[i];
switch (m_index_scan_type) {
case partition_index_read:
error=
read_with_icp(table,
pushed_idx_cond != nullptr,
rec_buf_ptr,
m_rec_length,
[this, file] (uchar* read_buf) {
return file->ha_index_read_map(read_buf,
m_start_key.key,
m_start_key.keypart_map,
m_start_key.flag);
});
/* Caller has specified reverse_order */
break;
case partition_index_first:
error= file->ha_index_first(rec_buf_ptr);
reverse_order= FALSE;
break;
case partition_index_last:
error= file->ha_index_last(rec_buf_ptr);
reverse_order= TRUE;
break;
case partition_read_range:
{
/*
This can only read record to table->record[0], as it was set when
the table was being opened. We have to memcpy data ourselves.
*/
error= file->read_range_first(m_start_key.key? &m_start_key: NULL,
end_range, eq_range, TRUE);
if (likely(!error))
memcpy(rec_buf_ptr, table->record[0], m_rec_length);
reverse_order= FALSE;
break;
}
case partition_read_multi_range:
{
if (!bitmap_is_set(&m_mrr_used_partitions, i))
continue;
DBUG_PRINT("info", ("partition %u", i));
error= file->multi_range_read_next(&m_range_info[i]);
DBUG_PRINT("info", ("error: %d", error));
if (error == HA_ERR_KEY_NOT_FOUND || error == HA_ERR_END_OF_FILE)
{
bitmap_clear_bit(&m_mrr_used_partitions, i);
continue;
}
if (likely(!error))
{
memcpy(rec_buf_ptr, table->record[0], m_rec_length);
reverse_order= FALSE;
m_stock_range_seq[i]= (((PARTITION_KEY_MULTI_RANGE *)
m_range_info[i])->id);
/* Test if the key is in the first key range */
if (m_stock_range_seq[i] != m_mrr_range_current->id)
{
/*
smallest_range_seq contains the smallest key range we have seen
so far
*/
if (!smallest_range_seq || smallest_range_seq > m_stock_range_seq[i])
smallest_range_seq= m_stock_range_seq[i];
continue;
}
}
break;
}
default:
DBUG_ASSERT(FALSE);
DBUG_RETURN(HA_ERR_END_OF_FILE);
}
if (likely(!error))
{
found= TRUE;
if (!m_using_extended_keys)
{
file->position(rec_buf_ptr);
memcpy(rec_buf_ptr + m_rec_length, file->ref, file->ref_length);
}
/*
Initialize queue without order first, simply insert
*/
queue_element(&m_queue, j++)= part_rec_buf_ptr;
if (table->s->blob_fields)
{
Ordered_blob_storage **storage=
*((Ordered_blob_storage ***) part_rec_buf_ptr);
swap_blobs(rec_buf_ptr, storage, false);
}
}
else if (error == HA_ERR_KEY_NOT_FOUND)
{
DBUG_PRINT("info", ("HA_ERR_KEY_NOT_FOUND from partition %u", i));
bitmap_set_bit(&m_key_not_found_partitions, i);
m_key_not_found= true;
saved_error= error;
}
else if (error != HA_ERR_END_OF_FILE)
{
DBUG_RETURN(error);
}
}
if (!found && smallest_range_seq)
{
/* We know that there is an existing row based on code above */
found= TRUE;
part_rec_buf_ptr= m_ordered_rec_buffer;
/*
No key found in the first key range
Collect all partitions that has a key in smallest_range_seq
*/
DBUG_PRINT("info", ("partition !found && smallest_range_seq"));
for (i= bitmap_get_first_set(&m_part_info->read_partitions);
i <= m_part_spec.end_part;
i= bitmap_get_next_set(&m_part_info->read_partitions, i))
{
DBUG_PRINT("info", ("partition current_part: %u", i));
if (i < m_part_spec.start_part)
{
part_rec_buf_ptr+= m_priority_queue_rec_len;
DBUG_PRINT("info", ("partition i < m_part_spec.start_part"));
continue;
}
if (!bitmap_is_set(&m_mrr_used_partitions, i))
{
part_rec_buf_ptr+= m_priority_queue_rec_len;
DBUG_PRINT("info", ("partition !bitmap_is_set(&m_mrr_used_partitions, i)"));
continue;
}
DBUG_ASSERT(i == uint2korr(part_rec_buf_ptr + ORDERED_PART_NUM_OFFSET));
if (smallest_range_seq == m_stock_range_seq[i])
{
m_stock_range_seq[i]= 0;
queue_element(&m_queue, j++)= (uchar *) part_rec_buf_ptr;
DBUG_PRINT("info", ("partition smallest_range_seq == m_stock_range_seq[i]"));
}
part_rec_buf_ptr+= m_priority_queue_rec_len;
}
/* Update global m_mrr_range_current to the current range */
while (m_mrr_range_current->id < smallest_range_seq)
m_mrr_range_current= m_mrr_range_current->next;
}
if (found)
{
/*
We found at least one partition with data, now sort all entries and
after that read the first entry and copy it to the buffer to return in.
*/
queue_set_max_at_top(&m_queue, reverse_order);
queue_set_cmp_arg(&m_queue, (void*) this);
m_queue.elements= j - queue_first_element(&m_queue);
queue_fix(&m_queue);
return_top_record(buf);
DBUG_PRINT("info", ("Record returned from partition %u", m_top_entry));
DBUG_RETURN(0);
}
DBUG_RETURN(saved_error);
}
/*
Return the top record in sort order
SYNOPSIS
return_top_record()
out:buf Row returned in MySQL Row Format
/**
Add index_next/prev from partitions without exact match.
If there where any partitions that returned HA_ERR_KEY_NOT_FOUND when
ha_index_read_map was done, those partitions must be included in the
following index_next/prev call.
*/
int ha_partition::handle_ordered_index_scan_key_not_found()
{
int error;
uint i, old_elements= m_queue.elements;
uchar *part_buf= m_ordered_rec_buffer;
uchar *curr_rec_buf= NULL;
DBUG_ENTER("ha_partition::handle_ordered_index_scan_key_not_found");
DBUG_PRINT("enter", ("partition this: %p", this));
DBUG_ASSERT(m_key_not_found);
/*
Loop over all used partitions to get the correct offset
into m_ordered_rec_buffer.
*/
for (i= bitmap_get_first_set(&m_part_info->read_partitions);
i < m_tot_parts;
i= bitmap_get_next_set(&m_part_info->read_partitions, i))
{
if (bitmap_is_set(&m_key_not_found_partitions, i))
{
/*
This partition is used and did return HA_ERR_KEY_NOT_FOUND
in index_read_map.
*/
curr_rec_buf= part_buf + ORDERED_REC_OFFSET;
error= m_file[i]->ha_index_next(curr_rec_buf);
/* HA_ERR_KEY_NOT_FOUND is not allowed from index_next! */
DBUG_ASSERT(error != HA_ERR_KEY_NOT_FOUND);
if (likely(!error))
{
DBUG_PRINT("info", ("partition queue_insert(1)"));
queue_insert(&m_queue, part_buf);
}
else if (error != HA_ERR_END_OF_FILE && error != HA_ERR_KEY_NOT_FOUND)
DBUG_RETURN(error);
}
part_buf += m_priority_queue_rec_len;
}
DBUG_ASSERT(curr_rec_buf);
bitmap_clear_all(&m_key_not_found_partitions);
m_key_not_found= false;
if (m_queue.elements > old_elements)
{
/* Update m_top_entry, which may have changed. */
uchar *key_buffer= queue_top(&m_queue);
m_top_entry= uint2korr(key_buffer);
}
DBUG_RETURN(0);
}
/*
Common routine to handle index_next with ordered results
SYNOPSIS
handle_ordered_next()
out:buf Read row in MySQL Row Format
next_same Called from index_next_same
RETURN VALUE
HA_ERR_END_OF_FILE End of scan 0 Success
other Error code
*/
int ha_partition::handle_ordered_next(uchar *buf, bool is_next_same)
{
int error;
DBUG_ENTER("ha_partition::handle_ordered_next");
if (m_top_entry == NO_CURRENT_PART_ID)
DBUG_RETURN(HA_ERR_END_OF_FILE);
if (m_key_not_found)
{
if (is_next_same)
{
/* Only rows which match the key. */
m_key_not_found= false;
bitmap_clear_all(&m_key_not_found_partitions);
}
else
{
/* There are partitions not included in the index record queue. */
uint old_elements= m_queue.elements;
if (unlikely((error= handle_ordered_index_scan_key_not_found())))
DBUG_RETURN(error);
/*
If the queue top changed, i.e. one of the partitions that gave
HA_ERR_KEY_NOT_FOUND in index_read_map found the next record,
return it.
Otherwise replace the old with a call to index_next (fall through).
*/
if (old_elements != m_queue.elements && part_id != m_top_entry)
{
return_top_record(buf);
DBUG_RETURN(0);
}
}
}
if (part_id >= m_tot_parts)
{
/* This should never happen! */
DBUG_ASSERT(0);
DBUG_RETURN(HA_ERR_END_OF_FILE);
}
file= m_file[part_id];
if (m_index_scan_type == partition_read_range)
{
error= file->read_range_next();
if (likely(!error))
{
memcpy(rec_buf, table->record[0], m_rec_length);
if (table->s->blob_fields)
{
Ordered_blob_storage **storage=
*((Ordered_blob_storage ***) part_rec_buf_ptr);
swap_blobs(rec_buf, storage, false);
}
}
}
else if (m_index_scan_type == partition_read_multi_range)
{
DBUG_PRINT("info", ("partition_read_multi_range route"));
DBUG_PRINT("info", ("part_id: %u", part_id));
bool get_next= FALSE;
error= file->multi_range_read_next(&m_range_info[part_id]);
DBUG_PRINT("info", ("error: %d", error));
if (unlikely(error == HA_ERR_KEY_NOT_FOUND))
error= HA_ERR_END_OF_FILE;
if (unlikely(error == HA_ERR_END_OF_FILE))
{
bitmap_clear_bit(&m_mrr_used_partitions, part_id);
DBUG_PRINT("info", ("partition m_queue.elements: %u", m_queue.elements));
if (m_queue.elements)
{
DBUG_PRINT("info", ("partition queue_remove_top(1)"));
queue_remove_top(&m_queue);
if (m_queue.elements)
{
return_top_record(buf);
DBUG_PRINT("info", ("Record returned from partition %u (3)",
m_top_entry));
DBUG_RETURN(0);
}
}
get_next= TRUE;
}
else if (likely(!error))
{
DBUG_PRINT("info", ("m_range_info[%u])->id: %u", part_id,
((PARTITION_KEY_MULTI_RANGE *)
m_range_info[part_id])->id));
DBUG_PRINT("info", ("m_mrr_range_current->id: %u",
m_mrr_range_current->id));
memcpy(rec_buf, table->record[0], m_rec_length);
if (table->s->blob_fields)
{
Ordered_blob_storage **storage= *((Ordered_blob_storage ***) part_rec_buf_ptr);
swap_blobs(rec_buf, storage, false);
}
if (((PARTITION_KEY_MULTI_RANGE *) m_range_info[part_id])->id !=
m_mrr_range_current->id)
{
m_stock_range_seq[part_id]=
((PARTITION_KEY_MULTI_RANGE *) m_range_info[part_id])->id;
DBUG_PRINT("info", ("partition queue_remove_top(2)"));
queue_remove_top(&m_queue);
if (!m_queue.elements)
get_next= TRUE;
}
}
if (get_next)
{
DBUG_PRINT("info", ("get_next route"));
uint i, j= 0, smallest_range_seq= UINT_MAX32;
for (i= m_part_spec.start_part; i <= m_part_spec.end_part; i++)
{
if (!(bitmap_is_set(&(m_part_info->read_partitions), i)))
continue;
if (!bitmap_is_set(&m_mrr_used_partitions, i))
continue;
if (smallest_range_seq > m_stock_range_seq[i])
smallest_range_seq= m_stock_range_seq[i];
}
if (unlikely((error= file->ha_index_prev(rec_buf))))
{
if (error == HA_ERR_END_OF_FILE && m_queue.elements)
{
DBUG_PRINT("info", ("partition queue_remove_top(4)"));
queue_remove_top(&m_queue);
if (m_queue.elements)
{
return_top_record(buf);
DBUG_PRINT("info", ("Record returned from partition %u (2)",
m_top_entry));
error= 0;
}
}
DBUG_RETURN(error);
}
queue_replace_top(&m_queue);
return_top_record(buf);
DBUG_PRINT("info", ("Record returned from partition %u", m_top_entry));
DBUG_RETURN(0);
}
/****************************************************************************
MODULE information calls
****************************************************************************/
/*
These are all first approximations of the extra, info, scan_time
and read_time calls
*/
/**
Helper function for sorting according to number of rows in descending order.
*/
SYNOPSIS
info()
flag Specifies what info is requested
RETURN VALUE
NONE
DESCRIPTION
::info() is used to return information to the optimizer.
Currently this table handler doesn't implement most of the fields
really needed. SHOW also makes use of this data
Another note, if your handler doesn't provide exact record count,
you will probably want to have the following in your code:
if (records < 2)
records = 2;
The reason is that the server will optimize for cases of only a single
record. If in a table scan you don't know the number of records
it will probably be better to set records to two so you can return
as many records as you need.
Along with records a few more variables you may wish to set are:
records
deleted
data_file_length
index_file_length
delete_length
check_time
Take a look at the public variables in handler.h for more information.
Some flags that are not implemented
HA_STATUS_POS:
This parameter is never used from the MySQL Server. It is checked in a
place in MyISAM so could potentially be used by MyISAM specific
programs.
HA_STATUS_NO_LOCK:
This is declared and often used. It's only used by MyISAM.
It means that MySQL doesn't need the absolute latest statistics
information. This may save the handler from doing internal locks while
retrieving statistics data.
*/
int ha_partition::info(uint flag)
{
int error;
uint no_lock_flag= flag & HA_STATUS_NO_LOCK;
uint extra_var_flag= flag & HA_STATUS_VARIABLE_EXTRA;
DBUG_ENTER("ha_partition::info");
#ifndef DBUG_OFF
if (bitmap_is_set_all(&(m_part_info->read_partitions)))
DBUG_PRINT("info", ("All partitions are used"));
#endif /* DBUG_OFF */
if (flag & HA_STATUS_AUTO)
{
bool auto_inc_is_first_in_idx= (table_share->next_number_keypart == 0);
bool all_parts_opened= true;
DBUG_PRINT("info", ("HA_STATUS_AUTO"));
if (!table->found_next_number_field)
stats.auto_increment_value= 0;
else if (part_share->auto_inc_initialized)
{
lock_auto_increment();
stats.auto_increment_value= part_share->next_auto_inc_val;
unlock_auto_increment();
}
else
{
lock_auto_increment();
/* to avoid two concurrent initializations, check again when locked */
if (part_share->auto_inc_initialized)
stats.auto_increment_value= part_share->next_auto_inc_val;
else
{
/*
The auto-inc mutex in the table_share is locked, so we do not need
to have the handlers locked.
HA_STATUS_NO_LOCK is not checked, since we cannot skip locking
the mutex, because it is initialized.
*/
handler *file, **file_array;
ulonglong auto_increment_value= 0;
file_array= m_file;
DBUG_PRINT("info",
("checking all partitions for auto_increment_value"));
do
{
if (!bitmap_is_set(&m_opened_partitions, (uint)(file_array - m_file)))
{
/*
Some partitions aren't opened.
So we can't calculate the autoincrement.
*/
all_parts_opened= false;
break;
}
file= *file_array;
if ((error= file->info(HA_STATUS_AUTO | no_lock_flag)))
{
unlock_auto_increment();
DBUG_RETURN(error);
}
set_if_bigger(auto_increment_value,
file->stats.auto_increment_value);
} while (*(++file_array));
DBUG_ASSERT(!all_parts_opened || auto_increment_value);
stats.auto_increment_value= auto_increment_value;
if (all_parts_opened && auto_inc_is_first_in_idx)
{
set_if_bigger(part_share->next_auto_inc_val,
auto_increment_value);
if (can_use_for_auto_inc_init())
part_share->auto_inc_initialized= true;
DBUG_PRINT("info", ("initializing next_auto_inc_val to %lu",
(ulong) part_share->next_auto_inc_val));
}
}
unlock_auto_increment();
}
}
if (flag & HA_STATUS_VARIABLE)
{
uint i;
DBUG_PRINT("info", ("HA_STATUS_VARIABLE"));
/*
Calculates statistical variables
records: Estimate of number records in table
We report sum (always at least 2 if not empty)
deleted: Estimate of number holes in the table due to
deletes
We report sum
data_file_length: Length of data file, in principle bytes in table
We report sum
index_file_length: Length of index file, in principle bytes in
indexes in the table
We report sum
delete_length: Length of free space easily used by new records in table
We report sum
mean_record_length:Mean record length in the table
We calculate this
check_time: Time of last check (only applicable to MyISAM)
We report last time of all underlying handlers
*/
handler *file;
stats.records= 0;
stats.deleted= 0;
stats.data_file_length= 0;
stats.index_file_length= 0;
stats.delete_length= 0;
stats.check_time= 0;
stats.checksum= 0;
stats.checksum_null= TRUE;
for (i= bitmap_get_first_set(&m_part_info->read_partitions);
i < m_tot_parts;
i= bitmap_get_next_set(&m_part_info->read_partitions, i))
{
file= m_file[i];
if ((error= file->info(HA_STATUS_VARIABLE | no_lock_flag | extra_var_flag)))
DBUG_RETURN(error);
stats.records+= file->stats.records;
stats.deleted+= file->stats.deleted;
stats.data_file_length+= file->stats.data_file_length;
stats.index_file_length+= file->stats.index_file_length;
stats.delete_length+= file->stats.delete_length;
if (file->stats.check_time > stats.check_time)
stats.check_time= file->stats.check_time;
if (!file->stats.checksum_null)
{
stats.checksum+= file->stats.checksum;
stats.checksum_null= FALSE;
}
}
if (stats.records && stats.records < 2 &&
!(m_file[0]->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT))
stats.records= 2;
if (stats.records > 0)
stats.mean_rec_length= (ulong) (stats.data_file_length / stats.records);
else
stats.mean_rec_length= 0;
}
if (flag & HA_STATUS_CONST)
{
DBUG_PRINT("info", ("HA_STATUS_CONST"));
/*
Recalculate loads of constant variables. MyISAM also sets things
directly on the table share object.
Check whether this should be fixed since handlers should not
change things directly on the table object.
Monty comment: This should NOT be changed! It's the handlers
responsibility to correct table->s->keys_xxxx information if keys
have been disabled.
The most important parameters set here is records per key on
all indexes. block_size and primary key ref_length.
For each index there is an array of rec_per_key.
As an example if we have an index with three attributes a,b and c
we will have an array of 3 rec_per_key.
rec_per_key[0] is an estimate of number of records divided by
number of unique values of the field a.
rec_per_key[1] is an estimate of the number of records divided
by the number of unique combinations of the fields a and b.
rec_per_key[2] is an estimate of the number of records divided
by the number of unique combinations of the fields a,b and c.
Many handlers only set the value of rec_per_key when all fields
are bound (rec_per_key[2] in the example above).
If the handler doesn't support statistics, it should set all of the
above to 0.
We first scans through all partitions to get the one holding most rows.
We will then allow the handler with the most rows to set
the rec_per_key and use this as an estimate on the total table.
max_data_file_length: Maximum data file length
We ignore it, is only used in
SHOW TABLE STATUS
max_index_file_length: Maximum index file length
We ignore it since it is never used
block_size: Block size used
We set it to the value of the first handler
ref_length: We set this to the value calculated
and stored in local object
create_time: Creation time of table
So we calculate these constants by using the variables from the
handler with most rows.
*/
handler *file, **file_array;
ulonglong max_records= 0;
uint32 i= 0;
uint32 handler_instance= 0;
bool handler_instance_set= 0;
file_array= m_file;
do
{
file= *file_array;
if (bitmap_is_set(&(m_opened_partitions), (uint)(file_array - m_file)))
{
/* Get variables if not already done */
if (!(flag & HA_STATUS_VARIABLE) ||
!bitmap_is_set(&(m_part_info->read_partitions),
(uint) (file_array - m_file)))
if ((error= file->info(HA_STATUS_VARIABLE | no_lock_flag | extra_var_flag)))
DBUG_RETURN(error);
if (file->stats.records > max_records || !handler_instance_set)
{
handler_instance_set= 1;
max_records= file->stats.records;
handler_instance= i;
}
}
i++;
} while (*(++file_array));
/*
Sort the array of part_ids by number of records in
in descending order.
*/
my_qsort2((void*) m_part_ids_sorted_by_num_of_records,
m_tot_parts,
sizeof(uint32),
compare_number_of_records,
this);
file= m_file[handler_instance];
if ((error= file->info(HA_STATUS_CONST | no_lock_flag)))
DBUG_RETURN(error);
stats.block_size= file->stats.block_size;
stats.create_time= file->stats.create_time;
ref_length= m_ref_length;
}
if (flag & HA_STATUS_ERRKEY)
{
handler *file= m_file[m_last_part];
DBUG_PRINT("info", ("info: HA_STATUS_ERRKEY"));
/*
This flag is used to get index number of the unique index that
reported duplicate key
We will report the errkey on the last handler used and ignore the rest
Note: all engines does not support HA_STATUS_ERRKEY, so set errkey.
*/
file->errkey= errkey;
if ((error= file->info(HA_STATUS_ERRKEY | no_lock_flag)))
DBUG_RETURN(error);
errkey= file->errkey;
}
if (flag & HA_STATUS_TIME)
{
handler *file, **file_array;
DBUG_PRINT("info", ("info: HA_STATUS_TIME"));
/*
This flag is used to set the latest update time of the table.
Used by SHOW commands
We will report the maximum of these times
*/
stats.update_time= 0;
file_array= m_file;
do
{
file= *file_array;
if ((error= file->info(HA_STATUS_TIME | no_lock_flag)))
DBUG_RETURN(error);
if (file->stats.update_time > stats.update_time)
stats.update_time= file->stats.update_time;
} while (*(++file_array));
}
DBUG_RETURN(0);
}
static int start_keyread_cb(handler* h, void *p)
{
return h->ha_start_keyread(*(uint*)p);
}
static int end_keyread_cb(handler* h, void *unused)
{
return h->ha_end_keyread();
}
/**
General function to prepare handler for certain behavior.
@param[in] operation operation to execute
@return status
@retval 0 success
@retval >0 error code
@detail
extra() is called whenever the server wishes to send a hint to
the storage engine. The MyISAM engine implements the most hints.
We divide the parameters into the following categories: 1) Operations used by most handlers 2) Operations used by some non-MyISAM handlers 3) Operations used only by MyISAM 4) Operations only used by temporary tables for query processing 5) Operations only used by MyISAM internally 6) Operations not used at all 7) Operations only used by federated tables for query processing 8) Operations only used by NDB 9) Operations only used by MERGE
The partition handler need to handle category 1), 2) and 3).
1) Operations used by most handlers
-----------------------------------
HA_EXTRA_RESET:
This option is used by most handlers and it resets the handler state
to the same state as after an open call. This includes releasing
any READ CACHE or WRITE CACHE or other internal buffer used.
It is called from the reset method in the handler interface. There are
three instances where this is called. 1) After completing a INSERT ... SELECT ... query the handler for the
table inserted into is reset 2) It is called from close_thread_table which in turn is called from
close_thread_tables except in the case where the tables are locked
in which case ha_commit_stmt is called instead.
It is only called from here if refresh_version hasn't changed and the
table is not an old table when calling close_thread_table.
close_thread_tables is called from many places as a general clean up
function after completing a query. 3) It is called when deleting the QUICK_RANGE_SELECT object if the
QUICK_RANGE_SELECT object had its own handler object. It is called
immediately before close of this local handler object.
HA_EXTRA_KEYREAD:
HA_EXTRA_NO_KEYREAD:
These parameters are used to provide an optimisation hint to the handler.
If HA_EXTRA_KEYREAD is set it is enough to read the index fields, for
many handlers this means that the index-only scans can be used and it
is not necessary to use the real records to satisfy this part of the
query. Index-only scans is a very important optimisation for disk-based
indexes. For main-memory indexes most indexes contain a reference to the
record and thus KEYREAD only says that it is enough to read key fields.
HA_EXTRA_NO_KEYREAD disables this for the handler, also HA_EXTRA_RESET
will disable this option.
The handler will set HA_KEYREAD_ONLY in its table flags to indicate this
feature is supported.
HA_EXTRA_FLUSH:
Indication to flush tables to disk, is supposed to be used to
ensure disk based tables are flushed at end of query execution.
Currently is never used.
HA_EXTRA_FORCE_REOPEN:
Only used by MyISAM and Archive, called when altering table,
closing tables to enforce a reopen of the table files.
2) Operations used by some non-MyISAM handlers
----------------------------------------------
HA_EXTRA_KEYREAD_PRESERVE_FIELDS:
This is a strictly InnoDB feature that is more or less undocumented.
When it is activated InnoDB copies field by field from its fetch
cache instead of all fields in one memcpy. Have no idea what the
purpose of this is.
Cut from include/my_base.h:
When using HA_EXTRA_KEYREAD, overwrite only key member fields and keep
other fields intact. When this is off (by default) InnoDB will use memcpy
to overwrite entire row.
HA_EXTRA_IGNORE_DUP_KEY:
HA_EXTRA_NO_IGNORE_DUP_KEY:
Informs the handler to we will not stop the transaction if we get an
duplicate key errors during insert/update.
Always called in pair, triggered by INSERT IGNORE and other similar
SQL constructs.
Not used by MyISAM.
3) Operations used only by MyISAM
---------------------------------
HA_EXTRA_NORMAL:
Only used in MyISAM to reset quick mode, not implemented by any other
handler. Quick mode is also reset in MyISAM by HA_EXTRA_RESET.
It is called after completing a successful DELETE query if the QUICK
option is set.
HA_EXTRA_QUICK:
When the user does DELETE QUICK FROM table where-clause; this extra
option is called before the delete query is performed and
HA_EXTRA_NORMAL is called after the delete query is completed.
Temporary tables used internally in MySQL always set this option
The meaning of quick mode is that when deleting in a B-tree no merging
of leafs is performed. This is a common method and many large DBMS's
actually only support this quick mode since it is very difficult to
merge leaves in a tree used by many threads concurrently.
HA_EXTRA_CACHE:
This flag is usually set with extra_opt along with a cache size.
The size of this buffer is set by the user variable
record_buffer_size. The value of this cache size is the amount of
data read from disk in each fetch when performing a table scan.
This means that before scanning a table it is normal to call
extra with HA_EXTRA_CACHE and when the scan is completed to call
HA_EXTRA_NO_CACHE to release the cache memory.
Some special care is taken when using this extra parameter since there
could be a write ongoing on the table in the same statement. In this
one has to take special care since there might be a WRITE CACHE as
well. HA_EXTRA_CACHE specifies using a READ CACHE and using
READ CACHE and WRITE CACHE at the same time is not possible.
Only MyISAM currently use this option.
It is set when doing full table scans using rr_sequential and
reset when completing such a scan with end_read_record
(resetting means calling extra with HA_EXTRA_NO_CACHE).
It is set in filesort.cc for MyISAM internal tables and it is set in
a multi-update where HA_EXTRA_CACHE is called on a temporary result
table and after that ha_rnd_init(0) on table to be updated
and immediately after that HA_EXTRA_NO_CACHE on table to be updated.
Apart from that it is always used from init_read_record but not when
used from UPDATE statements. It is not used from DELETE statements
with ORDER BY and LIMIT but it is used in normal scan loop in DELETE
statements. The reason here is that DELETE's in MyISAM doesn't move
existings data rows.
It is also set in copy_data_between_tables when scanning the old table
to copy over to the new table.
And it is set in join_init_read_record where quick objects are used
to perform a scan on the table. In this case the full table scan can
even be performed multiple times as part of the nested loop join.
For purposes of the partition handler it is obviously necessary to have
special treatment of this extra call. If we would simply pass this
extra call down to each handler we would allocate
cache size * no of partitions amount of memory and this is not
necessary since we will only scan one partition at a time when doing
full table scans.
Thus we treat it by first checking whether we have MyISAM handlers in
the table, if not we simply ignore the call and if we have we will
record the call but will not call any underlying handler yet. Then
when performing the sequential scan we will check this recorded value
and call extra_opt whenever we start scanning a new partition.
HA_EXTRA_NO_CACHE:
When performing a UNION SELECT HA_EXTRA_NO_CACHE is called from the
flush method in the select_union class.
It is used to some extent when insert delayed inserts.
See HA_EXTRA_RESET_STATE for use in conjunction with delete_all_rows().
It should be ok to call HA_EXTRA_NO_CACHE on all underlying handlers
if they are MyISAM handlers. Other handlers we can ignore the call
for. If no cache is in use they will quickly return after finding
this out. And we also ensure that all caches are disabled and no one
is left by mistake.
In the future this call will probably be deleted and we will instead call
::reset();
HA_EXTRA_WRITE_CACHE:
See above, called from various places. It is mostly used when we
do INSERT ... SELECT
No special handling to save cache space is developed currently.
HA_EXTRA_PREPARE_FOR_UPDATE:
This is called as part of a multi-table update. When the table to be
updated is also scanned then this informs MyISAM handler to drop any
caches if dynamic records are used (fixed size records do not care
about this call). We pass this along to the first partition to scan, and
flag that it is to be called after HA_EXTRA_CACHE when moving to the next
partition to scan.
HA_EXTRA_PREPARE_FOR_DROP:
Only used by MyISAM, called in preparation for a DROP TABLE.
It's used mostly by Windows that cannot handle dropping an open file.
On other platforms it has the same effect as HA_EXTRA_FORCE_REOPEN.
HA_EXTRA_PREPARE_FOR_RENAME:
Informs the handler we are about to attempt a rename of the table.
For handlers that have share open files (MyISAM key-file and
Archive writer) they must close the files before rename is possible
on Windows.
HA_EXTRA_READCHECK:
HA_EXTRA_NO_READCHECK:
Only one call to HA_EXTRA_NO_READCHECK from ha_open where it says that
this is not needed in SQL. The reason for this call is that MyISAM sets
the READ_CHECK_USED in the open call so the call is needed for MyISAM
to reset this feature.
The idea with this parameter was to inform of doing/not doing a read
check before applying an update. Since SQL always performs a read before
applying the update No Read Check is needed in MyISAM as well.
This is a cut from Docs/myisam.txt
Sometimes you might want to force an update without checking whether
another user has changed the record since you last read it. This is
somewhat dangerous, so it should ideally not be used. That can be
accomplished by wrapping the mi_update() call in two calls to mi_extra(),
using these functions:
HA_EXTRA_NO_READCHECK=5 No readcheck on update
HA_EXTRA_READCHECK=6 Use readcheck (def)
HA_EXTRA_REMEMBER_POS:
HA_EXTRA_RESTORE_POS:
System versioning needs this for MyISAM and Aria tables.
On DELETE using PRIMARY KEY: 1) handler::ha_index_read_map() saves rowid used for row delete/update 2) handler::ha_update_row() can rewrite saved rowid 3) handler::ha_delete_row()/handler::ha_update_row() expects saved but got
different rowid and operation fails
Using those flags prevents harmful side effect of 2)
4) Operations only used by temporary tables for query processing
----------------------------------------------------------------
HA_EXTRA_RESET_STATE:
Same as reset() except that buffers are not released. If there is
a READ CACHE it is reinit'ed. A cache is reinit'ed to restart reading
or to change type of cache between READ CACHE and WRITE CACHE.
This extra function is always called immediately before calling
delete_all_rows on the handler for temporary tables.
There are cases however when HA_EXTRA_RESET_STATE isn't called in
a similar case for a temporary table in sql_union.cc and in two other
cases HA_EXTRA_NO_CACHE is called before and HA_EXTRA_WRITE_CACHE
called afterwards.
The case with HA_EXTRA_NO_CACHE and HA_EXTRA_WRITE_CACHE means
disable caching, delete all rows and enable WRITE CACHE. This is
used for temporary tables containing distinct sums and a
functional group.
The only case that delete_all_rows is called on non-temporary tables
is in sql_delete.cc when DELETE FROM table; is called by a user.
In this case no special extra calls are performed before or after this
call.
The partition handler should not need to bother about this one. It
should never be called.
HA_EXTRA_NO_ROWS:
Don't insert rows indication to HEAP and MyISAM, only used by temporary
tables used in query processing.
Not handled by partition handler.
5) Operations only used by MyISAM internally
--------------------------------------------
HA_EXTRA_REINIT_CACHE:
This call reinitializes the READ CACHE described above if there is one
and otherwise the call is ignored.
We can thus safely call it on all underlying handlers if they are
MyISAM handlers. It is however never called so we don't handle it at all.
HA_EXTRA_FLUSH_CACHE:
Flush WRITE CACHE in MyISAM. It is only from one place in the code.
This is in sql_insert.cc where it is called if the table_flags doesn't
contain HA_DUPLICATE_POS. The only handler having the HA_DUPLICATE_POS
set is the MyISAM handler and so the only handler not receiving this
call is MyISAM.
Thus in effect this call is called but never used. Could be removed
from sql_insert.cc
HA_EXTRA_NO_USER_CHANGE:
Only used by MyISAM, never called.
Simulates lock_type as locked.
HA_EXTRA_WAIT_LOCK:
HA_EXTRA_WAIT_NOLOCK:
Only used by MyISAM, called from MyISAM handler but never from server
code on top of the handler.
Sets lock_wait on/off
HA_EXTRA_NO_KEYS:
Only used MyISAM, only used internally in MyISAM handler, never called
from server level.
HA_EXTRA_KEYREAD_CHANGE_POS:
HA_EXTRA_PRELOAD_BUFFER_SIZE:
HA_EXTRA_CHANGE_KEY_TO_DUP:
HA_EXTRA_CHANGE_KEY_TO_UNIQUE:
Only used by MyISAM, never called.
6) Operations not used at all
-----------------------------
HA_EXTRA_KEY_CACHE:
HA_EXTRA_NO_KEY_CACHE:
This parameters are no longer used and could be removed.
7) Operations only used by federated tables for query processing
----------------------------------------------------------------
HA_EXTRA_INSERT_WITH_UPDATE:
Inform handler that an "INSERT...ON DUPLICATE KEY UPDATE" will be
executed. This condition is unset by HA_EXTRA_NO_IGNORE_DUP_KEY.
8) Operations only used by NDB
------------------------------
HA_EXTRA_DELETE_CANNOT_BATCH:
HA_EXTRA_UPDATE_CANNOT_BATCH:
Inform handler that delete_row()/update_row() cannot batch deletes/updates
and should perform them immediately. This may be needed when table has
AFTER DELETE/UPDATE triggers which access to subject table.
These flags are reset by the handler::extra(HA_EXTRA_RESET) call.
9) Operations only used by MERGE
------------------------------
HA_EXTRA_ADD_CHILDREN_LIST:
HA_EXTRA_ATTACH_CHILDREN:
HA_EXTRA_IS_ATTACHED_CHILDREN:
HA_EXTRA_DETACH_CHILDREN:
Special actions for MERGE tables. Ignore.
*/
switch (operation) {
/* Category 1), used by most handlers */
case HA_EXTRA_NO_KEYREAD:
DBUG_RETURN(loop_read_partitions(end_keyread_cb, NULL));
case HA_EXTRA_KEYREAD:
DBUG_RETURN(loop_read_partitions(extra_cb, &operation));
case HA_EXTRA_FLUSH:
case HA_EXTRA_PREPARE_FOR_FORCED_CLOSE:
DBUG_RETURN(loop_partitions(extra_cb, &operation));
case HA_EXTRA_PREPARE_FOR_RENAME:
case HA_EXTRA_FORCE_REOPEN:
DBUG_RETURN(loop_extra_alter(operation));
break;
/* Category 2), used by non-MyISAM handlers */
case HA_EXTRA_IGNORE_DUP_KEY:
case HA_EXTRA_NO_IGNORE_DUP_KEY:
case HA_EXTRA_KEYREAD_PRESERVE_FIELDS:
{
if (!m_myisam)
DBUG_RETURN(loop_partitions(extra_cb, &operation));
}
break;
/* Category 3), used by MyISAM handlers */
case HA_EXTRA_PREPARE_FOR_UPDATE:
/*
Needs to be run on the first partition in the range now, and
later in late_extra_cache, when switching to a new partition to scan.
*/
m_extra_prepare_for_update= TRUE;
if (m_part_spec.start_part != NO_CURRENT_PART_ID)
{
if (!m_extra_cache)
m_extra_cache_part_id= m_part_spec.start_part;
DBUG_ASSERT(m_extra_cache_part_id == m_part_spec.start_part);
(void) m_file[m_part_spec.start_part]->extra(HA_EXTRA_PREPARE_FOR_UPDATE);
}
break;
case HA_EXTRA_NORMAL:
case HA_EXTRA_QUICK:
case HA_EXTRA_PREPARE_FOR_DROP:
case HA_EXTRA_FLUSH_CACHE:
case HA_EXTRA_PREPARE_FOR_ALTER_TABLE:
case HA_EXTRA_REMEMBER_POS:
case HA_EXTRA_RESTORE_POS:
{
DBUG_RETURN(loop_partitions(extra_cb, &operation));
}
case HA_EXTRA_NO_READCHECK:
{
/*
This is only done as a part of ha_open, which is also used in
ha_partition::open, so no need to do anything.
*/
break;
}
case HA_EXTRA_CACHE:
{
prepare_extra_cache(0);
break;
}
case HA_EXTRA_NO_CACHE:
{
int ret= 0;
if (m_extra_cache_part_id != NO_CURRENT_PART_ID)
ret= m_file[m_extra_cache_part_id]->extra(HA_EXTRA_NO_CACHE);
m_extra_cache= FALSE;
m_extra_cache_size= 0;
m_extra_prepare_for_update= FALSE;
m_extra_cache_part_id= NO_CURRENT_PART_ID;
DBUG_RETURN(ret);
}
case HA_EXTRA_WRITE_CACHE:
{
m_extra_cache= FALSE;
m_extra_cache_size= 0;
m_extra_prepare_for_update= FALSE;
m_extra_cache_part_id= NO_CURRENT_PART_ID;
DBUG_RETURN(loop_partitions(extra_cb, &operation));
}
case HA_EXTRA_IGNORE_NO_KEY:
case HA_EXTRA_NO_IGNORE_NO_KEY:
{
/*
Ignore as these are specific to NDB for handling
idempotency
*/
break;
}
case HA_EXTRA_WRITE_CAN_REPLACE:
case HA_EXTRA_WRITE_CANNOT_REPLACE:
{
/*
Informs handler that write_row() can replace rows which conflict
with row being inserted by PK/unique key without reporting error
to the SQL-layer.
At this time, this is safe by limitation of ha_partition
*/
DBUG_RETURN(loop_partitions(extra_cb, &operation));
}
/* Category 7), used by federated handlers */
case HA_EXTRA_INSERT_WITH_UPDATE:
DBUG_RETURN(loop_partitions(extra_cb, &operation));
/* Category 8) Operations only used by NDB */
case HA_EXTRA_DELETE_CANNOT_BATCH:
case HA_EXTRA_UPDATE_CANNOT_BATCH:
{
/* Currently only NDB use the *_CANNOT_BATCH */
break;
}
/* Category 9) Operations only used by MERGE */
case HA_EXTRA_ADD_CHILDREN_LIST:
if (!m_myisammrg)
DBUG_RETURN(0);
DBUG_RETURN(loop_partitions(extra_cb, &operation));
case HA_EXTRA_ATTACH_CHILDREN:
{
if (!m_myisammrg)
DBUG_RETURN(0);
int result;
uint num_locks;
handler **file;
if ((result= loop_partitions(extra_cb, &operation)))
DBUG_RETURN(result);
/* Recalculate lock count as each child may have different set of locks */
num_locks= 0;
file= m_file;
do
{
num_locks+= (*file)->lock_count();
} while (*(++file));
m_num_locks= num_locks;
break;
}
case HA_EXTRA_IS_ATTACHED_CHILDREN:
case HA_EXTRA_DETACH_CHILDREN:
if (!m_myisammrg)
DBUG_RETURN(0);
DBUG_RETURN(loop_partitions(extra_cb, &operation));
case HA_EXTRA_MARK_AS_LOG_TABLE:
/* http://dev.mysql.com/doc/refman/5.1/en/partitioning-limitations.html
says we no longer support logging to partitioned tables, so we fail
here.
*/
DBUG_RETURN(ER_UNSUPORTED_LOG_ENGINE); case HA_EXTRA_STARTING_ORDERED_INDEX_SCAN: case HA_EXTRA_BEGIN_COPY: case HA_EXTRA_END_COPY: case HA_EXTRA_ABORT_COPY: case HA_EXTRA_BEGIN_ALTER_IGNORE_COPY:
DBUG_RETURN(loop_partitions(extra_cb, &operation)); case HA_EXTRA_FULL_SCAN: break; default:
{ /* Temporary crash to discover what is wrong */
DBUG_ASSERT(0); break;
}
}
DBUG_RETURN(1);
}
int ha_partition::loop_partitions(handler_callback callback, void *param)
{ int result= loop_partitions_over_map(&m_part_info->lock_partitions,
callback, param); /* Add all used partitions to be called in reset(). */
bitmap_union(&m_partitions_to_reset, &m_part_info->lock_partitions); return result;
}
ha_rows rows_per_part= (rows + partitions - 1)/partitions; for (i= bitmap_get_first_set(&m_part_info->read_partitions);
i < m_tot_parts;
i= bitmap_get_next_set(&m_part_info->read_partitions, i))
{
IO_AND_CPU_COST cost= m_file[i]->keyread_time(inx, ranges, rows_per_part,
blocks);
read_time.io+= cost.io;
read_time.cpu+= cost.cpu;
} /* Add that we have to do a key lookup for all ranges in all partitions */
read_time.cpu= (partitions-1) * ranges * KEY_LOOKUP_COST;
DBUG_RETURN(read_time);
}
Notethatthisstatisticsmaynotalwaysbecorrect,sowemust continueevenifthecurrentpartitionhas0rows,sincewemighthave deletedrowsfromthecurrentpartition,orinsertedtothenext partition.
*/ if (estimated_rows && checked_rows &&
checked_rows >= min_rows_to_check)
{ /* We cannot use page ranges when there is more than one partion */
*pages= unused_page_range;
DBUG_PRINT("info",
("records_in_range(inx %u): %lu (%lu * %lu / %lu)",
inx,
(ulong) (estimated_rows * stats.records / checked_rows),
(ulong) estimated_rows,
(ulong) stats.records,
(ulong) checked_rows));
DBUG_RETURN(estimated_rows * stats.records / checked_rows);
}
}
DBUG_PRINT("info", ("records_in_range(inx %u): %lu",
inx,
(ulong) estimated_rows)); /* We cannot use page ranges when there is more than one partion */
*pages= unused_page_range;
DBUG_RETURN(estimated_rows);
}
/* Returns hash using the MYSQL51 algorithm. */ static uint64 mysql51_hash(Hasher *hasher, Field **field_array)
{ do
{
Field *field= *field_array;
switch (field->real_type()) { case MYSQL_TYPE_TINY: case MYSQL_TYPE_SHORT: case MYSQL_TYPE_LONG: case MYSQL_TYPE_FLOAT: case MYSQL_TYPE_DOUBLE: case MYSQL_TYPE_NEWDECIMAL: case MYSQL_TYPE_TIMESTAMP: case MYSQL_TYPE_LONGLONG: case MYSQL_TYPE_INT24: case MYSQL_TYPE_TIME: case MYSQL_TYPE_DATETIME: case MYSQL_TYPE_YEAR: case MYSQL_TYPE_NEWDATE:
{ if (field->is_null())
{
hasher->add_null();
continue;
} /* Force this to my_hash_sort_bin, which was used in 5.1! */
uint len= field->pack_length();
hasher->add(&my_charset_bin, field->ptr, len); /* Done with this field, continue with next one. */
continue;
} case MYSQL_TYPE_STRING: case MYSQL_TYPE_VARCHAR: case MYSQL_TYPE_BIT: /* Not affected, same in 5.1 and 5.5 */
break; /* ENUM/SETusesmy_hash_sort_simplein5.1(i.e.my_charset_latin1) andmy_hash_sort_binin5.5!
*/ case MYSQL_TYPE_ENUM: case MYSQL_TYPE_SET:
{ if (field->is_null())
{
hasher->add_null();
continue;
} /* Force this to my_hash_sort_bin, which was used in 5.1! */
uint len= field->pack_length();
hasher->add(&my_charset_latin1, field->ptr, len);
continue;
} /* New types in mysql-5.6. */ case MYSQL_TYPE_DATETIME2: case MYSQL_TYPE_TIME2: case MYSQL_TYPE_TIMESTAMP2: /* Not affected, 5.6+ only! */
break;
/* These types should not be allowed for partitioning! */ case MYSQL_TYPE_NULL: case MYSQL_TYPE_DECIMAL: case MYSQL_TYPE_DATE: case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_BLOB: case MYSQL_TYPE_VAR_STRING: case MYSQL_TYPE_GEOMETRY: /* fall through */
default:
DBUG_ASSERT(0); // New type? /* Fall through for default hashing (5.5). */
} /* fall through, use collation based hashing. */
field->hash(hasher);
} while (*(++field_array)); return hasher->finalize();
}
/* Should probably look for my own errors first */ if ((error == HA_ERR_NO_PARTITION_FOUND) &&
! (thd->lex->sql_command == SQLCOM_ALTER_TABLE &&
(thd->lex->alter_info.partition_flags & ALTER_PARTITION_TRUNCATE)))
{
m_part_info->print_no_partition_found(table, errflag);
DBUG_VOID_RETURN;
}
else if (error == HA_ERR_ROW_IN_WRONG_PARTITION)
{ /* Should only happen on DELETE, UPDATE or REBUILD PARTITION! */
DBUG_ASSERT(thd_sql_command(thd) == SQLCOM_DELETE ||
thd_sql_command(thd) == SQLCOM_DELETE_MULTI ||
thd_sql_command(thd) == SQLCOM_UPDATE ||
thd_sql_command(thd) == SQLCOM_UPDATE_MULTI ||
thd_sql_command(thd) == SQLCOM_ALTER_TABLE);
DBUG_ASSERT(m_err_rec); if (m_err_rec)
{
uint max_length;
char buf[MAX_KEY_LENGTH];
String str(buf,sizeof(buf),system_charset_info);
uint32 part_id;
str.length(0);
str.append('(');
str.append_ulonglong(m_last_part);
str.append(STRING_WITH_LEN(" != ")); if (get_part_for_buf(m_err_rec, table->record[0], m_part_info, &part_id))
str.append('?');
else
str.append_ulonglong(part_id);
str.append(')');
append_row_to_str(str);
/* Log this error, so the DBA can notice it and fix it! */
sql_print_error("Table '%-192s' corrupted: row in wrong partition: %s" "Please REPAIR the table!",
table->s->table_name.str,
str.c_ptr_safe());
max_length= (MYSQL_ERRMSG_SIZE -
(uint) strlen(ER_THD(thd, ER_ROW_IN_WRONG_PARTITION))); if (str.length() >= max_length)
{
str.length(max_length-4);
str.append(STRING_WITH_LEN("..."));
}
my_error(ER_ROW_IN_WRONG_PARTITION, MYF(0), str.c_ptr_safe());
m_err_rec= NULL;
DBUG_VOID_RETURN;
} /* fall through to generic error handling. */
}
/* Should probably look for my own errors first */
/* In case m_file has not been initialized, like in bug#42438 */ if (m_file)
DBUG_RETURN(m_file[m_last_part]->get_error_message(error, buf));
DBUG_RETURN(handler::get_error_message(error, buf));
part_inplace_ctx=
new (thd->mem_root) ha_partition_inplace_ctx(thd, m_tot_parts); if (!part_inplace_ctx)
DBUG_RETURN(HA_ALTER_ERROR);
part_inplace_ctx->handler_ctx_array=
thd->alloc<inplace_alter_handler_ctx *>(m_tot_parts + 1); if (!part_inplace_ctx->handler_ctx_array)
DBUG_RETURN(HA_ALTER_ERROR);
ha_table_option_struct *orig_opst= ha_alter_info->create_info->option_struct;
do {
result= HA_ALTER_INPLACE_NO_LOCK; /* Set all to NULL, including the terminating one. */
for (index= 0; index <= m_tot_parts; index++)
part_inplace_ctx->handler_ctx_array[index]= NULL;
if (index == 0)
first_is_set= (ha_alter_info->handler_ctx != NULL);
else if (first_is_set != (ha_alter_info->handler_ctx != NULL))
{ /* Either none or all partitions must set handler_ctx! */
DBUG_ASSERT(0);
DBUG_RETURN(HA_ALTER_ERROR);
} if (p_result < result)
result= p_result; if (result == HA_ALTER_ERROR)
break;
}
} while (orig_flags != ha_alter_info->handler_flags);
for (uint i= bitmap_get_first_set(&m_locked_partitions);
i < m_tot_parts;
i= bitmap_get_next_set(&m_locked_partitions, i))
{ if ((m_file[i])->need_info_for_auto_inc())
{
/* We have to get new auto_increment values from handler */
part_share->auto_inc_initialized= FALSE;
DBUG_RETURN(TRUE);
}
}
DBUG_RETURN(FALSE);
}
/**
Determine ifall partitions can use the current auto-increment value for
auto-increment initialization.
@return
TRUE All partitions can use the current auto-increment
value for auto-increment initialization
FALSE All partitions cannot use the current
auto-increment value for auto-increment
initialization
Notes
This function is only called for ::info(HA_STATUS_AUTO) and is
mainly used by the Spider engine, which returns false
except in the case of DROP TABLE or ALTER TABLE when it returns TRUE.
Other engines always returns TRUE for this call.
*/
do
{ if (!(*file)->can_use_for_auto_inc_init())
DBUG_RETURN(FALSE);
} while (*(++file));
DBUG_RETURN(TRUE);
}
int ha_partition::reset_auto_increment(ulonglong value)
{
handler **file= m_file;
int res;
DBUG_ENTER("ha_partition::reset_auto_increment");
lock_auto_increment();
part_share->auto_inc_initialized= false;
part_share->next_auto_inc_val= 0; do
{ if ((res= (*file)->ha_reset_auto_increment(value)) != 0)
break;
} while (*(++file));
unlock_auto_increment();
DBUG_RETURN(res);
}
/**
This method is called by update_auto_increment which in turn is called
by the individual handlers as part of write_row. We use the
part_share->next_auto_inc_val, or search all
partitions for the highest auto_increment_value if not initialized or if auto_increment field is a secondary part of a key, we must search
every partition when holding a mutex to be sure of correctness.
*/
void ha_partition::get_auto_increment(ulonglong offset, ulonglong increment,
ulonglong nb_desired_values,
ulonglong *first_value,
ulonglong *nb_reserved_values)
{
DBUG_ENTER("ha_partition::get_auto_increment");
DBUG_PRINT("enter", ("offset: %lu inc: %lu desired_values: %lu " "first_value: %lu", (ulong) offset, (ulong) increment,
(ulong) nb_desired_values, (ulong) *first_value));
DBUG_ASSERT(increment);
DBUG_ASSERT(nb_desired_values);
*first_value= 0; if (table->s->next_number_keypart)
{
/*
next_number_keypart is != 0if the auto_increment column is a secondary
column in the index (it is allowed in MyISAM)
*/
DBUG_PRINT("info", ("next_number_keypart != 0"));
ulonglong nb_reserved_values_part;
ulonglong first_value_part, max_first_value;
handler **file= m_file;
first_value_part= max_first_value= *first_value;
/* Must find highest value among all partitions. */ do
{
/* Only nb_desired_values = 1 makes sense */
(*file)->get_auto_increment(offset, increment, 1,
&first_value_part, &nb_reserved_values_part); if (unlikely(first_value_part == ULONGLONG_MAX)) // error in one partition
{
*first_value= first_value_part;
/* log that the error was between table/partition handler */
sql_print_error("Partition failed to reserve auto_increment value");
DBUG_VOID_RETURN;
}
DBUG_PRINT("info", ("first_value_part: %lu", (ulong) first_value_part));
set_if_bigger(max_first_value, first_value_part);
} while (*(++file));
*first_value= max_first_value;
*nb_reserved_values= 1;
} else
{
THD *thd= ha_thd();
update_next_auto_inc_val();
/*
Get a lock for handling the auto_increment in part_share for avoiding two concurrent statements getting the same number.
*/
lock_auto_increment();
/*
In a multi-row insert statement like INSERT SELECT and LOAD DATA
where the number of candidate rows to insert is not known in advance
we must hold a lock/mutex for the whole statement if we have statement
based replication. Because the statement-based binary log contains
only the first generated value used by the statement, and slaves assumes all other generated values used by this statement were consecutive to
this first one, we must exclusively lock the generator until the
statement is done.
*/ if (!auto_increment_safe_stmt_log_lock &&
thd->lex->sql_command != SQLCOM_INSERT &&
mysql_bin_log.is_open() &&
!thd->is_current_stmt_binlog_format_row() &&
(thd->variables.option_bits & OPTION_BIN_LOG))
{
DBUG_PRINT("info", ("locking auto_increment_safe_stmt_log_lock"));
auto_increment_safe_stmt_log_lock= TRUE;
}
/* this gets corrected (for offset/increment) in update_auto_increment */
*first_value= part_share->next_auto_inc_val;
part_share->next_auto_inc_val+= nb_desired_values * increment;
if (table->s->next_number_keypart)
{
uint i; for (i= bitmap_get_first_set(&m_part_info->lock_partitions);
i < m_tot_parts;
i= bitmap_get_next_set(&m_part_info->lock_partitions, i))
{
m_file[i]->ha_release_auto_increment();
}
} else
{
lock_auto_increment(); if (next_insert_id)
{
ulonglong next_auto_inc_val= part_share->next_auto_inc_val;
/* If the current auto_increment values is lower than the reserved
value, and the reserved value was reserved by this thread,
we can lower the reserved value.
*/ if (next_insert_id < next_auto_inc_val &&
auto_inc_interval_for_cur_row.maximum() >= next_auto_inc_val)
{
THD *thd= ha_thd();
/*
Check that we do not lower the value because of a failed insert
with SET INSERT_ID, i.e. forced/non generated values.
*/ if (thd->auto_inc_intervals_forced.maximum() < next_insert_id)
part_share->next_auto_inc_val= next_insert_id;
}
DBUG_PRINT("info", ("part_share->next_auto_inc_val: %lu",
(ulong) part_share->next_auto_inc_val));
}
/*
Unlock the multi-row statement lock taken in get_auto_increment.
These actions must be performed even if the next_insert_id field
contains zero, otherwise if the update_auto_increment fails then
an unnecessary lock will remain:
*/ if (auto_increment_safe_stmt_log_lock)
{
auto_increment_safe_stmt_log_lock= FALSE;
DBUG_PRINT("info", ("unlocking auto_increment_safe_stmt_log_lock"));
}
unlock_auto_increment();
}
DBUG_VOID_RETURN;
}
/****************************************************************************
MODULE initialize handler for HANDLER call
****************************************************************************/
int ha_partition::check_misplaced_rows(uint read_part_id, bool do_repair)
{
int result= 0;
uint32 correct_part_id;
longlong func_value;
longlong num_misplaced_rows= 0;
DBUG_ENTER("ha_partition::check_misplaced_rows");
DBUG_ASSERT(m_file);
if (m_part_info->vers_info &&
read_part_id != m_part_info->vers_info->now_part->id &&
!m_part_info->vers_info->interval.is_set())
{
/* Skip this check as it is not supported for non-INTERVAL history partitions. */
DBUG_RETURNHA_ADMIN_OK);
}
if (
{ {
java.lang.StringIndexOutOfBoundsException: Index 3 out of bounds for length 3
bitmap_set_all(table->read_set);
bitmap_set_all(table->write_set);
java.lang.StringIndexOutOfBoundsException: Index 3 out of bounds for length 3 else
{
/* Only need to read the partitioning fields. */
bitmap_union(table->read_set, &m_part_info->full_part_field_set);
}
if ((result= m_file[read_part_id]->ha_rnd_init(1)))
DBUG_RETURN(result);
while (true)
{ if ((result= m_file[read_part_id]->ha_rnd_next(table->record[0])))
{ if (result != HA_ERR_END_OF_FILE)
break;
if (num_misplaced_rows > 0)
{
print_admin_msg(ha_thd(), MYSQL_ERRMSG_SIZE, false,
table_share->db.str, table->alias,
&opt_op_name[REPAIR_PARTS], "Moved %lld misplaced rows",
num_misplaced_rows);
}
/* End-of-file reached, all rows are now OK, reset result and break. */
result= 0;
break;
}
result= m_part_info->get_partition_id(m_part_info, &correct_part_id,
&func_value); if (result)
break;
if (correct_part_id != read_part_id)
{
num_misplaced_rows++; if (!do_repair)
{
/* Check. */
print_admin_msg(ha_thd(), MYSQL_ERRMSG_SIZE, true,
table_share->db.str, table->alias,
&opt_op_name[CHECK_PARTS], "Found a misplaced row");
/* Break on first misplaced row! */
result= HA_ADMIN_NEEDS_UPGRADE;
break;
} else
{
DBUG_PRINT("info", ("Moving row from partition %u to %u",
(uint) read_part_id, (uint) correct_part_id));
/*
Insert row into correct partition. Notice that there are no commit for every N row, so the repair will be one large transaction!
*/ if ((result= m_file[correct_part_id]->ha_write_row(table->record[0])))
{
/*
We have failed to insert a row, it might have been a duplicate!
*/
char buf[MAX_KEY_LENGTH];
String str(buf,sizeof(buf),system_charset_info);
str.length(0); if (result == HA_ERR_FOUND_DUPP_KEY)
{
str.append(STRING_WITH_LEN("Duplicate key found, " "please update or delete the " "record:\n"));
result= HA_ADMIN_CORRUPT;
}
m_err_rec= NULL;
append_row_to_str(str);
/* If the engine supports transactions, the failure will be
rolled back
*/ if (!m_file[correct_part_id]->has_transactions_and_rollback())
{
/* Log this error, so the DBA can notice it and fix it! */
sql_print_error("Table '%-192s' failed to move/insert a row" " from part %u into part %u:\n%s",
table->s->table_name.str,
(uint) read_part_id,
(uint) correct_part_id,
str.c_ptr_safe());
}
print_admin_msg(ha_thd(), MYSQL_ERRMSG_SIZE, true,
table_share->db.str, table->alias,
&opt_op_name[REPAIR_PARTS], "Failed to move/insert a row" " from part %u into part %u:\n%s",
(uint) read_part_id,
(uint) correct_part_id,
str.c_ptr_safe());
break;
}
/* Delete row from wrong partition. */ if ((result= m_file[read_part_id]->ha_delete_row(table->record[0])))
{ if (m_file[correct_part_id]->has_transactions_and_rollback())
break;
/*
We have introduced a duplicate, since we failed to remove it
from the wrong partition.
*/
char buf[MAX_KEY_LENGTH];
String str(buf,sizeof(buf),system_charset_info);
str.length(0);
m_err_rec= NULL;
append_row_to_str(str);
/* Log this error, so the DBA can notice it and fix it! */
sql_print_error("Table '%-192s': Delete from part %u failed with" " error %d. But it was already inserted into" " part %u, when moving the misplaced row!" "\nPlease manually fix the duplicate row:\n%s",
table->s->table_name.str,
(uint) read_part_id,
result,
(uint) correct_part_id,
str.c_ptr_safe());
break;
}
}
}
}
int tmp_result= m_file[read_part_id]->ha_rnd_end();
DBUG_RETURN(result ? result : tmp_result);
}
int ha_partition::check_for_upgrade(HA_CHECK_OPT *check_opt)
{
int error= HA_ADMIN_NEEDS_CHECK;
DBUG_ENTER("ha_partition::check_for_upgrade");
/*
This is called even without FOR UPGRADE, if the .frm version is lower than the current version.
In that case return that it needs checking!
*/ if (!(check_opt->sql_flags & TT_FOR_UPGRADE))
DBUG_RETURN(error);
/*
Partitions will be checked for during their ha_check!
Check if KEY (sub)partitioning was used and any field's hash calculation
differs from 5.1, see bug#14521864.
*/ if (table->s->mysql_version < 50503 && // 5.1 table (<5.5.3)
((m_part_info->part_type == HASH_PARTITION && // KEY partitioned
m_part_info->list_of_part_fields) ||
(m_is_sub_partitioned && // KEY subpartitioned
m_part_info->list_of_subpart_fields)))
{
Field **field; if (m_is_sub_partitioned)
{
field= m_part_info->subpart_field_array;
} else
{
field= m_part_info->part_field_array;
} for (; *field; field++)
{
switch ((*field)->real_type()) {
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_FLOAT:
case MYSQL_TYPE_DOUBLE:
case MYSQL_TYPE_NEWDECIMAL:
case MYSQL_TYPE_TIMESTAMP:
case MYSQL_TYPE_LONGLONG:
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_TIME:
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_YEAR:
case MYSQL_TYPE_NEWDATE:
case MYSQL_TYPE_ENUM:
case MYSQL_TYPE_SET:
{
THD *thd= ha_thd();
char *part_buf;
String db_name, table_name;
uint part_buf_len;
bool skip_generation= false;
partition_info::enum_key_algorithm old_algorithm;
old_algorithm= m_part_info->key_algorithm;
error= HA_ADMIN_FAILED;
append_identifier(ha_thd(), &db_name, &table_share->db);
append_identifier(ha_thd(), &table_name, &table_share->table_name); if (m_part_info->key_algorithm != partition_info::KEY_ALGORITHM_NONE)
{
/*
Only possible when someone tampered with .frm files,
like during tests :)
*/
skip_generation= true;
}
m_part_info->key_algorithm= partition_info::KEY_ALGORITHM_51; if (skip_generation ||
!(part_buf= generate_partition_syntax_for_frm(thd, m_part_info,
&part_buf_len,
NULL, NULL)) ||
print_admin_msg(thd, SQL_ADMIN_MSG_TEXT_SIZE + 1, true,
table_share->db.str,
table->alias,
&opt_op_name[CHECK_PARTS],
KEY_PARTITIONING_CHANGED_STR,
db_name.c_ptr_safe(),
table_name.c_ptr_safe(),
part_buf))
{
/* Error creating admin message (too long string?). */
print_admin_msg(thd, MYSQL_ERRMSG_SIZE, true,
table_share->db.str, table->alias,
&opt_op_name[CHECK_PARTS],
KEY_PARTITIONING_CHANGED_STR,
db_name.c_ptr_safe(), table_name.c_ptr_safe(), "<old partition clause>, but add ALGORITHM = 1" " between 'KEY' and '(' to change the metadata" " without the need of a full table rebuild.");
}
m_part_info->key_algorithm= old_algorithm;
DBUG_RETURN(error);
}
default:
/* Not affected! */
;
}
}
}
/**
Push an engine condition to the condition stack of the storage engine for each partition.
@param cond Pointer to the engine condition to be pushed.
@return NULL Underlying engine will not return rows that do not match the passed condition.
<> NULL 'Remainder' condition that the caller must use
to filter out records.
*/
if (bitmap_is_overlapping(&m_part_info->full_part_field_set,
table->write_set))
DBUG_RETURN(TRUE);
do
{
bzero(&(*file)->copy_info, sizeof((*file)->copy_info)); if ((*file)->start_bulk_update())
DBUG_RETURN(TRUE);
} while (*(++file));
DBUG_RETURN(FALSE);
}
int ha_partition::direct_update_rows_init(List<Item> *update_fields)
{
int error;
uint i, found;
handler *file;
DBUG_ENTER("ha_partition::direct_update_rows_init");
if (bitmap_is_overlapping(&m_part_info->full_part_field_set,
table->write_set))
{
DBUG_PRINT("info", ("partition FALSE by updating part_key"));
DBUG_RETURN(HA_ERR_WRONG_COMMAND);
}
for (uint i= bitmap_get_first_set(&m_part_info->read_partitions);
i < m_tot_parts;
i= bitmap_get_next_set(&m_part_info->read_partitions, i))
{
Item* res= m_file[i]->idx_cond_push(keyno, idx_cond); if (!res) // Returning nullptr indicates success.
continue;
// One of the partitions couldn't accept the pushed condition, or
// one of the partitions returned a partial pushed condition that
// indicates that it could handle some portion of the pushed index
// condition. At this point, we require all partitions to handle
// the pushed condition in the same way; consequently we need to
// cancel the pushed condition for the partitions that succeeded
// up to this point.
DBUG_ASSERT(i == bitmap_get_first_set(&m_part_info->read_partitions));
DBUG_ASSERT(res == idx_cond); if (res != idx_cond)
m_file[i]->cancel_pushed_idx_cond();
cancel_pushed_idx_cond_impl(m_file, &m_part_info->read_partitions, i); return idx_cond;
}
pushed_idx_cond= idx_cond;
pushed_idx_cond_keyno= keyno;
in_range_check_pushed_down = TRUE; return NULL;
}
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.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.