/* Copyright (c) 2000, 2015, Oracle and/or its affiliates.
Copyright ( c ) 2009 , 2020 , MariaDB
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; version 2 of the License .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
/**
@ file
@ brief
Sorts a database
*/
#include "mariadb.h"
#include "sql_priv.h"
#include "filesort.h"
#include <m_ctype.h>
#include "sql_sort.h"
#include "probes_mysql.h"
#include "sql_base.h"
#include "sql_test.h" // TEST_filesort
#include "opt_range.h" // SQL_SELECT
#include "filesort_utils.h"
#include "sql_select.h"
#include "debug_sync.h"
#include "sql_queue.h"
static uint make_sortkey(Sort_param *, uchar *, uchar *, bool );
class Bounded_queue
{
public :
int init(ha_rows max_elements, size_t cmplen, Sort_param *sort_param,
uchar **sort_keys);
void push(uchar *element);
size_t num_elements() const { return m_queue.elements(); }
bool is_initialized() const { return m_queue.is_inited(); }
private :
uchar **m_sort_keys;
size_t m_compare_length;
Sort_param *m_sort_param;
Queue<uchar*, size_t> m_queue;
};
int Bounded_queue::init(ha_rows max_elements, size_t cmplen,
Sort_param *sort_param, uchar **sort_keys)
{
DBUG_ASSERT(sort_keys != NULL);
m_sort_keys= sort_keys;
m_compare_length= cmplen;
m_sort_param= sort_param;
// init_queue() takes an uint, and also does (max_elements + 1)
if (max_elements >= UINT_MAX - 1 )
return 1 ;
// We allocate space for one extra element, for replace when queue is full.
return m_queue.init((uint)max_elements + 1 , true ,
get_ptr_compare(cmplen), &m_compare_length);
}
void Bounded_queue::push(uchar *element)
{
DBUG_ASSERT(is_initialized());
if (m_queue.is_full())
{
// Replace top element with new key, and re-order the queue.
uchar **pq_top= m_queue.top();
make_sortkey(m_sort_param, *pq_top, element, 0 );
m_queue.propagate_top();
} else {
// Insert new key into the queue.
make_sortkey(m_sort_param, m_sort_keys[m_queue.elements()], element, 0 );
m_queue.push(&m_sort_keys[m_queue.elements()]);
}
}
static uchar *read_buffpek_from_file(IO_CACHE *, uint, uchar *);
static ha_rows find_all_keys(THD *, Sort_param *, SQL_SELECT *, SORT_INFO *,
IO_CACHE *, IO_CACHE *, Bounded_queue *,
ha_rows *);
static bool write_keys(Sort_param *, SORT_INFO *, uint, IO_CACHE *, IO_CACHE *);
static uint make_sortkey(Sort_param *param, uchar *to);
static uint make_packed_sortkey(Sort_param *param, uchar *to);
static void register_used_fields(Sort_param *param);
static bool save_index(Sort_param *, uint, SORT_INFO *);
static uint suffix_length(ulong string_length);
static uint sortlength(THD *, Sort_keys *, bool *);
static Addon_fields *get_addon_fields(TABLE *, uint, uint *, uint *);
static void store_key_part_length(uint32 num, uchar *to, uint bytes)
{
switch (bytes) {
case 1 : *to= (uchar)num; break ;
case 2 : int2store(to, num); break ;
case 3 : int3store(to, num); break ;
case 4 : int4store(to, num); break ;
default : DBUG_ASSERT(0 );
}
}
static uint32 read_keypart_length(const uchar *from, uint bytes)
{
switch (bytes) {
case 1 : return from[0 ];
case 2 : return uint2korr(from);
case 3 : return uint3korr(from);
case 4 : return uint4korr(from);
default : DBUG_ASSERT(0 ); return 0 ;
}
}
/*
Initialize Sort_param for doing a filesort
@ param table Table to sort
@ param Filesort Filesort parameter to filesort ( )
@ param sortlen [ Maximum ] length of the sort key
@ param limit_rows Number of rows to return ( may be less than rows to sort )
*/
void Sort_param::init_for_filesort(TABLE *table, Filesort *filesort,
uint sortlen, ha_rows limit_rows_arg)
{
DBUG_ASSERT(addon_fields == NULL);
if (!(table->file->ha_table_flags() & HA_FAST_KEY_READ) &&
!table->fulltext_searched && !filesort->sort_positions)
{
/*
Get the descriptors of all fields whose values are appended
to sorted fields and get its total length in addon_buf . length
*/
addon_fields= get_addon_fields(table, sortlen, &addon_length,
&m_packable_length);
}
DBUG_ASSERT((using_addon_fields() == 0 || addon_length != 0 ));
setup_lengths_and_limit(table, sortlen, addon_length, limit_rows_arg);
accepted_rows= filesort->accepted_rows;
}
void Sort_param::setup_lengths_and_limit(TABLE *table,
uint sort_len_arg,
uint addon_length_arg,
ha_rows limit_rows_arg)
{
sort_form= table;
sort_length= sort_len_arg;
limit_rows= limit_rows_arg;
ref_length= table->file->ref_length;
if (addon_length_arg)
{
DBUG_ASSERT(addon_length_arg < UINT_MAX32);
res_length= addon_length_arg;
}
else
{
res_length= ref_length;
/*
The reference ( rowid ) to the record is considered as an additional
sorted field as we want to access rows in rowid order if possible .
*/
sort_length+= ref_length;
}
rec_length= sort_length + addon_length_arg;
}
void Sort_param::try_to_pack_addons(ulong max_length_for_sort_data)
{
if (!using_addon_fields() || // no addons, or
using_packed_addons()) // already packed
return ;
if (!Addon_fields::can_pack_addon_fields(res_length))
return ;
const uint sz= Addon_fields::size_of_length_field;
// Heuristic: skip packing if potential savings are less than 10 bytes.
if (m_packable_length < (10 + sz))
return ;
SORT_ADDON_FIELD *addonf= addon_fields->begin();
for (;addonf != addon_fields->end(); ++addonf)
{
addonf->offset+= sz;
addonf->null_offset+= sz;
}
addon_fields->set_using_packed_addons(true );
m_using_packed_addons= true ;
m_packed_format= true ;
addon_length+= sz;
res_length+= sz;
rec_length+= sz;
}
/**
Sort a table .
Creates a set of pointers that can be used to read the rows
in sorted order . This should be done with the functions
in records . cc .
Before calling filesort , one must have done
table - > file - > info ( HA_STATUS_VARIABLE )
The result set is stored in
filesort_info - > io_cache or
filesort_info - > record_pointers .
@ param thd Current thread
@ param table Table to sort
@ param filesort How to sort the table
@ param [ out ] found_rows Store the number of found rows here .
This is the number of found rows after
applying WHERE condition .
@ note
If we sort by position ( like if filesort - > sort_positions = = true )
filesort ( ) will call table - > prepare_for_position ( ) .
@ retval
0 Error
# SORT_INFO
*/
SORT_INFO *filesort(THD *thd, TABLE *table, Filesort *filesort,
Filesort_tracker* tracker, JOIN *join,
table_map first_table_bit)
{
int error;
DBUG_ASSERT(thd->variables.sortbuff_size <= SIZE_T_MAX);
size_t memory_available= (size_t)thd->variables.sortbuff_size;
uint maxbuffer;
Merge_chunk *buffpek;
ha_rows num_rows= HA_POS_ERROR, not_used=0 ;
IO_CACHE tempfile, buffpek_pointers, *outfile;
Sort_param param;
bool allow_packing_for_sortkeys;
Bounded_queue pq;
SQL_SELECT *const select= filesort->select;
Sort_costs costs;
ha_rows limit_rows= filesort->limit;
uint s_length= 0 , sort_len;
Sort_keys *sort_keys;
DBUG_ENTER("filesort" );
if (!(sort_keys= filesort->make_sortorder(thd, join, first_table_bit)))
DBUG_RETURN(NULL); /* purecov: inspected */
s_length= static_cast <uint>(sort_keys->size());
DBUG_EXECUTE("info" ,TEST_filesort(filesort->sortorder, s_length););
#ifdef SKIP_DBUG_IN_FILESORT
DBUG_PUSH_EMPTY; /* No DBUG here */
#endif
SORT_INFO *sort;
TABLE_LIST *tab= table->pos_in_table_list;
Item_subselect *subselect= tab ? tab->containing_subselect() : 0 ;
MYSQL_FILESORT_START(table->s->db.str, table->s->table_name.str);
DEBUG_SYNC(thd, "filesort_start" );
if (!(sort= new SORT_INFO)) // Note that this is not automatically freed!
return 0 ;
if (subselect && subselect->filesort_buffer.is_allocated())
{
// Reuse cache from last call
sort->filesort_buffer= subselect->filesort_buffer;
sort->buffpek= subselect->sortbuffer;
subselect->filesort_buffer.reset();
subselect->sortbuffer.str=0 ;
}
DBUG_ASSERT(sort->sorted_result_in_fsbuf == FALSE ||
sort->record_pointers == NULL);
outfile= &sort->io_cache;
my_b_clear(&tempfile);
my_b_clear(&buffpek_pointers);
buffpek=0 ;
error= 1 ;
sort->found_rows= HA_POS_ERROR;
param.sort_keys= sort_keys;
sort_len= sortlength(thd, sort_keys, &allow_packing_for_sortkeys);
param.init_for_filesort(table, filesort, sort_len, limit_rows);
if (!param.accepted_rows)
param.accepted_rows= ¬_used;
param.set_all_read_bits= filesort->set_all_read_bits;
param.unpack= filesort->unpack;
sort->addon_fields= param.addon_fields;
sort->sort_keys= param.sort_keys;
if (select && select->quick)
thd->inc_status_sort_range();
else
thd->inc_status_sort_scan();
thd->query_plan_flags|= QPLAN_FILESORT;
tracker->report_use(thd, limit_rows);
// If number of rows is not known, use as much of sort buffer as possible.
num_rows= table->file->estimate_rows_upper_bound();
costs.compute_sort_costs(¶m, num_rows, memory_available,
param.using_addon_fields());
if (costs.fastest_sort == NO_SORT_POSSIBLE_OUT_OF_MEM)
{
my_error(ER_OUT_OF_SORTMEMORY,MYF(ME_ERROR_LOG + ME_FATAL));
goto err;
}
if (costs.fastest_sort == PQ_SORT_ALL_FIELDS ||
costs.fastest_sort == PQ_SORT_ORDER_BY_FIELDS)
{
/* We are going to use priority queue */
thd->query_plan_flags|= QPLAN_FILESORT_PRIORITY_QUEUE;
status_var_increment(thd->status_var.filesort_pq_sorts_);
tracker->incr_pq_used();
param.using_pq= true ;
const size_t compare_length= param.sort_length;
DBUG_ASSERT(param.using_packed_sortkeys() == false );
if (costs.fastest_sort == PQ_SORT_ORDER_BY_FIELDS && sort->addon_fields)
{
/*
Upper code have addon_fields enabled , which we have decided to
not use . Let ' s delete them .
*/
my_free(sort->addon_fields);
sort->addon_fields= NULL;
param.addon_fields= NULL;
param.res_length= param.ref_length;
/*
Add the ref ( rowid which is stored last in the sort key ) to the sort ,
as we want to retrieve rows in id order , if possible .
*/
param.sort_length+= param.ref_length;
param.rec_length= param.sort_length;
}
/* Priority queues needs one extra element for doing INSERT */
param.max_keys_per_buffer= (uint) param.limit_rows + 1 ;
if (!sort->alloc_sort_buffer(param.max_keys_per_buffer, param.rec_length))
goto err;
/*
For PQ queries ( with limit ) we know exactly how many pointers / records
we have in the buffer , so to simplify things , we initialize
all pointers here . ( We cannot pack fields anyways , so there is no
point in doing lazy initialization ) .
*/
sort->init_record_pointers();
if (pq.init(param.limit_rows, compare_length, ¶m, sort->get_sort_keys()))
{
/*
If we fail to init pq , we have to give up :
out of memory means my_malloc ( ) will call my_error ( ) .
*/
DBUG_PRINT("info" , ("failed to allocate PQ" ));
DBUG_ASSERT(thd->is_error());
goto err;
}
}
else
{
DBUG_PRINT("info" , ("filesort PQ is not applicable" ));
if (allow_packing_for_sortkeys)
param.try_to_pack_sortkeys();
param.try_to_pack_addons(thd->variables.max_length_for_sort_data);
tracker->report_sort_keys_format(param.using_packed_sortkeys());
param.using_pq= false ;
/* Allocate sort buffer. Use as much memory as possible. */
size_t min_sort_memory= MY_MAX(MIN_SORT_MEMORY,
param.sort_length*MERGEBUFF2);
set_if_bigger(min_sort_memory, sizeof (Merge_chunk*) * MERGEBUFF2);
while (memory_available >= min_sort_memory)
{
ulonglong keys= memory_available / (param.rec_length + sizeof (char *));
param.max_keys_per_buffer= (uint) MY_MAX(MERGEBUFF2,
MY_MIN(num_rows, keys));
sort->alloc_sort_buffer(param.max_keys_per_buffer, param.rec_length);
if (sort->sort_buffer_size() > 0 )
break ;
size_t old_memory_available= memory_available;
memory_available= memory_available/4 *3 ;
if (memory_available < min_sort_memory &&
old_memory_available > min_sort_memory)
memory_available= min_sort_memory;
}
if (memory_available < min_sort_memory)
{
my_error(ER_OUT_OF_SORTMEMORY,MYF(ME_ERROR_LOG + ME_FATAL));
goto err;
}
tracker->report_sort_buffer_size(sort->sort_buffer_size());
}
if (param.using_addon_fields())
{
// report information whether addon fields are packed or not
tracker->report_addon_fields_format(param.using_packed_addons());
}
if (param.tmp_buffer.alloc(param.sort_length))
goto err;
if (open_cached_file(&buffpek_pointers, mysql_tmpdir, TEMP_PREFIX,
DISK_CHUNK_SIZE,
MYF(MY_WME | MY_TRACK_WITH_LIMIT)))
goto err;
param.local_sortorder=
Bounds_checked_array<SORT_FIELD>(filesort->sortorder, s_length);
num_rows= find_all_keys(thd, ¶m, select,
sort,
&buffpek_pointers,
&tempfile,
pq.is_initialized() ? &pq : NULL,
&sort->found_rows);
if (num_rows == HA_POS_ERROR)
goto err;
maxbuffer= (uint) (my_b_tell(&buffpek_pointers)/sizeof (*buffpek));
tracker->report_merge_passes_at_start(thd->query_plan_fsort_passes);
tracker->report_row_numbers(param.examined_rows, sort->found_rows, num_rows);
if (maxbuffer == 0 ) // The whole set is in memory
{
if (save_index(¶m, (uint) num_rows, sort))
goto err;
}
else
{
/* filesort cannot handle zero-length records during merge. */
thd->query_plan_flags|= QPLAN_FILESORT_DISK;
DBUG_ASSERT(param.sort_length != 0 );
if (sort->buffpek.str && sort->buffpek.length < maxbuffer)
{
my_free(sort->buffpek.str);
sort->buffpek.str= 0 ;
}
if (param.using_addon_fields())
{
DBUG_ASSERT(sort->addon_fields);
if (!sort->addon_fields->allocate_addon_buf(param.addon_length))
goto err;
}
if (!(sort->buffpek.str=
(char *) read_buffpek_from_file(&buffpek_pointers, maxbuffer,
(uchar*) sort->buffpek.str)))
goto err;
sort->buffpek.length= maxbuffer;
buffpek= (Merge_chunk *) sort->buffpek.str;
close_cached_file(&buffpek_pointers);
/* Open cached file if it isn't open */
if (!my_b_inited(outfile) &&
open_cached_file(outfile, mysql_tmpdir, TEMP_PREFIX, DISK_CHUNK_SIZE,
MYF(MY_WME | MY_TRACK_WITH_LIMIT)))
goto err;
if (reinit_io_cache(outfile,WRITE_CACHE,0 L,0 ,0 ))
goto err;
/*
Use also the space previously used by string pointers in sort_buffer
for temporary key storage .
*/
param.max_keys_per_buffer= static_cast <uint>(sort->sort_buffer_size()) /
param.rec_length;
set_if_bigger(param.max_keys_per_buffer, 1 );
maxbuffer--; // Offset from 0
if (merge_many_buff(¶m, sort->get_raw_buf(),
buffpek, &maxbuffer,
&tempfile))
goto err;
if (flush_io_cache(&tempfile) ||
reinit_io_cache(&tempfile, READ_CACHE, 0 L,0 ,0 ))
goto err;
if (merge_index(¶m,
sort->get_raw_buf(),
buffpek,
maxbuffer,
&tempfile,
outfile))
goto err;
}
if (num_rows > param.limit_rows)
{
// If find_all_keys() produced more results than the query LIMIT.
num_rows= param.limit_rows;
}
error= 0 ;
err:
param.tmp_buffer.free();
if (!subselect || !subselect->is_uncacheable())
{
if (!param.using_addon_fields())
sort->free_sort_buffer();
my_free(sort->buffpek.str);
}
else
{
/* Remember sort buffers for next subquery call */
subselect->filesort_buffer= sort->filesort_buffer;
subselect->sortbuffer= sort->buffpek;
sort->filesort_buffer.reset(); // Don't free this*/
}
sort->buffpek.str= 0 ;
close_cached_file(&tempfile);
close_cached_file(&buffpek_pointers);
if (my_b_inited(outfile))
{
if (flush_io_cache(outfile))
error=1 ;
{
my_off_t save_pos=outfile->pos_in_file;
/* For following reads */
if (reinit_io_cache(outfile,READ_CACHE,0 L,0 ,0 ))
error=1 ;
outfile->end_of_file=save_pos;
}
}
tracker->report_merge_passes_at_end(thd, thd->query_plan_fsort_passes);
if (unlikely(error))
{
int kill_errno= thd->killed_errno();
DBUG_ASSERT(thd->is_error() || kill_errno || thd->killed == ABORT_QUERY);
my_printf_error(ER_FILSORT_ABORT,
"%s: %s" ,
MYF(0 ),
ER_THD(thd, ER_FILSORT_ABORT),
kill_errno ? ER_THD(thd, kill_errno) :
thd->killed == ABORT_QUERY ? "LIMIT ROWS EXAMINED" :
thd->get_stmt_da()->message());
if ((thd->killed == ABORT_QUERY || kill_errno) &&
global_system_variables.log_warnings > 1 )
{
sql_print_warning("%s, host: %s, user: %s, thread: %lu, query: %-.4096s" ,
ER_THD(thd, ER_FILSORT_ABORT),
thd->security_ctx->host_or_ip,
&thd->security_ctx->priv_user[0 ],
(ulong) thd->thread_id,
thd->query());
}
}
else
thd->inc_status_sort_rows(num_rows);
sort->m_examined_rows= param.examined_rows;
sort->return_rows= num_rows;
#ifdef SKIP_DBUG_IN_FILESORT
DBUG_POP_EMPTY; /* Ok to DBUG */
#endif
DBUG_PRINT("exit" ,
("num_rows: %lld examined_rows: %lld found_rows: %lld" ,
(longlong) sort->return_rows, (longlong) sort->m_examined_rows,
(longlong) sort->found_rows));
MYSQL_FILESORT_DONE(error, num_rows);
if (unlikely(error))
{
delete sort;
sort= 0 ;
}
DBUG_RETURN(sort);
} /* filesort */
void Filesort::cleanup()
{
if (select && own_select)
{
select->cleanup();
select= NULL;
}
}
/*
Create the Sort_keys array and fill the sort_keys [ i ] - > { item | field } .
This indicates which field / item values will be used as sort keys .
Attributes like lengths are not filled yet .
*/
Sort_keys*
Filesort::make_sortorder(THD *thd, JOIN *join, table_map first_table_bit)
{
uint count;
SORT_FIELD *sort,*pos;
ORDER *ord;
DBUG_ENTER("make_sortorder" );
count=0 ;
for (ord = order; ord; ord= ord->next)
count++;
if (sortorder)
DBUG_RETURN(sort_keys);
DBUG_ASSERT(sort_keys == NULL);
sortorder= thd->alloc<SORT_FIELD>(count);
pos= sort= sortorder;
if (!pos)
DBUG_RETURN(0 );
sort_keys= new Sort_keys(sortorder, count);
if (!sort_keys)
DBUG_RETURN(0 );
pos= sort_keys->begin();
for (ord= order; ord; ord= ord->next, pos++)
{
Item *first= ord->item[0 ];
/*
It is possible that the query plan is to read table t1 , while the
sort criteria actually has " ORDER BY t2 . col " and the WHERE clause has
a multi - equality ( t1 . col , t2 . col , . . . ) .
The optimizer detects such cases ( grep for
UseMultipleEqualitiesToRemoveTempTable to see where ) , but doesn ' t
perform equality substitution in the order - > item . We need to do the
substitution here ourselves .
*/
table_map item_map= first->used_tables();
if (join && (item_map & ~join->const_table_map) &&
!(item_map & first_table_bit) && join->cond_equal &&
first->get_item_equal())
{
/*
Ok , this is the case descibed just above . Get the first element of the
multi - equality .
*/
Item_equal *item_eq= first->get_item_equal();
first= item_eq->get_first(NO_PARTICULAR_TAB, NULL);
}
Item *item= first->real_item();
pos->field= 0 ; pos->item= 0 ;
if (item->type() == Item::FIELD_ITEM)
pos->field= ((Item_field*) item)->field;
else if (item->type() == Item::SUM_FUNC_ITEM && !item->const_item())
{
// Aggregate, or Item_aggregate_ref
DBUG_ASSERT(first->type() == Item::SUM_FUNC_ITEM ||
(first->type() == Item::REF_ITEM &&
static_cast <Item_ref*>(first)->ref_type() ==
Item_ref::AGGREGATE_REF));
pos->field= first->get_tmp_table_field();
}
else if (item->type() == Item::COPY_STR_ITEM)
{ // Blob patch
pos->item= ((Item_copy*) item)->get_item();
}
else
pos->item= *ord->item;
pos->reverse= (ord->direction == ORDER::ORDER_DESC);
DBUG_ASSERT(pos->field != NULL || pos->item != NULL);
}
DBUG_RETURN(sort_keys);
}
/** Read 'count' number of buffer pointers into memory. */
static uchar *read_buffpek_from_file(IO_CACHE *buffpek_pointers, uint count,
uchar *buf)
{
size_t length= sizeof (Merge_chunk)*count;
uchar *tmp= buf;
DBUG_ENTER("read_buffpek_from_file" );
if (count > UINT_MAX/sizeof (Merge_chunk))
return 0 ; /* sizeof(BUFFPEK)*count will overflow */
if (!tmp)
tmp= (uchar *)my_malloc(key_memory_Filesort_info_merge, length,
MYF(MY_WME | MY_THREAD_SPECIFIC));
if (tmp)
{
if (reinit_io_cache(buffpek_pointers,READ_CACHE,0 L,0 ,0 ) ||
my_b_read(buffpek_pointers, (uchar*) tmp, length))
{
my_free(tmp);
tmp=0 ;
}
}
DBUG_RETURN(tmp);
}
#ifndef DBUG_OFF
static char dbug_row_print_buf[4096 ];
String dbug_format_row(TABLE *table, const uchar *rec, bool print_names)
{
String output(dbug_row_print_buf, sizeof (dbug_row_print_buf),
&my_charset_bin);
output.length(0 );
format_and_store_row(table, rec, print_names, "=" , true , output);
return output;
}
/**
A function to display a row in debugger .
Example usage :
( gdb ) p dbug_print_row ( table , table - > record [ 1 ] )
*/
const char *dbug_print_row(TABLE *table, const uchar *rec)
{
String row= dbug_format_row(table, rec);
if (row.length() > sizeof dbug_row_print_buf - 1 )
return "Couldn't fit into buffer" ;
memcpy(dbug_row_print_buf, row.c_ptr(), row.length());
return dbug_row_print_buf;
}
/**
Print table ' s current row into a buffer and return a pointer to it .
This is intended to be used from gdb :
( gdb ) p dbug_print_table_row ( table )
$ 33 = " SUBQUERY2_t1 ( col_int_key , col_varchar_nokey ) = ( 7 , c ) "
( gdb )
Only columns in table - > read_set are printed
*/
const char * dbug_print_table_row(TABLE *table)
{
return dbug_print_row(table, table->record[0 ]);
}
/*
Print a text , SQL - like record representation into dbug trace .
Note : this function is a work in progress : at the moment
- column read bitmap is ignored ( can print garbage for unused columns )
- there is no quoting
*/
static void dbug_print_record(TABLE *table, bool print_rowid)
{
char buff[1024 ];
Field **pfield;
String tmp(buff,sizeof (buff),&my_charset_bin);
DBUG_LOCK_FILE;
fprintf(DBUG_FILE, "record (" );
for (pfield= table->field; *pfield ; pfield++)
fprintf(DBUG_FILE, "%s%s" , (*pfield)->field_name.str,
(pfield[1 ])? ", " :"" );
fprintf(DBUG_FILE, ") = " );
fprintf(DBUG_FILE, "(" );
for (pfield= table->field; *pfield ; pfield++)
{
Field *field= *pfield;
if (field->is_null())
fwrite("NULL" , sizeof (char ), 4 , DBUG_FILE);
if (field->type() == MYSQL_TYPE_BIT)
(void ) field->val_int_as_str(&tmp, 1 );
else
field->val_str(&tmp);
fwrite(tmp.ptr(),sizeof (char ),tmp.length(),DBUG_FILE);
if (pfield[1 ])
fwrite(", " , sizeof (char ), 2 , DBUG_FILE);
}
fprintf(DBUG_FILE, ")" );
if (print_rowid)
{
fprintf(DBUG_FILE, " rowid " );
for (uint i=0 ; i < table->file->ref_length; i++)
{
fprintf(DBUG_FILE, "%x" , (uchar)table->file->ref[i]);
}
}
fprintf(DBUG_FILE, "\n" );
DBUG_UNLOCK_FILE;
}
#endif
/**
Search after sort_keys , and write them into tempfile
( if we run out of space in the sort_keys buffer ) .
All produced sequences are guaranteed to be non - empty .
@ param param Sorting parameter
@ param select Use this to get source data
@ param sort_keys Array of pointers to sort key + addon buffers .
@ param buffpek_pointers File to write BUFFPEKs describing sorted segments
in tempfile .
@ param tempfile File to write sorted sequences of sortkeys to .
@ param pq If ! NULL , use it for keeping top N elements
@ param [ out ] found_rows The number of FOUND_ROWS ( ) .
For a query with LIMIT , this value will typically
be larger than the function return value .
@ note
Basic idea :
@ verbatim
while ( get_next_sortkey ( ) )
{
if ( using priority queue )
push sort key into queue
else
{
if ( no free space in sort_keys buffers )
{
qsort sort_keys buffer ;
dump sorted sequence to ' tempfile ' ;
dump BUFFPEK describing sequence location into ' buffpek_pointers ' ;
}
put sort key into ' sort_keys ' ;
}
}
if ( sort_keys has some elements & & dumped at least once )
sort - dump - dump as above ;
else
don ' t sort , leave sort_keys array to be sorted by caller .
@ endverbatim
@ retval
Number of records written on success .
@ retval
HA_POS_ERROR on error .
*/
static ha_rows find_all_keys(THD *thd, Sort_param *param, SQL_SELECT *select,
SORT_INFO *fs_info, IO_CACHE *buffpek_pointers,
IO_CACHE *tempfile, Bounded_queue *pq,
ha_rows *found_rows)
{
int error, quick_select;
uint num_elements_in_buffer, indexpos;
uchar *ref_pos, *next_pos, ref_buff[MAX_REFLENGTH];
TABLE *sort_form;
handler *file;
MY_BITMAP *save_read_set, *save_write_set;
Item *sort_cond;
ha_rows num_records= 0 ;
const bool packed_format= param->is_packed_format();
const bool using_packed_sortkeys= param->using_packed_sortkeys();
DBUG_ENTER("find_all_keys" );
DBUG_PRINT("info" ,("using: %s" ,
(select ? select->quick ? "ranges" : "where" :
"every row" )));
num_elements_in_buffer= indexpos= 0 ;
error= 0 ;
*found_rows= 0 ;
sort_form= param->sort_form;
file= sort_form->file;
ref_pos= ref_buff;
quick_select= select && select->quick;
next_pos= ref_pos= &file->ref[0 ];
DBUG_EXECUTE_IF("show_explain_in_find_all_keys" ,
dbug_serve_apcs(thd, 1 );
);
if (!quick_select)
{
next_pos= (uchar*) 0 ; /* Find records in sequence */
DBUG_EXECUTE_IF("bug14365043_1" ,
DBUG_SET("+d,ha_rnd_init_fail" ););
if (unlikely(file->ha_rnd_init_with_error(1 )))
DBUG_RETURN(HA_POS_ERROR);
file->extra_opt(HA_EXTRA_CACHE, thd->variables.read_buff_size);
}
/* Remember original bitmaps */
save_read_set= sort_form->read_set;
save_write_set= sort_form->write_set;
/* Set up temporary column read map for columns used by sort */
DBUG_ASSERT(save_read_set != &sort_form->tmp_set);
bitmap_clear_all(&sort_form->tmp_set);
sort_form->column_bitmaps_set(&sort_form->tmp_set, &sort_form->tmp_set);
register_used_fields(param);
if (quick_select)
select->quick->add_used_key_part_to_set();
sort_cond= (!select ? 0 :
(!select->pre_idx_push_select_cond ?
select->cond : select->pre_idx_push_select_cond));
if (sort_cond)
sort_cond->walk(&Item::register_field_in_read_map,
sort_form, WALK_SUBQUERY);
sort_form->file->column_bitmaps_signal(false );
if (quick_select)
{
if (select->quick->reset())
goto err;
}
if (param->set_all_read_bits)
sort_form->column_bitmaps_set(save_read_set, save_write_set);
DEBUG_SYNC(thd, "after_index_merge_phase1" );
for (;;)
{
if (quick_select)
error= select->quick->get_next();
else /* Not quick-select */
{
error= file->ha_rnd_next(sort_form->record[0 ]);
if (param->unpack)
param->unpack(sort_form);
}
if (unlikely(error))
break ;
file->position(sort_form->record[0 ]);
DBUG_EXECUTE_IF("debug_filesort" , dbug_print_record(sort_form, TRUE ););
if (unlikely(thd->check_killed()))
{
DBUG_PRINT("info" ,("Sort killed by user" ));
goto err; /* purecov: inspected */
}
bool write_record= false ;
if (likely(error == 0 ))
{
param->examined_rows++;
thd->inc_examined_row_count_fast();
if (select && select->cond)
{
/*
If the condition ' select - > cond ' contains a subquery , restore the
original read / write sets of the table ' sort_form ' because when
SQL_SELECT : : skip_record evaluates this condition . it may include a
correlated subquery predicate , such that some field in the subquery
refers to ' sort_form ' .
PSergey - todo : discuss the above with Timour .
*/
MY_BITMAP *tmp_read_set= sort_form->read_set;
MY_BITMAP *tmp_write_set= sort_form->write_set;
if (select->cond->with_subquery())
sort_form->column_bitmaps_set(save_read_set, save_write_set);
write_record= (select->skip_record(thd) > 0 );
if (select->cond->with_subquery())
sort_form->column_bitmaps_set(tmp_read_set, tmp_write_set);
}
else
write_record= true ;
}
if (write_record)
{
if (pq)
pq->push(ref_pos);
else
{
if (fs_info->isfull())
{
if (write_keys(param, fs_info, num_elements_in_buffer,
buffpek_pointers, tempfile))
goto err;
num_elements_in_buffer= 0 ;
indexpos++;
}
if (num_elements_in_buffer == 0 )
fs_info->init_next_record_pointer();
uchar *start_of_rec= fs_info->get_next_record_pointer();
const uint rec_sz= make_sortkey(param, start_of_rec,
ref_pos, using_packed_sortkeys);
if (packed_format && rec_sz != param->rec_length)
fs_info->adjust_next_record_pointer(rec_sz);
num_elements_in_buffer++;
}
num_records++;
(*param->accepted_rows)++;
}
/* It does not make sense to read more keys in case of a fatal error */
if (unlikely(thd->is_error()))
break ;
/*
We need to this after checking the error as the transaction may have
rolled back in case of a deadlock
*/
if (!write_record)
file->unlock_row();
}
if (!quick_select)
{
(void ) file->extra(HA_EXTRA_NO_CACHE); /* End caching of records */
if (!next_pos)
file->ha_rnd_end();
}
/* Signal we should use original column read and write maps */
sort_form->column_bitmaps_set(save_read_set, save_write_set);
if (unlikely(thd->is_error()))
DBUG_RETURN(HA_POS_ERROR);
DBUG_PRINT("test" ,("error: %d indexpos: %d" ,error,indexpos));
if (unlikely(error != HA_ERR_END_OF_FILE))
{
file->print_error(error,MYF(ME_ERROR_LOG));
DBUG_RETURN(HA_POS_ERROR);
}
if (indexpos && num_elements_in_buffer &&
write_keys(param, fs_info, num_elements_in_buffer, buffpek_pointers,
tempfile))
DBUG_RETURN(HA_POS_ERROR); /* purecov: inspected */
(*found_rows)= num_records;
if (pq)
num_records= pq->num_elements();
DBUG_PRINT("info" , ("find_all_keys return %llu" , (ulonglong) num_records));
DBUG_RETURN(num_records);
err:
if (!quick_select)
{
(void ) file->extra(HA_EXTRA_NO_CACHE);
file->ha_rnd_end();
}
sort_form->column_bitmaps_set(save_read_set, save_write_set);
DBUG_RETURN(HA_POS_ERROR);
} /* find_all_keys */
/**
@ details
Sort the buffer and write :
- # the sorted sequence to tempfile
- # a BUFFPEK describing the sorted sequence position to buffpek_pointers
( was : Skriver en buffert med nycklar till filen )
@ param param Sort parameters
@ param sort_keys Array of pointers to keys to sort
@ param count Number of elements in sort_keys array
@ param buffpek_pointers One ' BUFFPEK ' struct will be written into this file .
The BUFFPEK : : { file_pos , count } will indicate where
the sorted data was stored .
@ param tempfile The sorted sequence will be written into this file .
@ retval
0 OK
@ retval
1 Error
*/
static bool
write_keys(Sort_param *param, SORT_INFO *fs_info, uint count,
IO_CACHE *buffpek_pointers, IO_CACHE *tempfile)
{
Merge_chunk buffpek;
DBUG_ENTER("write_keys" );
fs_info->sort_buffer(param, count);
if (!my_b_inited(tempfile) &&
open_cached_file(tempfile, mysql_tmpdir, TEMP_PREFIX, DISK_CHUNK_SIZE,
MYF(MY_WME | MY_TRACK_WITH_LIMIT)))
DBUG_RETURN(1 ); /* purecov: inspected */
/* check we won't have more buffpeks than we can possibly keep in memory */
if (my_b_tell(buffpek_pointers) + sizeof (Merge_chunk) > (ulonglong)UINT_MAX)
DBUG_RETURN(1 );
buffpek.set_file_position(my_b_tell(tempfile));
if ((ha_rows) count > param->limit_rows)
count=(uint) param->limit_rows; /* purecov: inspected */
buffpek.set_rowcount(static_cast <ha_rows>(count));
for (uint ix= 0 ; ix < count; ++ix)
{
uchar *record= fs_info->get_sorted_record(ix);
if (my_b_write(tempfile, record, param->get_record_length(record)))
DBUG_RETURN(1 ); /* purecov: inspected */
}
if (my_b_write(buffpek_pointers, (uchar*) &buffpek, sizeof (buffpek)))
DBUG_RETURN(1 );
DBUG_RETURN(0 );
} /* write_keys */
/**
Store length in high - byte - first order .
*/
void store_length(uchar *to, uint length, uint pack_length)
{
switch (pack_length) {
case 1 :
*to= (uchar) length;
break ;
case 2 :
mi_int2store(to, length);
break ;
case 3 :
mi_int3store(to, length);
break ;
default :
mi_int4store(to, length);
break ;
}
}
void
Type_handler_string_result::make_sort_key_part(uchar *to, Item *item,
const SORT_FIELD_ATTR *sort_field,
String *tmp_buffer) const
{
CHARSET_INFO *cs= item->collation.collation;
bool maybe_null= item->maybe_null();
if (maybe_null)
*to++= 1 ;
Binary_string *res= item->str_result(tmp_buffer);
if (!res)
{
if (maybe_null)
memset(to - 1 , 0 , sort_field->length + 1 );
else
{
/* purecov: begin deadcode */
/*
This should only happen during extreme conditions if we run out
of memory or have an item marked not null when it can be null .
This code is here mainly to avoid a hard crash in this case .
*/
DBUG_ASSERT(0 );
DBUG_PRINT("warning" ,
("Got null on something that shouldn't be null" ));
memset(to, 0 , sort_field->length); // Avoid crash
/* purecov: end */
}
return ;
}
if (use_strnxfrm(cs))
{
my_strnxfrm_ret_t rc=
cs->strnxfrm(to, sort_field->length,
item->max_char_length() * cs->strxfrm_multiply,
(uchar*) res->ptr(), res->length(),
MY_STRXFRM_PAD_WITH_SPACE |
MY_STRXFRM_PAD_TO_MAXLEN);
DBUG_ASSERT(rc.m_result_length == sort_field->length);
if (rc.m_warnings & MY_STRNXFRM_TRUNCATED_WEIGHT_REAL_CHAR)
current_thd->num_of_strings_sorted_on_truncated_length++;
}
else
{
uint diff;
uint sort_field_length= sort_field->length - sort_field->suffix_length;
uint length= res->length();
if (sort_field_length < length)
{
diff= 0 ;
length= sort_field_length;
}
else
diff= sort_field_length - length;
if (sort_field->suffix_length)
{
/* Store length last in result_string */
store_length(to + sort_field_length, length, sort_field->suffix_length);
}
/* apply cs->sort_order for case-insensitive comparison if needed */
my_strnxfrm_ret_t rc= cs->strnxfrm((uchar*)to, length,
(const uchar*) res->ptr(), res->length());
if (rc.m_warnings & MY_STRNXFRM_TRUNCATED_WEIGHT_REAL_CHAR)
current_thd->num_of_strings_sorted_on_truncated_length++;
char fill_char= ((cs->state & MY_CS_BINSORT) ? (char ) 0 : ' ' );
cs->fill((char *) to + length, diff, fill_char);
}
}
void
Type_handler_int_result::make_sort_key_part(uchar *to, Item *item,
const SORT_FIELD_ATTR *sort_field,
String *tmp_buffer) const
{
longlong value= item->val_int_result();
make_sort_key_longlong(to, item->maybe_null(), item->null_value,
item->unsigned_flag, value);
}
void
Type_handler_temporal_result::make_sort_key_part(uchar *to, Item *item,
const SORT_FIELD_ATTR *sort_field,
String *tmp_buffer) const
{
MYSQL_TIME buf;
// This is a temporal type. No nanoseconds. Rounding mode is not important.
DBUG_ASSERT(item->cmp_type() == TIME_RESULT);
static const Temporal::Options opt(TIME_INVALID_DATES, TIME_FRAC_NONE);
if (item->get_date_result(current_thd, &buf, opt))
{
DBUG_ASSERT(item->maybe_null());
DBUG_ASSERT(item->null_value);
make_sort_key_longlong(to, item->maybe_null(), true ,
item->unsigned_flag, 0 );
}
else
make_sort_key_longlong(to, item->maybe_null(), false ,
item->unsigned_flag, pack_time(&buf));
}
void
Type_handler_timestamp_common::make_sort_key_part(uchar *to, Item *item,
const SORT_FIELD_ATTR *sort_field,
String *tmp_buffer) const
{
THD *thd= current_thd;
decimal_digits_t dec= MY_MIN(item->decimals, TIME_SECOND_PART_DIGITS);
uint binlen= my_timestamp_binary_length(dec);
Timestamp_or_zero_datetime_native_null native(thd, item);
if (native.is_null() || native.is_zero_datetime())
{
// NULL or '0000-00-00 00:00:00'
bzero(to, item->maybe_null() ? binlen + 1 : binlen);
}
else
{
if (item->maybe_null())
*to++= 1 ;
if (native.length() != binlen)
{
/*
Some items can return native representation with a different
number of fractional digits , e . g . : GREATEST ( ts_3 , ts_4 ) can
return a value with 3 fractional digits , although its fractional
precision is 4 . Re - pack with a proper precision now .
*/
Timestamp(native).to_native(&native, item->datetime_precision(thd));
}
DBUG_ASSERT(native.length() == binlen);
memcpy((char *) to, native.ptr(), binlen);
}
}
void
Type_handler::store_sort_key_longlong(uchar *to, bool unsigned_flag,
longlong value) const
{
to[7 ]= (uchar) value;
to[6 ]= (uchar) (value >> 8 );
to[5 ]= (uchar) (value >> 16 );
to[4 ]= (uchar) (value >> 24 );
to[3 ]= (uchar) (value >> 32 );
to[2 ]= (uchar) (value >> 40 );
to[1 ]= (uchar) (value >> 48 );
if (unsigned_flag) /* Fix sign */
to[0 ]= (uchar) (value >> 56 );
else
to[0 ]= (uchar) (value >> 56 ) ^ 128 ; /* Reverse signbit */
}
void
Type_handler::make_sort_key_longlong(uchar *to,
bool maybe_null,
bool null_value,
bool unsigned_flag,
longlong value) const
{
if (maybe_null)
{
if (null_value)
{
memset(to, 0 , 9 );
return ;
}
*to++= 1 ;
}
store_sort_key_longlong(to, unsigned_flag, value);
}
uint
Type_handler::make_packed_sort_key_longlong(uchar *to, bool maybe_null,
bool null_value, bool unsigned_flag,
longlong value,
const SORT_FIELD_ATTR *sort_field) const
{
if (maybe_null)
{
if (null_value)
{
*to++= 0 ;
return 0 ;
}
*to++= 1 ;
}
store_sort_key_longlong(to, unsigned_flag, value);
DBUG_ASSERT(sort_field->original_length == sort_field->length);
return sort_field->original_length;
}
void
Type_handler_decimal_result::make_sort_key_part(uchar *to, Item *item,
const SORT_FIELD_ATTR *sort_field,
String *tmp_buffer) const
{
my_decimal dec_buf, *dec_val= item->val_decimal_result(&dec_buf);
if (item->maybe_null())
{
if (item->null_value)
{
memset(to, 0 , sort_field->length + 1 );
return ;
}
*to++= 1 ;
}
dec_val->to_binary(to, item->max_length - (item->decimals ? 1 : 0 ),
item->decimals);
}
void
Type_handler_real_result::make_sort_key_part(uchar *to, Item *item,
const SORT_FIELD_ATTR *sort_field,
String *tmp_buffer) const
{
double value= item->val_result();
if (item->maybe_null())
{
if (item->null_value)
{
memset(to, 0 , sort_field->length + 1 );
return ;
}
*to++= 1 ;
}
change_double_for_sort(value, to);
}
/** Make a sort-key from record. */
static uint make_sortkey(Sort_param *param, uchar *to, uchar *ref_pos,
bool using_packed_sortkeys)
{
uchar *orig_to= to;
to+= using_packed_sortkeys ?
make_packed_sortkey(param, to) :
make_sortkey(param, to);
if (param->using_addon_fields())
{
/*
Save field values appended to sorted fields .
First null bit indicators are appended then field values follow .
In this implementation we use fixed layout for field values -
the same for all records .
*/
SORT_ADDON_FIELD *addonf= param->addon_fields->begin();
uchar *nulls= to;
uchar *p_len= to;
DBUG_ASSERT(addonf != 0 );
const bool packed_addon_fields= param->addon_fields->using_packed_addons();
uint32 res_len= addonf->offset;
memset(nulls, 0 , addonf->offset);
to+= addonf->offset;
for ( ; addonf != param->addon_fields->end() ; addonf++)
{
Field *field= addonf->field;
if (addonf->null_bit && field->is_null())
{
nulls[addonf->null_offset]|= addonf->null_bit;
if (!packed_addon_fields)
to+= addonf->length;
}
else
{
uchar *end= field->pack (to, field->ptr);
DBUG_ASSERT(end >= to);
uint sz= static_cast <uint>(end - to);
res_len += sz;
if (packed_addon_fields)
to+= sz;
else
{
if (addonf->length > sz)
bzero(end, addonf->length - sz); // Make Valgrind/MSAN happy
to+= addonf->length;
}
}
}
if (packed_addon_fields)
Addon_fields::store_addon_length(p_len, res_len);
}
else
{
/* Save filepos last */
memcpy((uchar*) to, ref_pos, (size_t) param->ref_length);
to+= param->ref_length;
}
return static_cast <uint>(to - orig_to);
}
/*
Register fields used by sorting in the sorted table ' s read set
*/
static void register_used_fields(Sort_param *param)
{
SORT_FIELD *sort_field;
TABLE *table=param->sort_form;
for (sort_field= param->local_sortorder.begin() ;
sort_field != param->local_sortorder.end() ;
sort_field++)
{
Field *field;
if ((field= sort_field->field))
{
if (field->table == table)
field->register_field_in_read_map();
}
else
{ // Item
sort_field->item->walk(&Item::register_field_in_read_map,
table, WALK_SUBQUERY);
}
}
if (param->using_addon_fields())
{
SORT_ADDON_FIELD *addonf= param->addon_fields->begin();
for ( ; (addonf != param->addon_fields->end()) ; addonf++)
{
Field *field= addonf->field;
field->register_field_in_read_map();
}
}
else
{
/* Save filepos last */
table->prepare_for_position();
}
}
static bool save_index(Sort_param *param, uint count, SORT_INFO *table_sort)
{
uint offset,res_length, length;
uchar *to;
DBUG_ENTER("save_index" );
DBUG_ASSERT(table_sort->record_pointers == 0 );
table_sort->sort_buffer(param, count);
if (param->using_addon_fields())
{
table_sort->sorted_result_in_fsbuf= TRUE ;
table_sort->set_sort_length(param->sort_length);
DBUG_RETURN(0 );
}
bool using_packed_sortkeys= param->using_packed_sortkeys();
res_length= param->res_length;
offset= param->rec_length-res_length;
if (!(to= table_sort->record_pointers=
(uchar*) my_malloc(key_memory_Filesort_info_record_pointers,
res_length*count, MYF(MY_WME | MY_THREAD_SPECIFIC))))
DBUG_RETURN(1 ); /* purecov: inspected */
for (uint ix= 0 ; ix < count; ++ix)
{
uchar *record= table_sort->get_sorted_record(ix);
length= using_packed_sortkeys ?
Sort_keys::read_sortkey_length(record) : offset;
memcpy(to, record + length, res_length);
to+= res_length;
}
DBUG_RETURN(0 );
}
/** Merge buffers to make < MERGEBUFF2 buffers. */
int merge_many_buff(Sort_param *param, Sort_buffer sort_buffer,
Merge_chunk *buffpek, uint *maxbuffer, IO_CACHE *t_file)
{
uint i;
IO_CACHE t_file2,*from_file,*to_file,*temp;
Merge_chunk *lastbuff;
DBUG_ENTER("merge_many_buff" );
if (*maxbuffer < MERGEBUFF2)
DBUG_RETURN(0 ); /* purecov: inspected */
if (flush_io_cache(t_file) ||
open_cached_file(&t_file2, mysql_tmpdir, TEMP_PREFIX, DISK_CHUNK_SIZE,
MYF(MY_WME | MY_TRACK_WITH_LIMIT)))
DBUG_RETURN(1 ); /* purecov: inspected */
from_file= t_file; to_file= &t_file2;
while (*maxbuffer >= MERGEBUFF2)
{
if (reinit_io_cache(from_file, READ_CACHE, 0 L, 0 , 0 ))
goto cleanup;
if (reinit_io_cache(to_file, WRITE_CACHE,0 L, 0 , 0 ))
goto cleanup;
lastbuff=buffpek;
for (i= 0 ; i <= *maxbuffer - MERGEBUFF * 3 / 2 ; i+= MERGEBUFF)
{
if (merge_buffers(param, from_file, to_file, sort_buffer, lastbuff++,
buffpek + i, buffpek + i + MERGEBUFF - 1 , 0 ))
goto cleanup;
}
if (merge_buffers(param, from_file, to_file, sort_buffer, lastbuff++,
buffpek + i, buffpek + *maxbuffer, 0 ))
break ; /* purecov: inspected */
if (flush_io_cache(to_file))
break ; /* purecov: inspected */
temp=from_file; from_file=to_file; to_file=temp;
*maxbuffer= (uint) (lastbuff-buffpek)-1 ;
}
cleanup:
close_cached_file(to_file); // This holds old result
if (to_file == t_file)
{
*t_file=t_file2; // Copy result file
}
DBUG_RETURN(*maxbuffer >= MERGEBUFF2); /* Return 1 if interrupted */
} /* merge_many_buff */
/**
Read data to buffer .
@ retval Number of bytes read
( ulong ) - 1 if something goes wrong
*/
ulong read_to_buffer(IO_CACHE *fromfile, Merge_chunk *buffpek,
Sort_param *param, bool packed_format)
{
ha_rows count;
uint rec_length= param->rec_length;
if ((count= MY_MIN(buffpek->max_keys(),buffpek->rowcount())))
{
size_t bytes_to_read;
if (packed_format)
{
count= buffpek->rowcount();
bytes_to_read= MY_MIN(buffpek->buffer_size(),
static_cast <size_t>(fromfile->end_of_file -
buffpek->file_position()));
}
else
bytes_to_read= rec_length * static_cast <size_t>(count);
if (unlikely(my_b_pread(fromfile, buffpek->buffer_start(),
bytes_to_read, buffpek->file_position())))
return ((ulong) -1 );
size_t num_bytes_read;
if (packed_format)
{
/*
The last record read is most likely not complete here .
We need to loop through all the records , reading the length fields ,
and then " chop off " the final incomplete record .
*/
uchar *record= buffpek->buffer_start();
uint ix= 0 ;
uint size_of_addon_length= param->using_packed_addons() ?
Addon_fields::size_of_length_field : 0 ;
uint size_of_sort_length= param->using_packed_sortkeys() ?
Sort_keys::size_of_length_field : 0 ;
for (; ix < count; ++ix)
{
if (record + size_of_sort_length > buffpek->buffer_end())
break ;
uint sort_length= param->using_packed_sortkeys() ?
Sort_keys::read_sortkey_length(record) :
param->sort_length;
DBUG_ASSERT(sort_length <= param->sort_length);
if (record + sort_length + size_of_addon_length >
buffpek->buffer_end())
break ; // Incomplete record.
uchar *plen= record + sort_length;
uint res_length= param->get_result_length(plen);
if (plen + res_length > buffpek->buffer_end())
break ; // Incomplete record.
DBUG_ASSERT(res_length > 0 );
DBUG_ASSERT(sort_length + res_length <= param->rec_length);
record+= sort_length;
record+= res_length;
}
DBUG_ASSERT(ix > 0 );
count= ix;
num_bytes_read= record - buffpek->buffer_start();
DBUG_PRINT("info" , ("read %llu bytes of complete records" ,
static_cast <ulonglong>(bytes_to_read)));
}
else
num_bytes_read= bytes_to_read;
buffpek->init_current_key();
buffpek->advance_file_position(num_bytes_read); /* New filepos */
buffpek->decrement_rowcount(count);
buffpek->set_mem_count(count);
return (ulong) num_bytes_read;
}
return 0 ;
} /* read_to_buffer */
/**
Put all room used by freed buffer to use in adjacent buffer .
Note , that we can ' t simply distribute memory evenly between all buffers ,
because new areas must not overlap with old ones .
@ param [ in ] queue list of non - empty buffers , without freed buffer
@ param [ in ] reuse empty buffer
@ param [ in ] key_length key length
*/
void reuse_freed_buff(QUEUE *queue, Merge_chunk *reuse, uint key_length)
{
for (uint i= queue_first_element(queue);
i <= queue_last_element(queue);
i++)
{
Merge_chunk *bp= (Merge_chunk *) queue_element(queue, i);
if (reuse->merge_freed_buff(bp))
return ;
}
DBUG_ASSERT(0 );
}
/**
Merge buffers to one buffer .
@ param param Sort parameter
@ param from_file File with source data ( BUFFPEKs point to this file )
@ param to_file File to write the sorted result data .
@ param sort_buffer Buffer for data to store up to MERGEBUFF2 sort keys .
@ param lastbuff OUT Store here BUFFPEK describing data written to to_file
@ param Fb First element in source BUFFPEKs array
@ param Tb Last element in source BUFFPEKs array
@ param flag 0 < = > write { sort_key , addon_fields } pairs as further
sorting will be performed
1 < = > write just addon_fields as this is the final
merge pass
@ retval
0 OK
@ retval
1 ERROR
*/
bool merge_buffers(Sort_param *param, IO_CACHE *from_file,
IO_CACHE *to_file, Sort_buffer sort_buffer,
Merge_chunk *lastbuff, Merge_chunk *Fb, Merge_chunk *Tb,
int flag)
{
bool error= 0 ;
uint rec_length,res_length,offset;
size_t sort_length;
ulong maxcount, bytes_read;
ha_rows max_rows,org_max_rows;
my_off_t to_start_filepos;
uchar *strpos;
Merge_chunk *buffpek;
QUEUE queue;
qsort_cmp2 cmp;
void *first_cmp_arg;
element_count dupl_count= 0 ;
uchar *src;
uchar *unique_buff= param->unique_buff;
const bool killable= !param->not_killable;
THD* const thd=current_thd;
DBUG_ENTER("merge_buffers" );
thd->inc_status_sort_merge_passes();
thd->query_plan_fsort_passes++;
rec_length= param->rec_length;
res_length= param->res_length;
sort_length= param->sort_length;
uint dupl_count_ofs= rec_length-sizeof (element_count);
uint min_dupl_count= param->min_dupl_count;
bool check_dupl_count= flag && min_dupl_count;
offset= (rec_length-
(flag && min_dupl_count ? sizeof (dupl_count) : 0 )-res_length);
uint wr_len= flag ? res_length : rec_length;
uint wr_offset= flag ? offset : 0 ;
const bool using_packed_sortkeys= param->using_packed_sortkeys();
bool offset_for_packing= (flag == 1 && using_packed_sortkeys);
const bool packed_format= param->is_packed_format();
maxcount= (ulong) (param->max_keys_per_buffer/((uint) (Tb-Fb) +1 ));
to_start_filepos= my_b_tell(to_file);
strpos= sort_buffer.array();
org_max_rows= max_rows= param->limit_rows;
set_if_bigger(maxcount, 1 );
if (unique_buff)
{
cmp= param->compare;
first_cmp_arg= (void *) ¶m->cmp_context;
}
else
{
cmp= param->get_compare_function();
first_cmp_arg= param->get_compare_argument(&sort_length);
}
if (unlikely(init_queue(&queue, (uint) (Tb - Fb) + 1 ,
offsetof(Merge_chunk, m_current_key), 0 , cmp,
first_cmp_arg, 0 , 0 )))
DBUG_RETURN(1 ); /* purecov: inspected */
const size_t chunk_sz= (sort_buffer.size()/((uint) (Tb-Fb) +1 ));
for (buffpek= Fb; buffpek <= Tb; buffpek++)
{
buffpek->set_buffer(strpos, strpos + chunk_sz);
buffpek->set_max_keys(maxcount);
bytes_read= read_to_buffer(from_file, buffpek, param, packed_format);
if (unlikely(bytes_read == (ulong) -1 ))
goto err; /* purecov: inspected */
strpos+= chunk_sz;
// If less data in buffers than expected
buffpek->set_max_keys(buffpek->mem_count());
queue_insert(&queue, (uchar*) buffpek);
}
if (unique_buff)
{
/*
Called by Unique : : get ( )
Copy the first argument to unique_buff for unique removal .
Store it also in ' to_file ' .
*/
buffpek= (Merge_chunk*) queue_top(&queue);
memcpy(unique_buff, buffpek->current_key(), rec_length);
if (min_dupl_count)
memcpy(&dupl_count, unique_buff+dupl_count_ofs,
sizeof (dupl_count));
buffpek->advance_current_key(rec_length);
buffpek->decrement_mem_count();
if (buffpek->mem_count() == 0 )
{
if (unlikely(!(bytes_read= read_to_buffer(from_file, buffpek,
param, packed_format))))
{
(void ) queue_remove_top(&queue);
reuse_freed_buff(&queue, buffpek, rec_length);
}
else if (unlikely(bytes_read == (ulong) -1 ))
goto err; /* purecov: inspected */
}
queue_replace_top(&queue); // Top element has been used
}
else
cmp= 0 ; // Not unique
while (queue.elements > 1 )
{
if (killable && unlikely(thd->check_killed()))
goto err; /* purecov: inspected */
for (;;)
{
buffpek= (Merge_chunk*) queue_top(&queue);
src= buffpek->current_key();
if (cmp) // Remove duplicates
{
uchar *current_key= buffpek->current_key();
if (!(*cmp)(first_cmp_arg, &unique_buff, ¤t_key))
{
if (min_dupl_count)
{
element_count cnt;
memcpy(&cnt, buffpek->current_key() + dupl_count_ofs, sizeof (cnt));
dupl_count+= cnt;
}
goto skip_duplicate;
}
if (min_dupl_count)
{
memcpy(unique_buff+dupl_count_ofs, &dupl_count,
sizeof (dupl_count));
}
src= unique_buff;
}
{
param->get_rec_and_res_len(buffpek->current_key(),
&rec_length, &res_length);
const uint bytes_to_write= (flag == 0 ) ? rec_length : res_length;
/*
Do not write into the output file if this is the final merge called
for a Unique object used for intersection and dupl_count is less
than min_dupl_count .
If the Unique object is used to intersect N sets of unique elements
then for any element :
dupl_count > = N < = > the element is occurred in each of these N sets .
*/
if (!check_dupl_count || dupl_count >= min_dupl_count)
{
if (my_b_write(to_file,
src + (offset_for_packing ?
rec_length - res_length : // sort length
wr_offset),
bytes_to_write))
goto err; /* purecov: inspected */
}
if (cmp)
{
memcpy(unique_buff, buffpek->current_key(), rec_length);
if (min_dupl_count)
memcpy(&dupl_count, unique_buff+dupl_count_ofs,
sizeof (dupl_count));
}
if (!--max_rows)
{
/* Nothing more to do */
goto end; /* purecov: inspected */
}
}
skip_duplicate:
buffpek->advance_current_key(rec_length);
buffpek->decrement_mem_count();
if (buffpek->mem_count() == 0 )
{
if (unlikely(!(bytes_read= read_to_buffer(from_file, buffpek,
param, packed_format))))
{
(void ) queue_remove_top(&queue);
reuse_freed_buff(&queue, buffpek, rec_length);
break ; /* One buffer have been removed */
}
else if (unlikely(bytes_read == (ulong) -1 ))
goto err; /* purecov: inspected */
}
queue_replace_top(&queue); /* Top element has been replaced */
}
}
buffpek= (Merge_chunk*) queue_top(&queue);
buffpek->set_buffer(sort_buffer.array(),
sort_buffer.array() + sort_buffer.size());
buffpek->set_max_keys(param->max_keys_per_buffer);
/*
As we know all entries in the buffer are unique , we only have to
check if the first one is the same as the last one we wrote
*/
if (cmp)
{
uchar *current_key= buffpek->current_key();
if (!(*cmp)(first_cmp_arg, &unique_buff, ¤t_key))
{
if (min_dupl_count)
{
element_count cnt;
memcpy(&cnt, buffpek->current_key() + dupl_count_ofs, sizeof (cnt));
dupl_count+= cnt;
}
buffpek->advance_current_key(rec_length);
buffpek->decrement_mem_count();
}
if (min_dupl_count)
memcpy(unique_buff+dupl_count_ofs, &dupl_count,
sizeof (dupl_count));
if (!check_dupl_count || dupl_count >= min_dupl_count)
{
src= unique_buff;
if (my_b_write(to_file, src+wr_offset, wr_len))
goto err; /* purecov: inspected */
if (!--max_rows)
goto end;
}
}
do
{
if (buffpek->mem_count() > max_rows)
{ /* Don't write too many records */
buffpek->set_mem_count(max_rows);
buffpek->set_rowcount(0 ); /* Don't read more */
}
max_rows-= buffpek->mem_count();
for (uint ix= 0 ; ix < buffpek->mem_count(); ++ix)
{
uchar *src= buffpek->current_key();
param->get_rec_and_res_len(src,
&rec_length, &res_length);
const uint bytes_to_write= (flag == 0 ) ? rec_length : res_length;
if (check_dupl_count)
{
memcpy((uchar *) &dupl_count,
buffpek->current_key() + offset + dupl_count_ofs,
sizeof (dupl_count));
if (dupl_count < min_dupl_count)
continue ;
}
if (my_b_write(to_file,
src + (offset_for_packing ?
rec_length - res_length : // sort length
wr_offset),
bytes_to_write))
goto err;
buffpek->advance_current_key(rec_length);
}
}
while (likely(!(error=
(bytes_read= read_to_buffer(from_file, buffpek, param,
packed_format)) == (ulong) -1 )) &&
bytes_read != 0 );
end:
lastbuff->set_rowcount(MY_MIN(org_max_rows - max_rows, param->limit_rows));
lastbuff->set_file_position(to_start_filepos);
cleanup:
delete_queue(&queue);
DBUG_RETURN(error);
err:
error= 1 ;
goto cleanup;
} /* merge_buffers */
/* Do a merge to output-file (save only positions) */
int merge_index(Sort_param *param, Sort_buffer sort_buffer,
Merge_chunk *buffpek, uint maxbuffer,
IO_CACHE *tempfile, IO_CACHE *outfile)
{
DBUG_ENTER("merge_index" );
if (merge_buffers(param, tempfile, outfile, sort_buffer, buffpek, buffpek,
buffpek + maxbuffer, 1 ))
DBUG_RETURN(1 ); /* purecov: inspected */
DBUG_RETURN(0 );
} /* merge_index */
static uint suffix_length(ulong string_length)
{
if (string_length < 256 )
return 1 ;
if (string_length < 256 L*256 L)
return 2 ;
if (string_length < 256 L*256 L*256 L)
return 3 ;
return 4 ; // Can't sort longer than 4G
}
void
Type_handler_string_result::sort_length(THD *thd,
const Type_std_attributes *item,
SORT_FIELD_ATTR *sortorder) const
{
CHARSET_INFO *cs;
sortorder->set_length_and_original_length(thd, item->max_length);
if (use_strnxfrm((cs= item->collation.collation)))
{
sortorder->length= (uint) cs->strnxfrmlen(sortorder->length);
}
else if (cs == &my_charset_bin)
{
/* Store length last to be able to sort blob/varbinary */
sortorder->suffix_length= suffix_length(item->max_length);
DBUG_ASSERT(sortorder->length <= UINT_MAX32 - sortorder->suffix_length);
sortorder->length+= sortorder->suffix_length;
if (sortorder->original_length >= UINT_MAX32 - sortorder->suffix_length)
sortorder->original_length= UINT_MAX32;
else
sortorder->original_length+= sortorder->suffix_length;
}
}
void
Type_handler_temporal_result::sort_length(THD *thd,
const Type_std_attributes *item,
SORT_FIELD_ATTR *sortorder) const
{
sortorder->original_length= sortorder->length= 8 ; // Sizof intern longlong
}
void
Type_handler_timestamp_common::sort_length(THD *thd,
const Type_std_attributes *item,
SORT_FIELD_ATTR *sortorder) const
{
decimal_digits_t dec= MY_MIN(item->decimals, TIME_SECOND_PART_DIGITS);
sortorder->length= my_timestamp_binary_length(dec);
sortorder->original_length= sortorder->length;
}
void
Type_handler_int_result::sort_length(THD *thd,
const Type_std_attributes *item,
SORT_FIELD_ATTR *sortorder) const
{
sortorder->original_length= sortorder->length= 8 ; // Sizof intern longlong
}
void
Type_handler_real_result::sort_length(THD *thd,
const Type_std_attributes *item,
SORT_FIELD_ATTR *sortorder) const
{
sortorder->original_length= sortorder->length= sizeof (double );
}
void
Type_handler_decimal_result::sort_length(THD *thd,
const Type_std_attributes *item,
SORT_FIELD_ATTR *sortorder) const
{
sortorder->length=
my_decimal_get_binary_size(item->max_length - (item->decimals ? 1 : 0 ),
item->decimals);
sortorder->original_length= sortorder->length;
}
/**
Calculate length of sort key .
@ param thd Thread handler
@ param sortorder Order of items to sort
@ param s_length Number of items to sort
@ param allow_packing_for_sortkeys [ out ] set to false if packing sort keys
is not allowed
@ note
- sortorder - > length and other members are updated for each sort item .
- TODO what is the meaning of this value if some fields are using packing
while others are not ?
@ return
Total length of sort buffer in bytes
*/
static uint
sortlength(THD *thd, Sort_keys *sort_keys, bool *allow_packing_for_sortkeys)
{
uint length;
*allow_packing_for_sortkeys= true ;
bool allow_packing_for_keys= true ;
length=0 ;
uint nullable_cols=0 ;
if (sort_keys->is_parameters_computed())
{
*allow_packing_for_sortkeys= sort_keys->using_packed_sortkeys();
return sort_keys->get_sort_length_with_memcmp_values();
}
for (SORT_FIELD *sortorder= sort_keys->begin();
sortorder != sort_keys->end();
sortorder++)
{
sortorder->suffix_length= 0 ;
sortorder->length_bytes= 0 ;
if (sortorder->field)
{
Field *field= sortorder->field;
CHARSET_INFO *cs= sortorder->field->sort_charset();
sortorder->type= field->is_packable() ?
SORT_FIELD_ATTR::VARIABLE_SIZE :
SORT_FIELD_ATTR::FIXED_SIZE;
sortorder->set_length_and_original_length(thd, field->sort_length());
sortorder->suffix_length= sortorder->field->sort_suffix_length();
sortorder->cs= cs;
if (use_strnxfrm((cs=sortorder->field->sort_charset())))
sortorder->length= (uint) cs->strnxfrmlen(sortorder->length);
if (sortorder->is_variable_sized() && allow_packing_for_keys)
{
allow_packing_for_keys= sortorder->check_if_packing_possible(thd);
sortorder->length_bytes=
number_storage_requirement(MY_MIN(sortorder->original_length,
thd->variables.max_sort_length));
}
if ((sortorder->maybe_null= sortorder->field->maybe_null()))
nullable_cols++; // Place for NULL marker
}
else
{
sortorder->type= sortorder->item->type_handler()->is_packable() ?
SORT_FIELD_ATTR::VARIABLE_SIZE :
SORT_FIELD_ATTR::FIXED_SIZE;
sortorder->item->type_handler()->sort_length(thd, sortorder->item,
sortorder);
sortorder->cs= sortorder->item->collation.collation;
if (sortorder->is_variable_sized() && allow_packing_for_keys)
{
allow_packing_for_keys= sortorder->check_if_packing_possible(thd);
sortorder->length_bytes=
number_storage_requirement(MY_MIN(sortorder->original_length,
thd->variables.max_sort_length));
}
if ((sortorder->maybe_null= sortorder->item->maybe_null()))
nullable_cols++; // Place for NULL marker
}
if (sortorder->is_variable_sized())
{
set_if_smaller(sortorder->length, thd->variables.max_sort_length);
set_if_smaller(sortorder->original_length, thd->variables.max_sort_length);
}
DBUG_ASSERT(length < UINT_MAX32 - sortorder->length);
length+= sortorder->length;
sort_keys->increment_size_of_packable_fields(sortorder->length_bytes);
sort_keys->increment_original_sort_length(sortorder->original_length);
}
// add bytes for nullable_cols
sort_keys->increment_original_sort_length(nullable_cols);
*allow_packing_for_sortkeys= allow_packing_for_keys;
sort_keys->set_sort_length_with_memcmp_values(length + nullable_cols);
sort_keys->set_parameters_computed(true );
DBUG_PRINT("info" ,("sort_length: %d" ,length));
return length + nullable_cols;
}
/*
Check whether addon fields can be used or not .
@ param table Table structure
@ param sortlength Length of sort key [ strxfrm form ]
@ param length [ OUT ] Max length of addon fields
@ param fields [ OUT ] Number of addon fields
@ param null_fields [ OUT ] Number of nullable addon fields
@ param packable_length [ OUT ] Max length of addon fields that can be
packed
@ retval
TRUE Addon fields can be used
FALSE Otherwise
*/
bool filesort_use_addons(TABLE *table, uint sortlength,
uint *length, uint *fields, uint *null_fields,
uint *packable_length)
{
Field **pfield, *field;
*length= *fields= *null_fields= *packable_length= 0 ;
uint field_length=0 ;
for (pfield= table->field; (field= *pfield) ; pfield++)
{
if (!bitmap_is_set(table->read_set, field->field_index))
continue ;
if (field->flags & BLOB_FLAG)
return false ;
field_length= field->max_packed_col_length(field->pack_length());
(*length)+= field_length;
if (field->maybe_null() || field->is_packable())
(*packable_length)+= field_length;
if (field->maybe_null())
(*null_fields)++;
(*fields)++;
}
if (!*fields)
return false ;
(*length)+= (*null_fields+7 )/8 ;
/*
sortlength used here is unpacked key length ( the strxfrm form ) . This is
done because unpacked key length is a good upper bound for packed sort
key length .
But for some collations the max packed length may be greater than the
length obtained from the strxfrm form .
Example : for utf8_general_ci , the original string form can be longer than
its mem - comparable form ( note that this is rarely achieved in practice ) .
*/
return *length + sortlength <
table->in_use->variables.max_length_for_sort_data;
}
/**
Get descriptors of fields appended to sorted fields and
calculate its total length .
The function first finds out what fields are used in the result set .
Then it calculates the length of the buffer to store the values of
these fields together with the value of sort values .
If the calculated length is not greater than max_length_for_sort_data
the function allocates memory for an array of descriptors containing
layouts for the values of the non - sorted fields in the buffer and
fills them .
@ param table Table structure
@ param sortlength Total length of sorted fields
@ param addon_length [ OUT ] Length of addon fields
@ param m_packable_length [ OUT ] Length of the addon fields that can be
packed
@ note
The null bits for the appended values are supposed to be put together
and stored the buffer just ahead of the value of the first field .
@ return
Pointer to the layout descriptors for the appended fields , if any
@ retval
NULL if we do not store field values with sort data .
*/
static Addon_fields*
get_addon_fields(TABLE *table, uint sortlength,
uint *addon_length, uint *m_packable_length)
{
Field **pfield;
Field *field;
uint length, fields, null_fields, packable_length;
MY_BITMAP *read_set= table->read_set;
DBUG_ENTER("get_addon_fields" );
/*
If there is a reference to a field in the query add it
to the set of appended fields .
Note for future refinement :
This this a too strong condition .
Actually we need only the fields referred in the
result set . And for some of them it makes sense to use
the values directly from sorted fields .
But beware the case when item - > cmp_type ( ) ! = item - > result_type ( )
*/
// see remove_const() for HA_SLOW_RND_POS explanation
if (table->file->ha_table_flags() & HA_SLOW_RND_POS)
sortlength= 0 ;
void *raw_mem_addon_field, *raw_mem;
if (!filesort_use_addons(table, sortlength, &length, &fields, &null_fields,
&packable_length) ||
!(my_multi_malloc(PSI_INSTRUMENT_ME, MYF(MY_WME | MY_THREAD_SPECIFIC),
&raw_mem, sizeof (Addon_fields),
&raw_mem_addon_field,
sizeof (SORT_ADDON_FIELD) * fields,
NullS)))
DBUG_RETURN(0 );
Addon_fields_array
addon_array(static_cast <SORT_ADDON_FIELD*>(raw_mem_addon_field), fields);
Addon_fields *addon_fields= new (raw_mem) Addon_fields(addon_array);
DBUG_ASSERT(addon_fields);
(*addon_length)= length;
(*m_packable_length)= packable_length;
length= (null_fields+7 )/8 ;
null_fields= 0 ;
SORT_ADDON_FIELD* addonf= addon_fields->begin();
for (pfield= table->field; (field= *pfield) ; pfield++)
{
if (!bitmap_is_set(read_set, field->field_index))
continue ;
addonf->field= field;
addonf->offset= length;
if (field->maybe_null())
{
addonf->null_offset= null_fields/8 ;
addonf->null_bit= 1 <<(null_fields & 7 );
null_fields++;
}
else
{
addonf->null_offset= 0 ;
addonf->null_bit= 0 ;
}
addonf->length= field->max_packed_col_length(field->pack_length());
length+= addonf->length;
addonf++;
}
DBUG_PRINT("info" ,("addon_length: %d" ,length));
DBUG_RETURN(addon_fields);
}
/*
* * functions to change a double or float to a sortable string
* * The following should work for IEEE
*/
#define DBL_EXP_DIG (sizeof (double )*8 -DBL_MANT_DIG)
void change_double_for_sort(double nr,uchar *to)
{
uchar *tmp=(uchar*) to;
if (nr == 0 .0 )
{ /* Change to zero string */
tmp[0 ]=(uchar) 128 ;
memset(tmp+1 , 0 , sizeof (nr)-1 );
}
else
{
#ifdef WORDS_BIGENDIAN
memcpy(tmp, &nr, sizeof (nr));
#else
{
uchar *ptr= (uchar*) &nr;
#if defined (__FLOAT_WORD_ORDER) && (__FLOAT_WORD_ORDER == __BIG_ENDIAN)
tmp[0 ]= ptr[3 ]; tmp[1 ]=ptr[2 ]; tmp[2 ]= ptr[1 ]; tmp[3 ]=ptr[0 ];
tmp[4 ]= ptr[7 ]; tmp[5 ]=ptr[6 ]; tmp[6 ]= ptr[5 ]; tmp[7 ]=ptr[4 ];
#else
tmp[0 ]= ptr[7 ]; tmp[1 ]=ptr[6 ]; tmp[2 ]= ptr[5 ]; tmp[3 ]=ptr[4 ];
tmp[4 ]= ptr[3 ]; tmp[5 ]=ptr[2 ]; tmp[6 ]= ptr[1 ]; tmp[7 ]=ptr[0 ];
#endif
}
#endif
if (tmp[0 ] & 128 ) /* Negative */
{ /* make complement */
uint i;
for (i=0 ; i < sizeof (nr); i++)
tmp[i]=tmp[i] ^ (uchar) 255 ;
}
else
{ /* Set high and move exponent one up */
ushort exp_part=(((ushort) tmp[0 ] << 8 ) | (ushort) tmp[1 ] |
(ushort) 32768 );
exp_part+= (ushort) 1 << (16 -1 -DBL_EXP_DIG);
tmp[0 ]= (uchar) (exp_part >> 8 );
tmp[1 ]= (uchar) exp_part;
}
}
}
bool SORT_INFO::using_packed_addons()
{
return addon_fields != NULL && addon_fields->using_packed_addons();
}
void SORT_INFO::free_addon_buff()
{
if (addon_fields)
addon_fields->free_addon_buff();
}
/*
Check if packed sortkeys are used or not
*/
bool SORT_INFO::using_packed_sortkeys()
{
return sort_keys != NULL && sort_keys->using_packed_sortkeys();
}
/**
Free SORT_INFO
*/
SORT_INFO::~SORT_INFO()
{
DBUG_ENTER("~SORT_INFO::SORT_INFO()" );
free_data();
DBUG_VOID_RETURN;
}
void Sort_param::try_to_pack_sortkeys()
{
#ifdef WITHOUT_PACKED_SORT_KEYS
return ;
#endif
uint size_of_packable_fields= sort_keys->get_size_of_packable_fields();
/*
Disable packing when all fields are fixed - size fields .
*/
if (size_of_packable_fields == 0 )
return ;
const uint sz= Sort_keys::size_of_length_field;
uint sort_len= sort_keys->get_sort_length_with_original_values();
/*
Heuristic introduced , skip packing sort keys if saving less than 128 bytes
*/
if (sort_len < 128 + sz + size_of_packable_fields)
return ;
sort_keys->set_using_packed_sortkeys(true );
m_packed_format= true ;
m_using_packed_sortkeys= true ;
sort_length= sort_len + sz + size_of_packable_fields +
(using_addon_fields() ? 0 : res_length);
/* Only the record length needs to be updated, the res_length does not need
to be updated
*/
rec_length= sort_length + addon_length;
}
uint
Type_handler_string_result::make_packed_sort_key_part(uchar *to, Item *item,
const SORT_FIELD_ATTR *sort_field,
String *tmp) const
{
CHARSET_INFO *cs= item->collation.collation;
bool maybe_null= item->maybe_null();
if (maybe_null)
*to++= 1 ;
Binary_string *res= item->str_result(tmp);
if (!res)
{
if (maybe_null)
{
*(to-1 )= 0 ;
return 0 ;
}
else
{
/* purecov: begin deadcode */
/*
This should only happen during extreme conditions if we run out
of memory or have an item marked not null when it can be null .
This code is here mainly to avoid a hard crash in this case .
*/
DBUG_ASSERT(0 );
DBUG_PRINT("warning" ,
("Got null on something that shouldn't be null" ));
memset(to, 0 , sort_field->length); // Avoid crash
/* purecov: end */
return sort_field->original_length;
}
}
return sort_field->pack_sort_string(to, res, cs);
}
uint
Type_handler_int_result::make_packed_sort_key_part(uchar *to, Item *item,
const SORT_FIELD_ATTR *sort_field,
String *tmp) const
{
longlong value= item->val_int_result();
return make_packed_sort_key_longlong(to, item->maybe_null(),
item->null_value, item->unsigned_flag,
value, sort_field);
}
uint
Type_handler_decimal_result::make_packed_sort_key_part(uchar *to, Item *item,
const SORT_FIELD_ATTR *sort_field,
String *tmp) const
{
my_decimal dec_buf, *dec_val= item->val_decimal_result(&dec_buf);
if (item->maybe_null())
{
if (item->null_value)
{
*to++=0 ;
return 0 ;
}
*to++= 1 ;
}
dec_val->to_binary(to, item->max_length - (item->decimals ? 1 : 0 ),
item->decimals);
DBUG_ASSERT(sort_field->original_length == sort_field->length);
return sort_field->original_length;
}
uint
Type_handler_real_result::make_packed_sort_key_part(uchar *to, Item *item,
const SORT_FIELD_ATTR *sort_field,
String *tmp) const
{
double value= item->val_result();
if (item->maybe_null())
{
if (item->null_value)
{
*to++=0 ;
return 0 ;
}
*to++= 1 ;
}
change_double_for_sort(value, to);
DBUG_ASSERT(sort_field->original_length == sort_field->length);
return sort_field->original_length;
}
uint
Type_handler_temporal_result::make_packed_sort_key_part(uchar *to, Item *item,
const SORT_FIELD_ATTR *sort_field,
String *tmp) const
{
MYSQL_TIME buf;
// This is a temporal type. No nanoseconds. Rounding mode is not important.
DBUG_ASSERT(item->cmp_type() == TIME_RESULT);
static const Temporal::Options opt(TIME_INVALID_DATES, TIME_FRAC_NONE);
if (item->get_date_result(current_thd, &buf, opt))
{
DBUG_ASSERT(item->maybe_null());
DBUG_ASSERT(item->null_value);
return make_packed_sort_key_longlong(to, item->maybe_null(), true ,
item->unsigned_flag, 0 , sort_field);
}
return make_packed_sort_key_longlong(to, item->maybe_null(), false ,
item->unsigned_flag, pack_time(&buf),
sort_field);
}
uint
Type_handler_timestamp_common::make_packed_sort_key_part(uchar *to, Item *item,
const SORT_FIELD_ATTR *sort_field,
String *tmp) const
{
THD *thd= current_thd;
decimal_digits_t dec= MY_MIN(item->decimals, TIME_SECOND_PART_DIGITS);
uint binlen= my_timestamp_binary_length(dec);
Timestamp_or_zero_datetime_native_null native(thd, item);
if (native.is_null() || native.is_zero_datetime())
{
// NULL or '0000-00-00 00:00:00'
if (item->maybe_null())
{
*to++=0 ;
return 0 ;
}
else
{
bzero(to, binlen);
return binlen;
}
}
else
{
if (item->maybe_null())
*to++= 1 ;
if (native.length() != binlen)
{
/*
Some items can return native representation with a different
number of fractional digits , e . g . : GREATEST ( ts_3 , ts_4 ) can
return a value with 3 fractional digits , although its fractional
precision is 4 . Re - pack with a proper precision now .
*/
Timestamp(native).to_native(&native, item->datetime_precision(thd));
}
DBUG_ASSERT(native.length() == binlen);
memcpy((char *) to, native.ptr(), binlen);
return binlen;
}
}
/*
@ brief
Reverse the key for DESC clause
@ param to buffer where values are written
@ param maybe_null nullability of a column
@ param sort_field Sort field structure
@ details
used for mem - comparable sort keys
*/
void reverse_key(uchar *to, const SORT_FIELD_ATTR *sort_field)
{
uint length;
if (sort_field->maybe_null && (to[-1 ]= !to[-1 ]))
{
to+= sort_field->length; // don't waste the time reversing all 0's
return ;
}
length=sort_field->length;
while (length--)
{
*to = (uchar) (~ *to);
to++;
}
}
/*
@ brief
Check if packing sort keys is allowed
@ param THD thread structure
@ retval
TRUE packing allowed
FALSE packing not allowed
*/
bool SORT_FIELD_ATTR::check_if_packing_possible(THD *thd) const
{
/*
Packing not allowed when original length is greater than max_sort_length
and we have a complex collation because cutting a prefix is not safe in
such a case
*/
if (original_length > thd->variables.max_sort_length &&
cs->state & MY_CS_NON1TO1)
return false ;
return true ;
}
void SORT_FIELD_ATTR::set_length_and_original_length(THD *thd, uint length_arg)
{
length= length_arg;
if (is_variable_sized())
set_if_smaller(length, thd->variables.max_sort_length);
original_length= length_arg;
}
/*
Compare function used for packing sort keys
*/
qsort_cmp2 get_packed_keys_compare_ptr()
{
return compare_packed_sort_keys;
}
/*
Compare two varstrings .
The strings are in this data format :
[ null_byte ] [ length of string + suffix_bytes ] [ the string ] [ suffix_bytes ]
suffix_bytes are used only for binary columns .
*/
int SORT_FIELD_ATTR::compare_packed_varstrings(const uchar *a, size_t *a_len,
const uchar *b, size_t *b_len)
{
int retval;
size_t a_length, b_length;
if (maybe_null)
{
*a_len= *b_len= 1 ; // NULL bytes are always stored
if (*a != *b)
{
// Note we don't return a proper value in *{a|b}_len for the non-NULL
// value but that's ok
if (*a == 0 )
return -1 ;
else
return 1 ;
}
else
{
if (*a == 0 )
return 0 ;
}
a++;
b++;
}
else
*a_len= *b_len= 0 ;
a_length= read_keypart_length(a, length_bytes);
b_length= read_keypart_length(b, length_bytes);
*a_len+= length_bytes + a_length;
*b_len+= length_bytes + b_length;
retval= cs->strnncollsp(a + length_bytes,
a_length - suffix_length,
b + length_bytes,
b_length - suffix_length);
if (!retval && suffix_length)
{
DBUG_ASSERT(cs == &my_charset_bin);
// comparing the length stored in suffix bytes for binary strings
a= a + length_bytes + a_length - suffix_length;
b= b + length_bytes + b_length - suffix_length;
retval= memcmp(a, b, suffix_length);
}
return retval;
}
/*
A value comparison function that has a signature that ' s suitable for
comparing packed values , but actually compares fixed - size values with memcmp .
This is used for ordering fixed - size columns when the sorting procedure used
packed - value format .
*/
int SORT_FIELD_ATTR::compare_packed_fixed_size_vals(const uchar *a, size_t *a_len,
const uchar *b, size_t *b_len)
{
if (maybe_null)
{
*a_len=1 ;
*b_len=1 ;
if (*a != *b)
{
if (*a == 0 )
return -1 ;
else
return 1 ;
}
else
{
if (*a == 0 )
return 0 ;
}
a++;
b++;
}
else
*a_len= *b_len= 0 ;
*a_len+= length;
*b_len+= length;
return memcmp(a,b, length);
}
/*
@ brief
Comparison function to compare two packed sort keys
@ param sort_param cmp argument
@ param a_ptr packed sort key
@ param b_ptr packed sort key
@ retval
> 0 key a_ptr greater than b_ptr
= 0 key a_ptr equal to b_ptr
< 0 key a_ptr less than b_ptr
*/
int compare_packed_sort_keys(void *sort_param, const void *a_ptr,
const void *b_ptr)
{
int retval= 0 ;
size_t a_len, b_len;
Sort_param *param= static_cast <Sort_param *>(sort_param);
Sort_keys *sort_keys= param->sort_keys;
auto a= *(static_cast <const uchar *const *>(a_ptr));
auto b= *(static_cast <const uchar *const *>(b_ptr));
a+= Sort_keys::size_of_length_field;
b+= Sort_keys::size_of_length_field;
for (SORT_FIELD *sort_field= sort_keys->begin();
sort_field != sort_keys->end(); sort_field++)
{
retval= sort_field->is_variable_sized() ?
sort_field->compare_packed_varstrings(a, &a_len, b, &b_len) :
sort_field->compare_packed_fixed_size_vals(a, &a_len, b, &b_len);
if (retval)
return sort_field->reverse ? -retval : retval;
a+= a_len;
b+= b_len;
}
/*
this comparison is done for the case when the sort keys is appended with
the ROW_ID pointer . For such cases we don ' t have addon fields
so we can make a memcmp check over both the sort keys
*/
if (!param->using_addon_fields())
retval= memcmp(a, b, param->res_length);
return retval;
}
/*
@ brief
Store a packed string in the buffer
@ param to buffer
@ param str packed string value
@ param cs character set
@ details
This function writes to the buffer the packed value of a key_part
of the sort key .
The values written to the buffer are in this order
- value for null byte
- length of the string
- value of the string
- suffix length ( for binary character set )
*/
uint
SORT_FIELD_ATTR::pack_sort_string(uchar *to, const Binary_string *str,
CHARSET_INFO *cs) const
{
uchar *orig_to= to;
uint32 length, data_length;
DBUG_ASSERT(str->length() <= UINT32_MAX);
length= (uint32) str->length();
if (length + suffix_length <= original_length)
{
data_length= length;
}
else
{
data_length= original_length - suffix_length;
current_thd->num_of_strings_sorted_on_truncated_length++;
}
// length stored in lowendian form
store_key_part_length(data_length + suffix_length, to, length_bytes);
to+= length_bytes;
// copying data length bytes to the buffer
memcpy(to, (uchar*)str->ptr(), data_length);
to+= data_length;
if (cs == &my_charset_bin && suffix_length)
{
// suffix length stored in bigendian form
store_bigendian(length, to, suffix_length);
to+= suffix_length;
}
return static_cast <uint>(to - orig_to);
}
/*
@ brief
Create a mem - comparable sort key
@ param param sort param structure
@ param to buffer where values are written
@ retval
length of the bytes written including the NULL bytes
*/
static uint make_sortkey(Sort_param *param, uchar *to)
{
Field *field;
SORT_FIELD *sort_field;
uchar *orig_to= to;
for (sort_field=param->local_sortorder.begin() ;
sort_field != param->local_sortorder.end() ;
sort_field++)
{
bool maybe_null=0 ;
if ((field=sort_field->field))
{ // Field
field->make_sort_key_part(to, sort_field->length);
if ((maybe_null= field->maybe_null()))
to++;
}
else
{ // Item
sort_field->item->type_handler()->make_sort_key_part(to, sort_field->item,
sort_field,
¶m->tmp_buffer);
if ((maybe_null= sort_field->item->maybe_null()))
to++;
}
if (sort_field->reverse)
reverse_key(to, sort_field);
to+= sort_field->length;
}
DBUG_ASSERT(static_cast <uint>(to - orig_to) <= param->sort_length);
return static_cast <uint>(to - orig_to);
}
/*
@ brief
create a compact sort key which can be compared with a comparison
function . They are called packed sort keys
@ param param sort param structure
@ param to buffer where values are written
@ retval
length of the bytes written including the NULL bytes
*/
static uint make_packed_sortkey(Sort_param *param, uchar *to)
{
Field *field;
SORT_FIELD *sort_field;
uint length;
uchar *orig_to= to;
to+= Sort_keys::size_of_length_field;
for (sort_field=param->local_sortorder.begin() ;
sort_field != param->local_sortorder.end() ;
sort_field++)
{
bool maybe_null=0 ;
if ((field=sort_field->field))
{
// Field
length= field->make_packed_sort_key_part(to, sort_field);
if ((maybe_null= field->maybe_null()))
to++;
}
else
{ // Item
Item *item= sort_field->item;
length= item->type_handler()->make_packed_sort_key_part(to, item,
sort_field,
¶m->tmp_buffer);
if ((maybe_null= sort_field->item->maybe_null()))
to++;
}
to+= length;
}
length= static_cast <int >(to - orig_to);
DBUG_ASSERT(length <= param->sort_length);
Sort_keys::store_sortkey_length(orig_to, length);
return length;
}
/*
@ brief
Format the row record and store it in the output
@ detail
Call this function with print_names = true , reqd_table_alias = " tbl " ,
separator = " = " and the output would have this form :
tbl ( col1 , col2 , . . . , coln ) = ( val1 , val2 , . . . , valn )
*/
void format_and_store_row(TABLE *table, const uchar *rec, bool print_names,
const char *separator, bool reqd_table_alias,
String &output)
{
Field **pfield;
char row_buff_tmp[512 ];
// charset will be overwritten below using field's charset
String tmp(row_buff_tmp, sizeof (row_buff_tmp), &my_charset_utf8mb4_bin);
auto move_back_lambda= [table, rec]() mutable {
table->move_fields(table->field, table->record[0 ], rec);
};
auto move_back_guard= make_scope_exit(move_back_lambda, false );
if (rec != table->record[0 ])
{
table->move_fields(table->field, rec, table->record[0 ]);
move_back_guard.engage();
}
bool check_also_write_set= false ;
if (table->reginfo.lock_type >= TL_WRITE_ALLOW_WRITE && table->write_set)
{
/* Print the column if it is present in either read_set or write_set */
check_also_write_set= true ;
}
if (reqd_table_alias)
{
output.append(table->alias);
}
bool first= true ;
if (print_names)
{
output.append('(' );
for (pfield= table->field; *pfield; pfield++)
{
Field *field= *pfield;
bool print_col= false ;
if ((check_also_write_set &&
bitmap_is_set(table->write_set, field->field_index)) ||
(table->read_set &&
bitmap_is_set(table->read_set, field->field_index)))
print_col= true ;
if (!print_col)
continue ;
if (first)
first= false ;
else
output.append(STRING_WITH_LEN(", " ));
output.append((*pfield)->field_name.str ? field->field_name
: NULL_clex_str);
}
output.append(STRING_WITH_LEN(")" ));
first= true ;
}
output.append(separator, strlen(separator));
output.append(STRING_WITH_LEN("(" ));
for (pfield= table->field; *pfield; pfield++)
{
Field *field= *pfield;
bool require_quote= false ;
bool print_col= false ;
bool clear_read_bit= false ;
if (table->read_set && bitmap_is_set(table->read_set, field->field_index))
print_col= true ;
else if ((check_also_write_set && bitmap_is_set(table->write_set,
field->field_index)))
{
print_col= true ;
bitmap_set_bit(table->read_set, field->field_index);
clear_read_bit= true ;
}
if (!print_col)
continue ;
if (first)
first= false ;
else
output.append(STRING_WITH_LEN(", " ));
if (field->is_null())
output.append(&NULL_clex_str);
else
{
if (field->type() == MYSQL_TYPE_BIT)
(void ) field->val_int_as_str(&tmp, 1 );
else
{
switch (field->type())
{
case MYSQL_TYPE_VARCHAR:
case MYSQL_TYPE_STRING:
case MYSQL_TYPE_VAR_STRING:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_TIME2:
case MYSQL_TYPE_DATETIME2:
case MYSQL_TYPE_TIMESTAMP2:
case MYSQL_TYPE_TIME:
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_TIMESTAMP:
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_YEAR:
case MYSQL_TYPE_ENUM:
case MYSQL_TYPE_SET:
case MYSQL_TYPE_GEOMETRY:
require_quote= true ;
break ;
default :
break ;
}
field->val_str(&tmp);
}
/*
Emit non - empty values as a hex literal whenever converting field ' s
charset to the output charset conversion is lossy ; otherwise emit the
charset - converted value , quoted only when the type requires it .
*/
if (require_quote && tmp.length() &&
!String::is_charset_conversion_lossless(tmp.charset(),
output.charset()))
{
output.append(STRING_WITH_LEN("0x" ));
output.append_hex(tmp.ptr(), tmp.length());
}
else
{
if (require_quote)
output.append('\' ');
output.append_for_single_quote_opt_convert(tmp);
if (require_quote)
output.append('\' ');
}
}
if (clear_read_bit)
bitmap_clear_bit(table->read_set, field->field_index);
}
output.append(')' );
}
Messung V0.5 in Prozent C=95 H=85 G=90
¤ Dauer der Verarbeitung: 0.78 Sekunden
(vorverarbeitet am 2026-10-08)
¤
*© Formatika GbR, Deutschland